nushell/crates/nu-command/src/commands/path/join.rs
Jakub Žádník cc3653cfd9
Path commands: Put column path args behid flag; Allow path join appending without flag (#4008)
* Change path join signature

* Appending now works without flag
* Column path operation is behind a -c flag

* Move column path arg retrieval to a function

Also improves errors

* Fix path join tests

* Propagate column path changes to all path commands

* Update path command examples with columns paths

* Modernize path command examples by removing "echo"

* Improve structured path error message

* Fix typo
2021-09-15 21:03:51 +03:00

194 lines
6.0 KiB
Rust

use super::{
column_paths_from_args, handle_value, join_path, operate_column_paths, PathSubcommandArguments,
};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::{Path, PathBuf};
pub struct PathJoin;
struct PathJoinArguments {
columns: Vec<ColumnPath>,
append: Option<Tagged<PathBuf>>,
}
impl PathSubcommandArguments for PathJoinArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.columns
}
}
impl WholeStreamCommand for PathJoin {
fn name(&self) -> &str {
"path join"
}
fn signature(&self) -> Signature {
Signature::build("path join")
.named(
"columns",
SyntaxShape::Table,
"Optionally operate by column path",
Some('c'),
)
.optional(
"append",
SyntaxShape::FilePath,
"Path to append to the input",
)
}
fn usage(&self) -> &str {
"Join a structured path or a list of path parts."
}
fn extra_usage(&self) -> &str {
r#"Optionally, append an additional path to the result. It is designed to accept
the output of 'path parse' and 'path split' subcommands."#
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let cmd_args = Arc::new(PathJoinArguments {
columns: column_paths_from_args(&args)?,
append: args.opt(0)?,
});
Ok(operate_join(args.input, &action, tag, cmd_args))
}
#[cfg(windows)]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Append a filename to a path",
example: r"'C:\Users\viking' | path join spam.txt",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"C:\Users\viking\spam.txt",
))]),
},
Example {
description: "Append a filename to a path inside a column",
example: r"ls | path join spam.txt -c [ name ]",
result: None,
},
Example {
description: "Join a list of parts into a path",
example: r"[ 'C:' '\' 'Users' 'viking' 'spam.txt' ] | path join",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"C:\Users\viking\spam.txt",
))]),
},
Example {
description: "Join a structured path into a path",
example: r"[ [parent stem extension]; ['C:\Users\viking' 'spam' 'txt']] | path join",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"C:\Users\viking\spam.txt",
))]),
},
]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Append a filename to a path",
example: r"'/home/viking' | path join spam.txt",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"/home/viking/spam.txt",
))]),
},
Example {
description: "Append a filename to a path inside a column",
example: r"ls | path join spam.txt -c [ name ]",
result: None,
},
Example {
description: "Join a list of parts into a path",
example: r"[ '/' 'home' 'viking' 'spam.txt' ] | path join",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"/home/viking/spam.txt",
))]),
},
Example {
description: "Join a structured path into a path",
example: r"[[ parent stem extension ]; [ '/home/viking' 'spam' 'txt' ]] | path join",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"/home/viking/spam.txt",
))]),
},
]
}
}
fn operate_join<F, T>(
input: crate::InputStream,
action: &'static F,
tag: Tag,
args: Arc<T>,
) -> OutputStream
where
T: PathSubcommandArguments + Send + Sync + 'static,
F: Fn(&Path, Tag, &T) -> Value + Send + Sync + 'static,
{
let span = tag.span;
if args.get_column_paths().is_empty() {
let mut parts = input.peekable();
let has_rows = matches!(
parts.peek(),
Some(&Value {
value: UntaggedValue::Row(_),
..
})
);
if has_rows {
// operate one-by-one like the other path subcommands
parts
.into_iter()
.map(
move |v| match handle_value(&action, &v, span, Arc::clone(&args)) {
Ok(v) => v,
Err(e) => Value::error(e),
},
)
.into_output_stream()
} else {
// join the whole input stream
match join_path(&parts.collect_vec(), &span) {
Ok(path_buf) => OutputStream::one(action(&path_buf, tag, &args)),
Err(e) => OutputStream::one(Value::error(e)),
}
}
} else {
operate_column_paths(input, action, span, args)
}
}
fn action(path: &Path, tag: Tag, args: &PathJoinArguments) -> Value {
if let Some(ref append) = args.append {
UntaggedValue::filepath(path.join(&append.item)).into_value(tag)
} else {
UntaggedValue::filepath(path).into_value(tag)
}
}
#[cfg(test)]
mod tests {
use super::PathJoin;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(PathJoin {})
}
}