Introduce command touch

This commit is contained in:
Josh Hansen 2023-09-22 17:11:01 -07:00
parent bb0263e40b
commit f07ec0fa5b
6 changed files with 214 additions and 3 deletions

View file

@ -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`.

View file

@ -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));
}
}
}

View file

@ -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
View 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);
}
}

View file

@ -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")]

View file

@ -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 {