Refactor path commands (#9687)

This commit is contained in:
Jakub Žádník 2023-07-15 00:04:22 +03:00 committed by GitHub
parent 8c52b7a23a
commit ba766de5d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 254 additions and 338 deletions

View file

@ -11,15 +11,10 @@ use nu_protocol::{
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments {
columns: Option<Vec<String>>,
replace: Option<Spanned<String>>, replace: Option<Spanned<String>>,
} }
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -33,15 +28,11 @@ impl Command for SubCommand {
Signature::build("path basename") Signature::build("path basename")
.input_output_types(vec![ .input_output_types(vec![
(Type::String, Type::String), (Type::String, Type::String),
// TODO: Why do these commands not use CellPaths in a standard way? (
(Type::Table(vec![]), Type::Table(vec![])), Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
]) ])
.named(
"columns",
SyntaxShape::Table(vec![]),
"For a record or table input, convert strings in the given columns to their basename",
Some('c'),
)
.named( .named(
"replace", "replace",
SyntaxShape::String, SyntaxShape::String,
@ -63,7 +54,6 @@ impl Command for SubCommand {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments {
columns: call.get_flag(engine_state, stack, "columns")?,
replace: call.get_flag(engine_state, stack, "replace")?, replace: call.get_flag(engine_state, stack, "replace")?,
}; };
@ -86,21 +76,12 @@ impl Command for SubCommand {
result: Some(Value::test_string("test.txt")), result: Some(Value::test_string("test.txt")),
}, },
Example { Example {
description: "Get basename of a path in a column", description: "Get basename of a list of paths",
example: "ls .. | path basename -c [ name ]", example: r"[ C:\Users\joe, C:\Users\doe ] | path basename",
result: None, result: Some(Value::test_list(vec![
}, Value::test_string("joe"),
Example { Value::test_string("doe"),
description: "Get basename of a path in a column", ])),
example: "[[name];[C:\\Users\\Joe]] | path basename -c [ name ]",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["name".to_string()],
vals: vec![Value::test_string("Joe")],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Replace basename of a path", description: "Replace basename of a path",
@ -119,16 +100,12 @@ impl Command for SubCommand {
result: Some(Value::test_string("test.txt")), result: Some(Value::test_string("test.txt")),
}, },
Example { Example {
description: "Get basename of a path by column", description: "Get basename of a list of paths",
example: "[[name];[/home/joe]] | path basename -c [ name ]", example: "[ /home/joe, /home/doe ] | path basename",
result: Some(Value::List { result: Some(Value::test_list(vec![
vals: vec![Value::Record { Value::test_string("joe"),
cols: vec!["name".to_string()], Value::test_string("doe"),
vals: vec![Value::test_string("joe")], ])),
span: Span::test_data(),
}],
span: Span::test_data(),
}),
}, },
Example { Example {
description: "Replace basename of a path", description: "Replace basename of a path",

View file

@ -11,16 +11,11 @@ use nu_protocol::{
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments {
columns: Option<Vec<String>>,
replace: Option<Spanned<String>>, replace: Option<Spanned<String>>,
num_levels: Option<i64>, num_levels: Option<i64>,
} }
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -32,13 +27,13 @@ impl Command for SubCommand {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path dirname") Signature::build("path dirname")
.input_output_types(vec![(Type::String, Type::String)]) .input_output_types(vec![
.named( (Type::String, Type::String),
"columns", (
SyntaxShape::Table(vec![]), Type::List(Box::new(Type::String)),
"For a record or table input, convert strings at the given columns to their dirname", Type::List(Box::new(Type::String)),
Some('c'), ),
) ])
.named( .named(
"replace", "replace",
SyntaxShape::String, SyntaxShape::String,
@ -66,7 +61,6 @@ impl Command for SubCommand {
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments {
columns: call.get_flag(engine_state, stack, "columns")?,
replace: call.get_flag(engine_state, stack, "replace")?, replace: call.get_flag(engine_state, stack, "replace")?,
num_levels: call.get_flag(engine_state, stack, "num-levels")?, num_levels: call.get_flag(engine_state, stack, "num-levels")?,
}; };
@ -90,9 +84,12 @@ impl Command for SubCommand {
result: Some(Value::test_string("C:\\Users\\joe\\code")), result: Some(Value::test_string("C:\\Users\\joe\\code")),
}, },
Example { Example {
description: "Get dirname of a path in a column", description: "Get dirname of a list of paths",
example: "ls ('.' | path expand) | path dirname -c [ name ]", example: r"[ C:\Users\joe\test.txt, C:\Users\doe\test.txt ] | path dirname",
result: None, result: Some(Value::test_list(vec![
Value::test_string(r"C:\Users\joe"),
Value::test_string(r"C:\Users\doe"),
])),
}, },
Example { Example {
description: "Walk up two levels", description: "Walk up two levels",
@ -117,9 +114,12 @@ impl Command for SubCommand {
result: Some(Value::test_string("/home/joe/code")), result: Some(Value::test_string("/home/joe/code")),
}, },
Example { Example {
description: "Get dirname of a path in a column", description: "Get dirname of a list of paths",
example: "ls ('.' | path expand) | path dirname -c [ name ]", example: "[ /home/joe/test.txt, /home/doe/test.txt ] | path dirname",
result: None, result: Some(Value::test_list(vec![
Value::test_string("/home/joe"),
Value::test_string("/home/doe"),
])),
}, },
Example { Example {
description: "Walk up two levels", description: "Walk up two levels",

View file

@ -1,25 +1,20 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use nu_engine::{current_dir, CallExt}; use nu_engine::current_dir;
use nu_path::expand_path_with; use nu_path::expand_path_with;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
engine::Command, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, engine::Command, Example, PipelineData, ShellError, Signature, Span, Type, Value,
}; };
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments {
columns: Option<Vec<String>>,
pwd: PathBuf, pwd: PathBuf,
} }
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -30,14 +25,13 @@ impl Command for SubCommand {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path exists") Signature::build("path exists").input_output_types(vec![
.input_output_types(vec![(Type::String, Type::Bool)]) (Type::String, Type::Bool),
.named( (
"columns", Type::List(Box::new(Type::String)),
SyntaxShape::Table(vec![]), Type::List(Box::new(Type::Bool)),
"For a record or table input, check strings at the given columns, and replace with result", ),
Some('c'), ])
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -58,7 +52,6 @@ If you need to distinguish dirs and files, please use `path type`."#
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments {
columns: call.get_flag(engine_state, stack, "columns")?,
pwd: current_dir(engine_state, stack)?, pwd: current_dir(engine_state, stack)?,
}; };
// This doesn't match explicit nulls // This doesn't match explicit nulls
@ -80,9 +73,12 @@ If you need to distinguish dirs and files, please use `path type`."#
result: Some(Value::test_bool(false)), result: Some(Value::test_bool(false)),
}, },
Example { Example {
description: "Check if a file exists in a column", description: "Check if files in list exist",
example: "ls | path exists -c [ name ]", example: r"[ C:\joe\todo.txt, C:\Users\doe\todo.txt ] | path exists",
result: None, result: Some(Value::test_list(vec![
Value::test_bool(false),
Value::test_bool(false),
])),
}, },
] ]
} }
@ -96,9 +92,12 @@ If you need to distinguish dirs and files, please use `path type`."#
result: Some(Value::test_bool(false)), result: Some(Value::test_bool(false)),
}, },
Example { Example {
description: "Check if a file exists in a column", description: "Check if files in list exist",
example: "ls | path exists -c [ name ]", example: "[ /home/joe/todo.txt, /home/doe/todo.txt ] | path exists",
result: None, result: Some(Value::test_list(vec![
Value::test_bool(false),
Value::test_bool(false),
])),
}, },
] ]
} }

