Introduce command touch
This commit is contained in:
parent
bb0263e40b
commit
f07ec0fa5b
6 changed files with 214 additions and 3 deletions
19
README.md
19
README.md
|
@ -180,4 +180,21 @@ Like the `ls` command, lists directory contents, but annotated from Ghee's point
|
|||
Each path is marked as either a table or a record. For tables, the primary key is given.
|
||||
|
||||
* `ghee ls`: lists the current directory's contents
|
||||
* `ghee ls example`: lists the contents of ./example
|
||||
* `ghee ls example`: lists the contents of ./example
|
||||
|
||||
### Commit
|
||||
|
||||
### Log
|
||||
|
||||
### Touch
|
||||
|
||||
Similar to the Unix `touch` command, creates an empty file at the specified path;
|
||||
if the path is part of a Ghee table, xattrs are inferred from the path and written
|
||||
to the new file.
|
||||
|
||||
This is a convenient way to add new records to new tables.
|
||||
|
||||
With `-p / --parents`, parent directories will be created.
|
||||
|
||||
* `ghee touch /pizza/pepperoni`: creates an empty file at `/pizza/pepperoni`, setting
|
||||
xattr `topping` to `pepperoni` because the key of the `/pizza` table is `topping`.
|
|
@ -6,7 +6,7 @@ use rustyline::DefaultEditor;
|
|||
use thiserror::Error;
|
||||
|
||||
use ghee::{
|
||||
cmd::{commit, cp, create, del, get, idx, init, ins, log, ls, mv, rm, set},
|
||||
cmd::{commit, cp, create, del, get, idx, init, ins, log, ls, mv, rm, set, touch},
|
||||
parser::{
|
||||
assignment::{Assignment, AssignmentParser},
|
||||
key::Key,
|
||||
|
@ -221,6 +221,14 @@ enum Commands {
|
|||
)]
|
||||
dir: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Create a file, inferring xattrs from path if part of a table
|
||||
Touch {
|
||||
#[arg(help = "Path at which to create the file")]
|
||||
path: PathBuf,
|
||||
#[arg(short, long, help = "Create parent directories")]
|
||||
parents: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn repl() -> rustyline::Result<()> {
|
||||
|
@ -434,6 +442,11 @@ fn run_command(cmd: &Commands) {
|
|||
|
||||
log(dir).unwrap_or_else(|e| panic!("Could not print commit log: {}", e));
|
||||
}
|
||||
|
||||
Commands::Touch { path, parents } => {
|
||||
touch(path, *parents)
|
||||
.unwrap_or_else(|e| panic!("Could not touch path {}: {}", path.display(), e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ mod ls;
|
|||
mod mv;
|
||||
mod rm;
|
||||
mod set;
|
||||
mod touch;
|
||||
pub use commit::commit;
|
||||
pub use cp::cp;
|
||||
pub use create::create;
|
||||
|
@ -25,3 +26,4 @@ pub use ls::ls;
|
|||
pub use mv::mv;
|
||||
pub use rm::rm;
|
||||
pub use set::set;
|
||||
pub use touch::touch;
|
||||
|
|
167
src/cmd/touch.rs
Normal file
167
src/cmd/touch.rs
Normal file
|
@ -0,0 +1,167 @@
|
|||
use std::{
|
||||
fs::{create_dir_all, File},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{containing_table_info, write_xattr_values};
|
||||
|
||||
/**
|
||||
* Create a file at `path`; if part of a table, initialize its
|
||||
* xattrs from those inferred from the path.
|
||||
*
|
||||
* Parent directories will be created as necessary.
|
||||
*/
|
||||
pub fn touch(path: &PathBuf, create_parents: bool) -> Result<()> {
|
||||
if create_parents {
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
}
|
||||
|
||||
File::create(&path)?;
|
||||
|
||||
if let Some(info) = containing_table_info(path)? {
|
||||
let xattrs = info.key.record_for_path(info.path(), path)?;
|
||||
|
||||
write_xattr_values(path, &xattrs)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use tempdir::TempDir;
|
||||
|
||||
use crate::{cmd::init, parser::key::Key, xattr_values};
|
||||
|
||||
use super::touch;
|
||||
|
||||
#[test]
|
||||
fn test_touch() {
|
||||
let dir = TempDir::new("ghee-test-touch").unwrap().into_path();
|
||||
|
||||
let f = {
|
||||
let mut p = dir.clone();
|
||||
p.push("README.md");
|
||||
p
|
||||
};
|
||||
|
||||
assert!(dir.exists());
|
||||
|
||||
assert!(!f.exists());
|
||||
|
||||
touch(&f, false).unwrap();
|
||||
|
||||
assert!(f.exists());
|
||||
|
||||
let xattrs = xattr_values(&f).unwrap();
|
||||
|
||||
assert_eq!(xattrs.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_no_parent() {
|
||||
let dir = TempDir::new("ghee-test-touch-no-parent")
|
||||
.unwrap()
|
||||
.into_path();
|
||||
|
||||
let sub = {
|
||||
let mut p = dir.clone();
|
||||
p.push("fasteners");
|
||||
p
|
||||
};
|
||||
|
||||
let f = {
|
||||
let mut p = sub.clone();
|
||||
p.push("velcro");
|
||||
p
|
||||
};
|
||||
|
||||
assert!(dir.exists());
|
||||
|
||||
assert!(!sub.exists());
|
||||
|
||||
assert!(!f.exists());
|
||||
|
||||
touch(&f, false).expect_err("Should have failed due to nonexisting parent dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_create_parents() {
|
||||
let dir = TempDir::new("ghee-test-touch-create-parents")
|
||||
.unwrap()
|
||||
.into_path();
|
||||
|
||||
let sub = {
|
||||
let mut p = dir.clone();
|
||||
p.push("fasteners");
|
||||
p
|
||||
};
|
||||
|
||||
let f = {
|
||||
let mut p = sub.clone();
|
||||
p.push("velcro");
|
||||
p
|
||||
};
|
||||
|
||||
assert!(dir.exists());
|
||||
|
||||
assert!(!sub.exists());
|
||||
|
||||
assert!(!f.exists());
|
||||
|
||||
touch(&f, true).unwrap();
|
||||
|
||||
assert!(dir.exists());
|
||||
|
||||
assert!(sub.exists());
|
||||
|
||||
assert!(f.exists());
|
||||
|
||||
let xattrs = xattr_values(&f).unwrap();
|
||||
|
||||
assert_eq!(xattrs.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_inferred_xattrs() {
|
||||
let dir = TempDir::new("ghee-test-touch-inferred-xattrs")
|
||||
.unwrap()
|
||||
.into_path();
|
||||
|
||||
init(&dir, &Key::from_string("color,cost"), false).unwrap();
|
||||
|
||||
let sub = {
|
||||
let mut p = dir.clone();
|
||||
p.push("red");
|
||||
p
|
||||
};
|
||||
|
||||
let f = {
|
||||
let mut p = sub.clone();
|
||||
p.push("5");
|
||||
p
|
||||
};
|
||||
|
||||
assert!(dir.exists());
|
||||
|
||||
assert!(!sub.exists());
|
||||
|
||||
assert!(!f.exists());
|
||||
|
||||
touch(&f, true).unwrap();
|
||||
|
||||
assert!(dir.exists());
|
||||
|
||||
assert!(sub.exists());
|
||||
|
||||
assert!(f.exists());
|
||||
|
||||
let xattrs = xattr_values(&f).unwrap();
|
||||
|
||||
assert_eq!(xattrs.len(), 2);
|
||||
}
|
||||
}
|
|
@ -282,6 +282,13 @@ pub fn xattr_values<P: AsRef<Path>>(path: P) -> Result<Record> {
|
|||
Ok(values)
|
||||
}
|
||||
|
||||
pub fn write_xattr_values<P: AsRef<Path>>(path: P, record: &Record) -> Result<()> {
|
||||
for (xattr, value) in record {
|
||||
xattr::set(&path, xattr.to_osstring(), value.as_bytes().as_slice())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GetKeyErr {
|
||||
#[error("An IO error occurred while getting the key")]
|
||||
|
|
|
@ -9,7 +9,7 @@ use nom::{character::complete::char, combinator::map, multi::separated_list1, IR
|
|||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{xattr_values, Record};
|
||||
use crate::{xattr_values, xattr_values_from_path, Record};
|
||||
|
||||
use super::{
|
||||
value::Value,
|
||||
|
@ -155,6 +155,11 @@ impl Key {
|
|||
|
||||
Ok(base_path)
|
||||
}
|
||||
|
||||
/// The xattrs inferred from the path itself
|
||||
pub fn record_for_path(&self, base_path: &PathBuf, path: &PathBuf) -> Result<Record> {
|
||||
xattr_values_from_path(self, base_path, path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ToString, I: IntoIterator<Item = S>> From<I> for Key {
|
||||
|
|
Loading…
Reference in a new issue