nushell/crates/nu-command/src/commands/path/join.rs
Jakub Žádník 3075e2cfbf
Remove rest_args() from evaluated CommandArgs (#3449)
It was too error prone when positional arguments were used with the rest
arguments. Now, you need to explicitly state from which position you
want to count the rest args (e.g., `rest(0)`).
2021-05-20 10:26:23 +12:00

178 lines
5.5 KiB
Rust

use super::{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 {
rest: Vec<ColumnPath>,
append: Option<Tagged<PathBuf>>,
}
impl PathSubcommandArguments for PathJoinArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
impl WholeStreamCommand for PathJoin {
fn name(&self) -> &str {
"path join"
}
fn signature(&self) -> Signature {
Signature::build("path join")
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
.named(
"append",
SyntaxShape::FilePath,
"Path to append to the input",
Some('a'),
)
}
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 args = args.evaluate_once()?;
let cmd_args = Arc::new(PathJoinArguments {
rest: args.rest(0)?,
append: args.get_flag("append")?,
});
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"echo 'C:\Users\viking' | path join -a spam.txt",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"C:\Users\viking\spam.txt",
))]),
},
Example {
description: "Join a list of parts into a path",
example: r"echo [ '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"echo [ [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"echo '/home/viking' | path join -a spam.txt",
result: Some(vec![Value::from(UntaggedValue::filepath(
r"/home/viking/spam.txt",
))]),
},
Example {
description: "Join a list of parts into a path",
example: r"echo [ '/' '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"echo [[ 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),
},
)
.to_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 {})
}
}