View file

@ -1,28 +1,22 @@
use std::path::Path; use std::path::Path;
use nu_engine::env::current_dir_str; use nu_engine::env::current_dir_str;
use nu_engine::CallExt;
use nu_path::{canonicalize_with, expand_path_with}; use nu_path::{canonicalize_with, expand_path_with};
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
engine::Command, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, engine::Command, Example, PipelineData, ShellError, Signature, Span, Type, Value,
}; };
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments {
strict: bool, strict: bool,
columns: Option<Vec<String>>,
cwd: String, cwd: String,
not_follow_symlink: bool, not_follow_symlink: bool,
} }
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -34,19 +28,19 @@ impl Command for SubCommand {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path expand") Signature::build("path expand")
.input_output_types(vec![(Type::String, Type::String)]) .input_output_types(vec![
(Type::String, Type::String),
(
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
])
.switch( .switch(
"strict", "strict",
"Throw an error if the path could not be expanded", "Throw an error if the path could not be expanded",
Some('s'), Some('s'),
) )
.switch("no-symlink", "Do not resolve symbolic links", Some('n')) .switch("no-symlink", "Do not resolve symbolic links", Some('n'))
.named(
"columns",
SyntaxShape::Table(vec![]),
"For a record or table input, expand strings at the given columns",
Some('c'),
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -63,7 +57,6 @@ impl Command for SubCommand {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments {
strict: call.has_flag("strict"), strict: call.has_flag("strict"),
columns: call.get_flag(engine_state, stack, "columns")?,
cwd: current_dir_str(engine_state, stack)?, cwd: current_dir_str(engine_state, stack)?,
not_follow_symlink: call.has_flag("no-symlink"), not_follow_symlink: call.has_flag("no-symlink"),
}; };
@ -85,20 +78,18 @@ impl Command for SubCommand {
example: r"'C:\Users\joe\foo\..\bar' | path expand", example: r"'C:\Users\joe\foo\..\bar' | path expand",
result: Some(Value::test_string(r"C:\Users\joe\bar")), result: Some(Value::test_string(r"C:\Users\joe\bar")),
}, },
Example {
description: "Expand a path in a column",
example: "ls | path expand -c [ name ]",
result: None,
},
Example { Example {
description: "Expand a relative path", description: "Expand a relative path",
example: r"'foo\..\bar' | path expand", example: r"'foo\..\bar' | path expand",
result: None, result: None,
}, },
Example { Example {
description: "Expand an absolute path without following symlink", description: "Expand a list of paths",
example: r"'foo\..\bar' | path expand -n", example: r"[ C:\foo\..\bar, C:\foo\..\baz ] | path expand",
result: None, result: Some(Value::test_list(vec![
Value::test_string(r"C:\bar"),
Value::test_string(r"C:\baz"),
])),
}, },
] ]
} }
@ -111,16 +102,19 @@ impl Command for SubCommand {
example: "'/home/joe/foo/../bar' | path expand", example: "'/home/joe/foo/../bar' | path expand",
result: Some(Value::test_string("/home/joe/bar")), result: Some(Value::test_string("/home/joe/bar")),
}, },
Example {
description: "Expand a path in a column",
example: "ls | path expand -c [ name ]",
result: None,
},
Example { Example {
description: "Expand a relative path", description: "Expand a relative path",
example: "'foo/../bar' | path expand", example: "'foo/../bar' | path expand",
result: None, result: None,
}, },
Example {
description: "Expand a list of paths",
example: "[ /foo/../bar, /foo/../baz ] | path expand",
result: Some(Value::test_list(vec![
Value::test_string("/bar"),
Value::test_string("/baz"),
])),
},
] ]
} }
} }

