From 94a26abf21ca1062ea3c18fb6873dd08bb299414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 22 May 2021 17:29:40 +0300 Subject: [PATCH] Implement `path relative-to` subcommand (#3461) * Register new path relative-to command * Implement `path relative-to` subcommand --- crates/nu-command/src/commands.rs | 4 +- .../src/commands/default_context.rs | 1 + crates/nu-command/src/commands/path/mod.rs | 2 + .../src/commands/path/relative_to.rs | 119 ++++++++++++++++++ 4 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/src/commands/path/relative_to.rs diff --git a/crates/nu-command/src/commands.rs b/crates/nu-command/src/commands.rs index aa148ce544..40105082aa 100644 --- a/crates/nu-command/src/commands.rs +++ b/crates/nu-command/src/commands.rs @@ -240,8 +240,8 @@ pub(crate) use nth::Nth; pub(crate) use open::Open; pub(crate) use parse::Parse; pub(crate) use path::{ - PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathJoin, PathParse, PathSplit, - PathType, + PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathJoin, PathParse, + PathRelativeTo, PathSplit, PathType, }; pub(crate) use pivot::Pivot; pub(crate) use prepend::Prepend; diff --git a/crates/nu-command/src/commands/default_context.rs b/crates/nu-command/src/commands/default_context.rs index 9ba9d98d3e..76532a8203 100644 --- a/crates/nu-command/src/commands/default_context.rs +++ b/crates/nu-command/src/commands/default_context.rs @@ -240,6 +240,7 @@ pub fn create_default_context(interactive: bool) -> Result, + rest: Vec, +} + +impl PathSubcommandArguments for PathRelativeToArguments { + fn get_column_paths(&self) -> &Vec { + &self.rest + } +} + +impl WholeStreamCommand for PathRelativeTo { + fn name(&self) -> &str { + "path relative-to" + } + + fn signature(&self) -> Signature { + Signature::build("path relative-to") + .required( + "path", + SyntaxShape::FilePath, + "Parent shared with the input path", + ) + .rest(SyntaxShape::ColumnPath, "Optionally operate by column path") + } + + fn usage(&self) -> &str { + "Get a path as relative to another path." + } + + fn extra_usage(&self) -> &str { + r#"Can be used only when the input and the argument paths are either both +absolute or both relative. The argument path needs to be a parent of the input +path."# + } + + fn run(&self, args: CommandArgs) -> Result { + let tag = args.call_info.name_tag.clone(); + let args = args.evaluate_once()?; + let cmd_args = Arc::new(PathRelativeToArguments { + path: args.req(0)?, + rest: args.rest(1)?, + }); + + Ok(operate(args.input, &action, tag.span, cmd_args)) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Find a relative path from two absolute paths", + example: r"'C:\Users\viking' | path relative-to 'C:\Users'", + result: Some(vec![Value::from(UntaggedValue::filepath(r"viking"))]), + }, + Example { + description: "Find a relative path from two relative paths", + example: r"'eggs\bacon\sausage\spam' | path relative-to 'eggs\bacon\sausage'", + result: Some(vec![Value::from(UntaggedValue::filepath(r"spam"))]), + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Find a relative path from two absolute paths", + example: r"'/home/viking' | path relative-to '/home'", + result: Some(vec![Value::from(UntaggedValue::filepath(r"viking"))]), + }, + Example { + description: "Find a relative path from two relative paths", + example: r"'eggs/bacon/sausage/spam' | path relative-to 'eggs/bacon/sausage'", + result: Some(vec![Value::from(UntaggedValue::filepath(r"spam"))]), + }, + ] + } +} + +fn action(path: &Path, tag: Tag, args: &PathRelativeToArguments) -> Value { + match path.strip_prefix(&args.path.item) { + Ok(p) => UntaggedValue::filepath(p).into_value(tag), + Err(_) => Value::error(ShellError::labeled_error_with_secondary( + format!( + "'{}' is not a subpath of '{}'", + path.to_string_lossy(), + &args.path.item.to_string_lossy() + ), + "should be a parent of the input path", + args.path.tag.span, + "originates from here", + tag.span, + )), + } +} + +#[cfg(test)] +mod tests { + use super::PathRelativeTo; + use super::ShellError; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(PathRelativeTo {}) + } +}