View file

@ -12,15 +12,10 @@ use nu_protocol::{
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments {
columns: Option<Vec<String>>,
append: Vec<Spanned<String>>, append: Vec<Spanned<String>>,
} }
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -38,13 +33,6 @@ impl Command for SubCommand {
(Type::Record(vec![]), Type::String), (Type::Record(vec![]), Type::String),
(Type::Table(vec![]), Type::List(Box::new(Type::String))), (Type::Table(vec![]), Type::List(Box::new(Type::String))),
]) ])
.named(
"columns",
SyntaxShape::Table(vec![]),
"For a record or table input, join strings at the given columns",
Some('c'),
)
.allow_variants_without_examples(true)
.rest("append", SyntaxShape::String, "Path to append to the input") .rest("append", SyntaxShape::String, "Path to append to the input")
} }
@ -66,7 +54,6 @@ the output of 'path parse' and 'path split' subcommands."#
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments {
columns: call.get_flag(engine_state, stack, "columns")?,
append: call.rest(engine_state, stack, 0)?, append: call.rest(engine_state, stack, 0)?,
}; };
@ -105,11 +92,6 @@ the output of 'path parse' and 'path split' subcommands."#
example: r"'C:\Users\viking' | path join spams this_spam.txt", example: r"'C:\Users\viking' | path join spams this_spam.txt",
result: Some(Value::test_string(r"C:\Users\viking\spams\this_spam.txt")), result: Some(Value::test_string(r"C:\Users\viking\spams\this_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 { Example {
description: "Join a list of parts into a path", description: "Join a list of parts into a path",
example: r"[ 'C:' '\' 'Users' 'viking' 'spam.txt' ] | path join", example: r"[ 'C:' '\' 'Users' 'viking' 'spam.txt' ] | path join",
@ -117,6 +99,11 @@ the output of 'path parse' and 'path split' subcommands."#
}, },
Example { Example {
description: "Join a structured path into a path", description: "Join a structured path into a path",
example: r"{ parent: 'C:\Users\viking', stem: 'spam', extension: 'txt' } | path join",
result: Some(Value::test_string(r"C:\Users\viking\spam.txt")),
},
Example {
description: "Join a table of structured paths into a list of paths",
example: r"[ [parent stem extension]; ['C:\Users\viking' 'spam' 'txt']] | path join", example: r"[ [parent stem extension]; ['C:\Users\viking' 'spam' 'txt']] | path join",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::test_string(r"C:\Users\viking\spam.txt")], vals: vec![Value::test_string(r"C:\Users\viking\spam.txt")],
@ -139,11 +126,6 @@ the output of 'path parse' and 'path split' subcommands."#
example: r"'/home/viking' | path join spams this_spam.txt", example: r"'/home/viking' | path join spams this_spam.txt",
result: Some(Value::test_string(r"/home/viking/spams/this_spam.txt")), result: Some(Value::test_string(r"/home/viking/spams/this_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 { Example {
description: "Join a list of parts into a path", description: "Join a list of parts into a path",
example: r"[ '/' 'home' 'viking' 'spam.txt' ] | path join", example: r"[ '/' 'home' 'viking' 'spam.txt' ] | path join",
@ -151,6 +133,11 @@ the output of 'path parse' and 'path split' subcommands."#
}, },
Example { Example {
description: "Join a structured path into a path", description: "Join a structured path into a path",
example: r"{ parent: '/home/viking', stem: 'spam', extension: 'txt' } | path join",
result: Some(Value::test_string(r"/home/viking/spam.txt")),
},
Example {
description: "Join a table of structured paths into a list of paths",
example: r"[[ parent stem extension ]; [ '/home/viking' 'spam' 'txt' ]] | path join", example: r"[[ parent stem extension ]; [ '/home/viking' 'spam' 'txt' ]] | path join",
result: Some(Value::List { result: Some(Value::List {
vals: vec![Value::test_string(r"/home/viking/spam.txt")], vals: vec![Value::test_string(r"/home/viking/spam.txt")],
@ -209,25 +196,12 @@ fn join_list(parts: &[Value], head: Span, span: Span, args: &Arguments) -> Value
} }
fn join_record(cols: &[String], vals: &[Value], head: Span, span: Span, args: &Arguments) -> Value { fn join_record(cols: &[String], vals: &[Value], head: Span, span: Span, args: &Arguments) -> Value {
if args.columns.is_some() {
super::operate(
&join_single,
args,
Value::Record {
cols: cols.to_vec(),
vals: vals.to_vec(),
span,
},
span,
)
} else {
match merge_record(cols, vals, head, span) { match merge_record(cols, vals, head, span) {
Ok(p) => join_single(p.as_path(), head, args), Ok(p) => join_single(p.as_path(), head, args),
Err(error) => Value::Error { Err(error) => Value::Error {
error: Box::new(error), error: Box::new(error),
}, },
} }
}
} }
fn merge_record( fn merge_record(

View file

@ -29,9 +29,7 @@ const ALLOWED_COLUMNS: [&str; 4] = ["prefix", "parent", "stem", "extension"];
#[cfg(not(windows))] #[cfg(not(windows))]
const ALLOWED_COLUMNS: [&str; 3] = ["parent", "stem", "extension"]; const ALLOWED_COLUMNS: [&str; 3] = ["parent", "stem", "extension"];
trait PathSubcommandArguments { trait PathSubcommandArguments {}
fn get_columns(&self) -> Option<Vec<String>>;
}
fn operate<F, A>(cmd: &F, args: &A, v: Value, name: Span) -> Value fn operate<F, A>(cmd: &F, args: &A, v: Value, name: Span) -> Value
where where
@ -40,45 +38,6 @@ where
{ {
match v { match v {
Value::String { val, span } => cmd(StdPath::new(&val), span, args), Value::String { val, span } => cmd(StdPath::new(&val), span, args),
Value::Record { cols, vals, span } => {
let col = if let Some(col) = args.get_columns() {
col
} else {
vec![]
};
if col.is_empty() {
return Value::Error {
error: Box::new(ShellError::UnsupportedInput(
String::from("when the input is a table, you must specify the columns"),
"value originates from here".into(),
name,
span,
)),
};
}
let mut output_cols = vec![];
let mut output_vals = vec![];
for (k, v) in cols.iter().zip(vals) {
output_cols.push(k.clone());
if col.contains(k) {
let new_val = match v {
Value::String { val, span } => cmd(StdPath::new(&val), span, args),
_ => return handle_invalid_values(v, name),
};
output_vals.push(new_val);
} else {
output_vals.push(v);
}
}
Value::Record {
cols: output_cols,
vals: output_vals,
span,
}
}
_ => handle_invalid_values(v, name), _ => handle_invalid_values(v, name),
} }
} }

View file

@ -12,15 +12,10 @@ use nu_protocol::{
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments {
columns: Option<Vec<String>>,
extension: Option<Spanned<String>>, extension: Option<Spanned<String>>,
} }
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -32,13 +27,10 @@ impl Command for SubCommand {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path parse") Signature::build("path parse")
.input_output_types(vec![(Type::String, Type::Record(vec![]))]) .input_output_types(vec![
.named( (Type::String, Type::Record(vec![])),
"columns", (Type::List(Box::new(Type::String)), Type::Table(vec![])),
SyntaxShape::Table(vec![]), ])
"For a record or table input, convert strings at the given columns",
Some('c'),
)
.named( .named(
"extension", "extension",
SyntaxShape::String, SyntaxShape::String,
@ -65,7 +57,6 @@ On Windows, an extra 'prefix' column is added."#
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments {
columns: call.get_flag(engine_state, stack, "columns")?,
extension: call.get_flag(engine_state, stack, "extension")?, extension: call.get_flag(engine_state, stack, "extension")?,
}; };
@ -126,9 +117,40 @@ On Windows, an extra 'prefix' column is added."#
}), }),
}, },
Example { Example {
description: "Parse all paths under the 'name' column", description: "Parse all paths in a list",
example: r"ls | path parse -c [ name ]", example: r"[ C:\Users\viking.d C:\Users\spam.txt ] | path parse",
result: None, result: Some(Value::test_list(vec![
Value::Record {
cols: vec![
"prefix".into(),
"parent".into(),
"stem".into(),
"extension".into(),
],
vals: vec![
Value::test_string("C:"),
Value::test_string(r"C:\Users"),
Value::test_string("viking"),
Value::test_string("d"),
],
span: Span::test_data(),
},
Value::Record {
cols: vec![
"prefix".into(),
"parent".into(),
"stem".into(),
"extension".into(),
],
vals: vec![
Value::test_string("C:"),
Value::test_string(r"C:\Users"),
Value::test_string("spam"),
Value::test_string("txt"),
],
span: Span::test_data(),
},
])),
}, },
] ]
} }
@ -168,9 +190,28 @@ On Windows, an extra 'prefix' column is added."#
}), }),
}, },
Example { Example {
description: "Parse all paths under the 'name' column", description: "Parse all paths in a list",
example: r"ls | path parse -c [ name ]", example: r"[ /home/viking.d /home/spam.txt ] | path parse",
result: None, result: Some(Value::test_list(vec![
Value::Record {
cols: vec!["parent".into(), "stem".into(), "extension".into()],
vals: vec![
Value::test_string("/home"),
Value::test_string("viking"),
Value::test_string("d"),
],
span: Span::test_data(),
},
Value::Record {
cols: vec!["parent".into(), "stem".into(), "extension".into()],
vals: vec![
Value::test_string("/home"),
Value::test_string("spam"),
Value::test_string("txt"),
],
span: Span::test_data(),
},
])),
}, },
] ]
} }

View file

@ -13,14 +13,9 @@ use super::PathSubcommandArguments;
struct Arguments { struct Arguments {
path: Spanned<String>, path: Spanned<String>,
columns: Option<Vec<String>>,
} }
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -32,18 +27,18 @@ impl Command for SubCommand {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path relative-to") Signature::build("path relative-to")
.input_output_types(vec![(Type::String, Type::String)]) .input_output_types(vec![
(Type::String, Type::String),
(
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
])
.required( .required(
"path", "path",
SyntaxShape::String, SyntaxShape::String,
"Parent shared with the input path", "Parent shared with the input path",
) )
.named(
"columns",
SyntaxShape::Table(vec![]),
"For a record or table input, convert strings at the given columns",
Some('c'),
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -66,7 +61,6 @@ path."#
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments {
path: call.req(engine_state, stack, 0)?, path: call.req(engine_state, stack, 0)?,
columns: call.get_flag(engine_state, stack, "columns")?,
}; };
// This doesn't match explicit nulls // This doesn't match explicit nulls
@ -88,9 +82,12 @@ path."#
result: Some(Value::test_string(r"viking")), result: Some(Value::test_string(r"viking")),
}, },
Example { Example {
description: "Find a relative path from two absolute paths in a column", description: "Find a relative path from absolute paths in list",
example: "ls ~ | path relative-to ~ -c [ name ]", example: r"[ C:\Users\viking, C:\Users\spam ] | path relative-to C:\Users",
result: None, result: Some(Value::test_list(vec![
Value::test_string("viking"),
Value::test_string("spam"),
])),
}, },
Example { Example {
description: "Find a relative path from two relative paths", description: "Find a relative path from two relative paths",
@ -109,9 +106,12 @@ path."#
result: Some(Value::test_string(r"viking")), result: Some(Value::test_string(r"viking")),
}, },
Example { Example {
description: "Find a relative path from two absolute paths in a column", description: "Find a relative path from absolute paths in list",
example: "ls ~ | path relative-to ~ -c [ name ]", example: r"[ /home/viking, /home/spam ] | path relative-to '/home'",
result: None, result: Some(Value::test_list(vec![
Value::test_string("viking"),
Value::test_string("spam"),
])),
}, },
Example { Example {
description: "Find a relative path from two relative paths", description: "Find a relative path from two relative paths",

View file

@ -1,23 +1,16 @@
use std::path::{Component, Path}; use std::path::{Component, Path};
use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
engine::Command, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, engine::Command, Example, PipelineData, ShellError, Signature, Span, Type, Value,
}; };
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments;
columns: Option<Vec<String>>,
}
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -28,14 +21,13 @@ impl Command for SubCommand {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path split") Signature::build("path split").input_output_types(vec![
.input_output_types(vec![(Type::String, Type::List(Box::new(Type::String)))]) (Type::String, Type::List(Box::new(Type::String))),
.named( (
"columns", Type::List(Box::new(Type::String)),
SyntaxShape::Table(vec![]), Type::List(Box::new(Type::List(Box::new(Type::String)))),
"For a record or table input, split strings at the given columns", ),
Some('c'), ])
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -45,14 +37,12 @@ impl Command for SubCommand {
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments;
columns: call.get_flag(engine_state, stack, "columns")?,
};
// This doesn't match explicit nulls // This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) { if matches!(input, PipelineData::Empty) {
@ -81,9 +71,25 @@ impl Command for SubCommand {
}), }),
}, },
Example { Example {
description: "Split all paths under the 'name' column", description: "Split paths in list into parts",
example: r"ls ('.' | path expand) | path split -c [ name ]", example: r"[ C:\Users\viking\spam.txt C:\Users\viking\eggs.txt ] | path split",
result: None, result: Some(Value::List {
vals: vec![
Value::test_list(vec![
Value::test_string(r"C:\"),
Value::test_string("Users"),
Value::test_string("viking"),
Value::test_string("spam.txt"),
]),
Value::test_list(vec![
Value::test_string(r"C:\"),
Value::test_string("Users"),
Value::test_string("viking"),
Value::test_string("eggs.txt"),
]),
],
span: Span::test_data(),
}),
}, },
] ]
} }
@ -105,9 +111,25 @@ impl Command for SubCommand {
}), }),
}, },
Example { Example {
description: "Split all paths under the 'name' column", description: "Split paths in list into parts",
example: r"ls ('.' | path expand) | path split -c [ name ]", example: r"[ /home/viking/spam.txt /home/viking/eggs.txt ] | path split",
result: None, result: Some(Value::List {
vals: vec![
Value::test_list(vec![
Value::test_string("/"),
Value::test_string("home"),
Value::test_string("viking"),
Value::test_string("spam.txt"),
]),
Value::test_list(vec![
Value::test_string("/"),
Value::test_string("home"),
Value::test_string("viking"),
Value::test_string("eggs.txt"),
]),
],
span: Span::test_data(),
}),
}, },
] ]
} }

View file

@ -1,23 +1,16 @@
use std::path::Path; use std::path::Path;
use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::engine::{EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
engine::Command, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, engine::Command, Example, PipelineData, ShellError, Signature, Span, Type, Value,
}; };
use super::PathSubcommandArguments; use super::PathSubcommandArguments;
struct Arguments { struct Arguments;
columns: Option<Vec<String>>,
}
impl PathSubcommandArguments for Arguments { impl PathSubcommandArguments for Arguments {}
fn get_columns(&self) -> Option<Vec<String>> {
self.columns.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -29,13 +22,14 @@ impl Command for SubCommand {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("path type") Signature::build("path type")
.input_output_types(vec![(Type::String, Type::String)]) .input_output_types(vec![
.named( (Type::String, Type::String),
"columns", (
SyntaxShape::Table(vec![]), Type::List(Box::new(Type::String)),
"For a record or table input, check strings at the given columns, and replace with result", Type::List(Box::new(Type::String)),
Some('c'), ),
) ])
.allow_variants_without_examples(true)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -50,14 +44,12 @@ If nothing is found, an empty string will be returned."#
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let args = Arguments { let args = Arguments;
columns: call.get_flag(engine_state, stack, "columns")?,
};
// This doesn't match explicit nulls // This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) { if matches!(input, PipelineData::Empty) {
@ -77,8 +69,8 @@ If nothing is found, an empty string will be returned."#
result: Some(Value::test_string("dir")), result: Some(Value::test_string("dir")),
}, },
Example { Example {
description: "Show type of a filepath in a column", description: "Show type of a filepaths in a list",
example: "ls | path type -c [ name ]", example: "ls | get name | path type",
result: None, result: None,
}, },
] ]

View file

@ -2,21 +2,6 @@ use nu_test_support::{nu, pipeline};
use super::join_path_sep; use super::join_path_sep;
#[test]
fn returns_path_joined_with_column_path() {
let actual = nu!(
cwd: "tests", pipeline(
r#"
echo [ [name]; [eggs] ]
| path join spam.txt -c [ name ]
| get name.0
"#
));
let expected = join_path_sep(&["eggs", "spam.txt"]);
assert_eq!(actual.out, expected);
}
#[test] #[test]
fn returns_path_joined_from_list() { fn returns_path_joined_from_list() {
let actual = nu!( let actual = nu!(

View file

@ -99,21 +99,6 @@ fn parses_ignoring_extension_gets_stem() {
assert_eq!(actual.out, "spam.tar.gz"); assert_eq!(actual.out, "spam.tar.gz");
} }
#[test]
fn parses_column_path_extension() {
let actual = nu!(
cwd: "tests", pipeline(
r#"
echo [[home, barn]; ['home/viking/spam.txt', 'barn/cow/moo.png']]
| path parse -c [ home barn ]
| get barn
| get extension.0
"#
));
assert_eq!(actual.out, "png");
}
#[test] #[test]
fn parses_into_correct_number_of_columns() { fn parses_into_correct_number_of_columns() {
let actual = nu!( let actual = nu!(

View file

@ -25,24 +25,3 @@ fn splits_correctly_single_path() {
assert_eq!(actual.out, "spam.txt"); assert_eq!(actual.out, "spam.txt");
} }
#[test]
fn splits_correctly_with_column_path() {
let actual = nu!(
cwd: "tests", pipeline(
r#"
echo [
[home, barn];
['home/viking/spam.txt', 'barn/cow/moo.png']
['home/viking/eggs.txt', 'barn/goat/cheese.png']
]
| path split -c [ home barn ]
| get barn
| flatten
| length
"#
));
assert_eq!(actual.out, "6");
}

View file

@ -1740,6 +1740,15 @@ impl Value {
} }
} }
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_list(vals: Vec<Value>) -> Value {
Value::List {
vals,
span: Span::test_data(),
}
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source /// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors. /// when used in errors.
pub fn test_date(val: DateTime<FixedOffset>) -> Value { pub fn test_date(val: DateTime<FixedOffset>) -> Value {