From c59d6d31bcb8828b56769de6dc9e13b103942ba8 Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Sun, 21 Jan 2024 23:22:25 +0800 Subject: [PATCH] do not attempt to glob expand if the file path is wrapped in quotes (#11569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes: #11455 ### For arguments which is annotated with `:path/:directory/:glob` To fix the issue, we need to have a way to know if a path is originally quoted during runtime. So the information needed to be added at several levels: * parse time (from user input to expression) We need to add quoted information into `Expr::Filepath`, `Expr::Directory`, `Expr::GlobPattern` * eval time When convert from `Expr::Filepath`, `Expr::Directory`, `Expr::GlobPattern` to `Value::String` during runtime, we won't auto expanded the path if it's quoted ### For `ls` It's really special, because it accepts a `String` as a pattern, and it generates `glob` expression inside the command itself. So the idea behind the change is introducing a special SyntaxShape to ls: `SyntaxShape::LsGlobPattern`. So we can track if the pattern is originally quoted easier, and we don't auto expand the path either. Then when constructing a glob pattern inside ls, we check if input pattern is quoted, if so: we escape the input pattern, so we can run `ls a[123]b`, because it's already escaped. Finally, to accomplish the checking process, we also need to introduce a new value type called `Value::QuotedString` to differ from `Value::String`, it's used to generate an enum called `NuPath`, which is finally used in `ls` function. `ls` learned from `NuPath` to know if user input is quoted. # User-Facing Changes Actually it contains several changes ### For arguments which is annotated with `:path/:directory/:glob` #### Before ```nushell > def foo [p: path] { echo $p }; print (foo "~/a"); print (foo '~/a') /home/windsoilder/a /home/windsoilder/a > def foo [p: directory] { echo $p }; print (foo "~/a"); print (foo '~/a') /home/windsoilder/a /home/windsoilder/a > def foo [p: glob] { echo $p }; print (foo "~/a"); print (foo '~/a') /home/windsoilder/a /home/windsoilder/a ``` #### After ```nushell > def foo [p: path] { echo $p }; print (foo "~/a"); print (foo '~/a') ~/a ~/a > def foo [p: directory] { echo $p }; print (foo "~/a"); print (foo '~/a') ~/a ~/a > def foo [p: glob] { echo $p }; print (foo "~/a"); print (foo '~/a') ~/a ~/a ``` ### For ls command `touch '[uwu]'` #### Before ``` ❯ ls -D "[uwu]" Error: × No matches found for [uwu] ╭─[entry #6:1:1] 1 │ ls -D "[uwu]" · ───┬─── · ╰── Pattern, file or folder not found ╰──── help: no matches found ``` #### After ``` ❯ ls -D "[uwu]" ╭───┬───────┬──────┬──────┬──────────╮ │ # │ name │ type │ size │ modified │ ├───┼───────┼──────┼──────┼──────────┤ │ 0 │ [uwu] │ file │ 0 B │ now │ ╰───┴───────┴──────┴──────┴──────────╯ ``` # Tests + Formatting Done # After Submitting NaN --- crates/nu-cli/src/syntax_highlight.rs | 7 +- .../nu-cmd-lang/src/core_commands/describe.rs | 1 + crates/nu-cmd-lang/src/example_support.rs | 2 +- crates/nu-color-config/src/style_computer.rs | 1 + crates/nu-command/src/debug/explain.rs | 1 + crates/nu-command/src/filesystem/ls.rs | 70 ++++++++++++++----- crates/nu-command/src/filters/find.rs | 1 + crates/nu-command/src/formats/from/nuon.rs | 7 +- crates/nu-command/src/formats/to/json.rs | 1 + crates/nu-command/src/formats/to/nuon.rs | 1 + crates/nu-command/src/formats/to/text.rs | 1 + crates/nu-command/src/formats/to/toml.rs | 4 +- crates/nu-command/src/formats/to/yaml.rs | 4 +- crates/nu-command/tests/commands/ls.rs | 2 + crates/nu-engine/src/eval.rs | 31 +++++--- crates/nu-parser/src/flatten.rs | 9 ++- crates/nu-parser/src/parser.rs | 47 +++++++++++-- crates/nu-protocol/src/ast/expr.rs | 7 +- crates/nu-protocol/src/ast/expression.rs | 14 ++-- crates/nu-protocol/src/eval_base.rs | 20 ++++-- crates/nu-protocol/src/eval_const.rs | 3 + crates/nu-protocol/src/syntax_shape.rs | 5 ++ crates/nu-protocol/src/value/from_value.rs | 40 +++++++++++ crates/nu-protocol/src/value/mod.rs | 62 ++++++++++++++++ crates/nu-protocol/src/value/path.rs | 19 +++++ src/tests/test_custom_commands.rs | 10 +++ 26 files changed, 310 insertions(+), 60 deletions(-) create mode 100644 crates/nu-protocol/src/value/path.rs diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index c3e88190a8..81e4d2a093 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -321,9 +321,10 @@ fn find_matching_block_end_in_expr( Expr::Keyword(..) => None, Expr::ValueWithUnit(..) => None, Expr::DateTime(_) => None, - Expr::Filepath(_) => None, - Expr::Directory(_) => None, - Expr::GlobPattern(_) => None, + Expr::Filepath(_, _) => None, + Expr::Directory(_, _) => None, + Expr::GlobPattern(_, _) => None, + Expr::LsGlobPattern(_, _) => None, Expr::String(_) => None, Expr::CellPath(_) => None, Expr::ImportPattern(_) => None, diff --git a/crates/nu-cmd-lang/src/core_commands/describe.rs b/crates/nu-cmd-lang/src/core_commands/describe.rs index bd78d31671..cd52084f49 100644 --- a/crates/nu-cmd-lang/src/core_commands/describe.rs +++ b/crates/nu-cmd-lang/src/core_commands/describe.rs @@ -305,6 +305,7 @@ fn describe_value( | Value::Date { .. } | Value::Range { .. } | Value::String { .. } + | Value::QuotedString { .. } | Value::Nothing { .. } => Value::record( record!( "type" => Value::string(value.get_type().to_string(), head), diff --git a/crates/nu-cmd-lang/src/example_support.rs b/crates/nu-cmd-lang/src/example_support.rs index 44d3ae445a..9dd73ff7d4 100644 --- a/crates/nu-cmd-lang/src/example_support.rs +++ b/crates/nu-cmd-lang/src/example_support.rs @@ -228,7 +228,7 @@ impl<'a> std::fmt::Debug for DebuggableValue<'a> { val.from, val.to, val.incr ), }, - Value::String { val, .. } => { + Value::String { val, .. } | Value::QuotedString { val, .. } => { write!(f, "{:?}", val) } Value::Record { val, .. } => { diff --git a/crates/nu-color-config/src/style_computer.rs b/crates/nu-color-config/src/style_computer.rs index 2dd79d8367..97bf2af115 100644 --- a/crates/nu-color-config/src/style_computer.rs +++ b/crates/nu-color-config/src/style_computer.rs @@ -122,6 +122,7 @@ impl<'a> StyleComputer<'a> { Value::Range { .. } => TextStyle::with_style(Left, s), Value::Float { .. } => TextStyle::with_style(Right, s), Value::String { .. } => TextStyle::with_style(Left, s), + Value::QuotedString { .. } => TextStyle::with_style(Left, s), Value::Nothing { .. } => TextStyle::with_style(Left, s), Value::Binary { .. } => TextStyle::with_style(Left, s), Value::CellPath { .. } => TextStyle::with_style(Left, s), diff --git a/crates/nu-command/src/debug/explain.rs b/crates/nu-command/src/debug/explain.rs index 3d1018f3d7..c52758a8ed 100644 --- a/crates/nu-command/src/debug/explain.rs +++ b/crates/nu-command/src/debug/explain.rs @@ -251,6 +251,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String { ) } Value::String { val, .. } => val.clone(), + Value::QuotedString { val, .. } => val.clone(), Value::List { vals: val, .. } => format!( "[{}]", val.iter() diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 033497d96d..89dca8d267 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -3,10 +3,11 @@ use crate::DirInfo; use chrono::{DateTime, Local, LocalResult, TimeZone, Utc}; use nu_engine::env::current_dir; use nu_engine::CallExt; -use nu_glob::MatchOptions; +use nu_glob::{MatchOptions, Pattern}; use nu_path::expand_to_real_path; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::NuPath; use nu_protocol::{ Category, DataSource, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, PipelineMetadata, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, @@ -38,8 +39,9 @@ impl Command for Ls { fn signature(&self) -> nu_protocol::Signature { Signature::build("ls") .input_output_types(vec![(Type::Nothing, Type::Table(vec![]))]) - // Using a string instead of a glob pattern shape so it won't auto-expand - .optional("pattern", SyntaxShape::String, "The glob pattern to use.") + // LsGlobPattern is similar to string, it won't auto-expand + // and we use it to track if the user input is quoted. + .optional("pattern", SyntaxShape::LsGlobPattern, "The glob pattern to use.") .switch("all", "Show hidden files", Some('a')) .switch( "long", @@ -84,23 +86,32 @@ impl Command for Ls { let call_span = call.head; let cwd = current_dir(engine_state, stack)?; - let pattern_arg: Option> = call.opt(engine_state, stack, 0)?; + let pattern_arg: Option> = call.opt(engine_state, stack, 0)?; let pattern_arg = { if let Some(path) = pattern_arg { - Some(Spanned { - item: nu_utils::strip_ansi_string_unlikely(path.item), - span: path.span, - }) + match path.item { + NuPath::Quoted(p) => Some(Spanned { + item: NuPath::Quoted(nu_utils::strip_ansi_string_unlikely(p)), + span: path.span, + }), + NuPath::UnQuoted(p) => Some(Spanned { + item: NuPath::UnQuoted(nu_utils::strip_ansi_string_unlikely(p)), + span: path.span, + }), + } } else { pattern_arg } }; - let (path, p_tag, absolute_path) = match pattern_arg { - Some(p) => { - let p_tag = p.span; - let mut p = expand_to_real_path(p.item); + // it indicates we need to append an extra '*' after pattern for listing given directory + // Example: 'ls directory' -> 'ls directory/*' + let mut extra_star_under_given_directory = false; + let (path, p_tag, absolute_path, quoted) = match pattern_arg { + Some(pat) => { + let p_tag = pat.span; + let p = expand_to_real_path(pat.item.as_ref()); let expanded = nu_path::expand_path_with(&p, &cwd); // Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true @@ -131,27 +142,50 @@ impl Command for Ls { if is_empty_dir(&expanded) { return Ok(Value::list(vec![], call_span).into_pipeline_data()); } - p.push("*"); + extra_star_under_given_directory = true; } let absolute_path = p.is_absolute(); - (p, p_tag, absolute_path) + ( + p, + p_tag, + absolute_path, + matches!(pat.item, NuPath::Quoted(_)), + ) } None => { // Avoid pushing "*" to the default path when directory (do not show contents) flag is true if directory { - (PathBuf::from("."), call_span, false) + (PathBuf::from("."), call_span, false, false) } else if is_empty_dir(current_dir(engine_state, stack)?) { return Ok(Value::list(vec![], call_span).into_pipeline_data()); } else { - (PathBuf::from("*"), call_span, false) + (PathBuf::from("*"), call_span, false, false) } } }; let hidden_dir_specified = is_hidden_dir(&path); + // when it's quoted, we need to escape our glob pattern + // so we can do ls for a file or directory like `a[123]b` + let path = if quoted { + let p = path.display().to_string(); + let mut glob_escaped = Pattern::escape(&p); + if extra_star_under_given_directory { + glob_escaped.push(std::path::MAIN_SEPARATOR); + glob_escaped.push('*'); + } + glob_escaped + } else { + let mut p = path.display().to_string(); + if extra_star_under_given_directory { + p.push(std::path::MAIN_SEPARATOR); + p.push('*'); + } + p + }; let glob_path = Spanned { - item: path.display().to_string(), + item: path.clone(), span: p_tag, }; @@ -169,7 +203,7 @@ impl Command for Ls { let mut paths_peek = paths.peekable(); if paths_peek.peek().is_none() { return Err(ShellError::GenericError { - error: format!("No matches found for {}", &path.display().to_string()), + error: format!("No matches found for {}", &path), msg: "Pattern, file or folder not found".into(), span: Some(p_tag), help: Some("no matches found".into()), diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index 7fdd936508..709a98a7ee 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -495,6 +495,7 @@ fn value_should_be_printed( | Value::Nothing { .. } | Value::Error { .. } => term_equals_value(term, &lower_value, span), Value::String { .. } + | Value::QuotedString { .. } | Value::List { .. } | Value::CellPath { .. } | Value::CustomValue { .. } => term_contains_value(term, &lower_value, span), diff --git a/crates/nu-command/src/formats/from/nuon.rs b/crates/nu-command/src/formats/from/nuon.rs index c56fb5eabd..014cd2419c 100644 --- a/crates/nu-command/src/formats/from/nuon.rs +++ b/crates/nu-command/src/formats/from/nuon.rs @@ -220,8 +220,8 @@ fn convert_to_value( msg: "calls not supported in nuon".into(), span: expr.span, }), - Expr::Filepath(val) => Ok(Value::string(val, span)), - Expr::Directory(val) => Ok(Value::string(val, span)), + Expr::Filepath(val, _) => Ok(Value::string(val, span)), + Expr::Directory(val, _) => Ok(Value::string(val, span)), Expr::Float(val) => Ok(Value::float(val, span)), Expr::FullCellPath(full_cell_path) => { if !full_cell_path.tail.is_empty() { @@ -242,7 +242,8 @@ fn convert_to_value( msg: "extra tokens in input file".into(), span: expr.span, }), - Expr::GlobPattern(val) => Ok(Value::string(val, span)), + Expr::GlobPattern(val, _) => Ok(Value::string(val, span)), + Expr::LsGlobPattern(val, _) => Ok(Value::string(val, span)), Expr::ImportPattern(..) => Err(ShellError::OutsideSpannedLabeledError { src: original_text.to_string(), error: "Error when loading".into(), diff --git a/crates/nu-command/src/formats/to/json.rs b/crates/nu-command/src/formats/to/json.rs index 6e561230e0..7f5eb748ed 100644 --- a/crates/nu-command/src/formats/to/json.rs +++ b/crates/nu-command/src/formats/to/json.rs @@ -115,6 +115,7 @@ pub fn value_to_json_value(v: &Value) -> Result { Value::Int { val, .. } => nu_json::Value::I64(*val), Value::Nothing { .. } => nu_json::Value::Null, Value::String { val, .. } => nu_json::Value::String(val.to_string()), + Value::QuotedString { val, .. } => nu_json::Value::String(val.to_string()), Value::CellPath { val, .. } => nu_json::Value::Array( val.members .iter() diff --git a/crates/nu-command/src/formats/to/nuon.rs b/crates/nu-command/src/formats/to/nuon.rs index fb779bbe3e..02754b15fd 100644 --- a/crates/nu-command/src/formats/to/nuon.rs +++ b/crates/nu-command/src/formats/to/nuon.rs @@ -279,6 +279,7 @@ pub fn value_to_string( // All strings outside data structures are quoted because they are in 'command position' // (could be mistaken for commands by the Nu parser) Value::String { val, .. } => Ok(escape_quote_string(val)), + Value::QuotedString { val, .. } => Ok(escape_quote_string(val)), } } diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 43767cbae6..1c906b4e3d 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -127,6 +127,7 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String { ) } Value::String { val, .. } => val, + Value::QuotedString { val, .. } => val, Value::List { vals: val, .. } => val .into_iter() .map(|x| local_into_string(x, ", ", config)) diff --git a/crates/nu-command/src/formats/to/toml.rs b/crates/nu-command/src/formats/to/toml.rs index 0274695a65..d12571719f 100644 --- a/crates/nu-command/src/formats/to/toml.rs +++ b/crates/nu-command/src/formats/to/toml.rs @@ -54,7 +54,9 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result toml::Value::String(val.to_string()), Value::Range { .. } => toml::Value::String("".to_string()), Value::Float { val, .. } => toml::Value::Float(*val), - Value::String { val, .. } => toml::Value::String(val.clone()), + Value::String { val, .. } | Value::QuotedString { val, .. } => { + toml::Value::String(val.clone()) + } Value::Record { val, .. } => { let mut m = toml::map::Map::new(); for (k, v) in val { diff --git a/crates/nu-command/src/formats/to/yaml.rs b/crates/nu-command/src/formats/to/yaml.rs index f1c0c56d96..62303b2660 100644 --- a/crates/nu-command/src/formats/to/yaml.rs +++ b/crates/nu-command/src/formats/to/yaml.rs @@ -52,7 +52,9 @@ pub fn value_to_yaml_value(v: &Value) -> Result { Value::Date { val, .. } => serde_yaml::Value::String(val.to_string()), Value::Range { .. } => serde_yaml::Value::Null, Value::Float { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)), - Value::String { val, .. } => serde_yaml::Value::String(val.clone()), + Value::String { val, .. } | Value::QuotedString { val, .. } => { + serde_yaml::Value::String(val.clone()) + } Value::Record { val, .. } => { let mut m = serde_yaml::Mapping::new(); for (k, v) in val { diff --git a/crates/nu-command/tests/commands/ls.rs b/crates/nu-command/tests/commands/ls.rs index 02588dccac..eb5c9e70d8 100644 --- a/crates/nu-command/tests/commands/ls.rs +++ b/crates/nu-command/tests/commands/ls.rs @@ -115,6 +115,8 @@ fn lists_regular_files_in_special_folder() { #[case("[[]?bcd].txt", 2)] #[case("[[]abcd].txt", 1)] #[case("[[][abcd]bcd[]].txt", 2)] +#[case("'[abcd].txt'", 1)] +#[case("'[bbcd].txt'", 1)] fn lists_regular_files_using_question_mark(#[case] command: &str, #[case] expected: usize) { Playground::setup("ls_test_3", |dirs, sandbox| { sandbox.mkdir("abcd").mkdir("bbcd").with_files(vec![ diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 89fe9a7a91..71ce05ad3d 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -387,7 +387,7 @@ fn eval_element_with_input( Expr::String(_) | Expr::FullCellPath(_) | Expr::StringInterpolation(_) - | Expr::Filepath(_) => { + | Expr::Filepath(_, _) => { let exit_code = match &mut input { PipelineData::ExternalStream { exit_code, .. } => exit_code.take(), _ => None, @@ -485,11 +485,11 @@ fn eval_element_with_input( Expr::String(_) | Expr::FullCellPath(_) | Expr::StringInterpolation(_) - | Expr::Filepath(_), + | Expr::Filepath(_, _), Expr::String(_) | Expr::FullCellPath(_) | Expr::StringInterpolation(_) - | Expr::Filepath(_), + | Expr::Filepath(_, _), ) => { if let Some(save_command) = engine_state.find_decl(b"save", &[]) { let exit_code = match &mut input { @@ -917,22 +917,30 @@ impl Eval for EvalRuntime { engine_state: Self::State<'_>, stack: &mut Self::MutState, path: String, + quoted: bool, span: Span, ) -> Result { - let cwd = current_dir_str(engine_state, stack)?; - let path = expand_path_with(path, cwd); + if quoted { + Ok(Value::string(path, span)) + } else { + let cwd = current_dir_str(engine_state, stack)?; + let path = expand_path_with(path, cwd); - Ok(Value::string(path.to_string_lossy(), span)) + Ok(Value::string(path.to_string_lossy(), span)) + } } fn eval_directory( engine_state: Self::State<'_>, stack: &mut Self::MutState, path: String, + quoted: bool, span: Span, ) -> Result { if path == "-" { Ok(Value::string("-", span)) + } else if quoted { + Ok(Value::string(path, span)) } else { let cwd = current_dir_str(engine_state, stack)?; let path = expand_path_with(path, cwd); @@ -1163,12 +1171,17 @@ impl Eval for EvalRuntime { engine_state: Self::State<'_>, stack: &mut Self::MutState, pattern: String, + quoted: bool, span: Span, ) -> Result { - let cwd = current_dir_str(engine_state, stack)?; - let path = expand_path_with(pattern, cwd); + if quoted { + Ok(Value::string(pattern, span)) + } else { + let cwd = current_dir_str(engine_state, stack)?; + let path = expand_path_with(pattern, cwd); - Ok(Value::string(path.to_string_lossy(), span)) + Ok(Value::string(path.to_string_lossy(), span)) + } } fn unreachable(expr: &Expression) -> Result { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index dc37822a09..0d585a982a 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -361,13 +361,16 @@ pub fn flatten_expression( Expr::Bool(_) => { vec![(expr.span, FlatShape::Bool)] } - Expr::Filepath(_) => { + Expr::Filepath(_, _) => { vec![(expr.span, FlatShape::Filepath)] } - Expr::Directory(_) => { + Expr::Directory(_, _) => { vec![(expr.span, FlatShape::Directory)] } - Expr::GlobPattern(_) => { + Expr::GlobPattern(_, _) => { + vec![(expr.span, FlatShape::GlobPattern)] + } + Expr::LsGlobPattern(_, _) => { vec![(expr.span, FlatShape::GlobPattern)] } Expr::List(list) => { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index c3795e5adf..6e1ab7fc6f 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2179,6 +2179,7 @@ pub fn parse_full_cell_path( pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Expression { let bytes = working_set.get_span_contents(span); + let quoted = is_quoted(bytes); let (token, err) = unescape_unquote_string(bytes, span); trace!("parsing: directory"); @@ -2186,7 +2187,7 @@ pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Express trace!("-- found {}", token); Expression { - expr: Expr::Directory(token), + expr: Expr::Directory(token, quoted), span, ty: Type::String, custom_completion: None, @@ -2200,6 +2201,7 @@ pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Express pub fn parse_filepath(working_set: &mut StateWorkingSet, span: Span) -> Expression { let bytes = working_set.get_span_contents(span); + let quoted = is_quoted(bytes); let (token, err) = unescape_unquote_string(bytes, span); trace!("parsing: filepath"); @@ -2207,7 +2209,7 @@ pub fn parse_filepath(working_set: &mut StateWorkingSet, span: Span) -> Expressi trace!("-- found {}", token); Expression { - expr: Expr::Filepath(token), + expr: Expr::Filepath(token, quoted), span, ty: Type::String, custom_completion: None, @@ -2467,6 +2469,7 @@ fn modf(x: f64) -> (f64, f64) { pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression { let bytes = working_set.get_span_contents(span); + let quoted = is_quoted(bytes); let (token, err) = unescape_unquote_string(bytes, span); trace!("parsing: glob pattern"); @@ -2474,7 +2477,29 @@ pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expr trace!("-- found {}", token); Expression { - expr: Expr::GlobPattern(token), + expr: Expr::GlobPattern(token, quoted), + span, + ty: Type::String, + custom_completion: None, + } + } else { + working_set.error(ParseError::Expected("glob pattern string", span)); + + garbage(span) + } +} + +pub fn parse_ls_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression { + let bytes = working_set.get_span_contents(span); + let quoted = is_quoted(bytes); + let (token, err) = unescape_unquote_string(bytes, span); + trace!("parsing: glob pattern"); + + if err.is_none() { + trace!("-- found {}", token); + + Expression { + expr: Expr::LsGlobPattern(token, quoted), span, ty: Type::String, custom_completion: None, @@ -2709,6 +2734,11 @@ pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression } } +fn is_quoted(bytes: &[u8]) -> bool { + (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) + || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) +} + pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Expression { trace!("parsing: string, with required delimiters"); @@ -4544,7 +4574,8 @@ pub fn parse_value( | SyntaxShape::Table(_) | SyntaxShape::Signature | SyntaxShape::Filepath - | SyntaxShape::String => {} + | SyntaxShape::String + | SyntaxShape::LsGlobPattern => {} _ => { working_set.error(ParseError::Expected("non-[] value", span)); return Expression::garbage(span); @@ -4569,6 +4600,7 @@ pub fn parse_value( SyntaxShape::Filepath => parse_filepath(working_set, span), SyntaxShape::Directory => parse_directory(working_set, span), SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span), + SyntaxShape::LsGlobPattern => parse_ls_glob_pattern(working_set, span), SyntaxShape::String => parse_string(working_set, span), SyntaxShape::Binary => parse_binary(working_set, span), SyntaxShape::Signature => { @@ -5961,8 +5993,8 @@ pub fn discover_captures_in_expr( discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; } } - Expr::Filepath(_) => {} - Expr::Directory(_) => {} + Expr::Filepath(_, _) => {} + Expr::Directory(_, _) => {} Expr::Float(_) => {} Expr::FullCellPath(cell_path) => { discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks, output)?; @@ -5971,7 +6003,8 @@ pub fn discover_captures_in_expr( Expr::Overlay(_) => {} Expr::Garbage => {} Expr::Nothing => {} - Expr::GlobPattern(_) => {} + Expr::GlobPattern(_, _) => {} + Expr::LsGlobPattern(_, _) => {} Expr::Int(_) => {} Expr::Keyword(_, _, expr) => { discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?; diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index ef8349ff35..e46e88fba2 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -37,9 +37,10 @@ pub enum Expr { Keyword(Vec, Span, Box), ValueWithUnit(Box, Spanned), DateTime(chrono::DateTime), - Filepath(String), - Directory(String), - GlobPattern(String), + Filepath(String, bool), + Directory(String, bool), + GlobPattern(String, bool), + LsGlobPattern(String, bool), String(String), CellPath(CellPath), FullCellPath(Box), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 9361e0bbae..3f30ac6461 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -197,8 +197,8 @@ impl Expression { } Expr::ImportPattern(_) => false, Expr::Overlay(_) => false, - Expr::Filepath(_) => false, - Expr::Directory(_) => false, + Expr::Filepath(_, _) => false, + Expr::Directory(_, _) => false, Expr::Float(_) => false, Expr::FullCellPath(full_cell_path) => { if full_cell_path.head.has_in_variable(working_set) { @@ -208,7 +208,8 @@ impl Expression { } Expr::Garbage => false, Expr::Nothing => false, - Expr::GlobPattern(_) => false, + Expr::GlobPattern(_, _) => false, + Expr::LsGlobPattern(_, _) => false, Expr::Int(_) => false, Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set), Expr::List(list) => { @@ -375,8 +376,8 @@ impl Expression { expr.replace_span(working_set, replaced, new_span); } } - Expr::Filepath(_) => {} - Expr::Directory(_) => {} + Expr::Filepath(_, _) => {} + Expr::Directory(_, _) => {} Expr::Float(_) => {} Expr::FullCellPath(full_cell_path) => { full_cell_path @@ -387,7 +388,8 @@ impl Expression { Expr::Overlay(_) => {} Expr::Garbage => {} Expr::Nothing => {} - Expr::GlobPattern(_) => {} + Expr::GlobPattern(_, _) => {} + Expr::LsGlobPattern(_, _) => {} Expr::MatchBlock(_) => {} Expr::Int(_) => {} Expr::Keyword(_, _, expr) => expr.replace_span(working_set, replaced, new_span), diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index dedc8abd17..a96fcb085e 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -27,9 +27,9 @@ pub trait Eval { Expr::Int(i) => Ok(Value::int(*i, expr.span)), Expr::Float(f) => Ok(Value::float(*f, expr.span)), Expr::Binary(b) => Ok(Value::binary(b.clone(), expr.span)), - Expr::Filepath(path) => Self::eval_filepath(state, mut_state, path.clone(), expr.span), - Expr::Directory(path) => { - Self::eval_directory(state, mut_state, path.clone(), expr.span) + Expr::Filepath(path, quoted) => Self::eval_filepath(state, mut_state, path.clone(), *quoted, expr.span), + Expr::Directory(path, quoted) => { + Self::eval_directory(state, mut_state, path.clone(), *quoted, expr.span) } Expr::Var(var_id) => Self::eval_var(state, mut_state, *var_id, expr.span), Expr::CellPath(cell_path) => Ok(Value::cell_path(cell_path.clone(), expr.span)), @@ -274,8 +274,15 @@ pub trait Eval { Self::eval_string_interpolation(state, mut_state, exprs, expr.span) } Expr::Overlay(_) => Self::eval_overlay(state, expr.span), - Expr::GlobPattern(pattern) => { - Self::eval_glob_pattern(state, mut_state, pattern.clone(), expr.span) + Expr::GlobPattern(pattern, quoted) => { + Self::eval_glob_pattern(state, mut_state, pattern.clone(), *quoted, expr.span) + } + Expr::LsGlobPattern(pattern, quoted) => { + if *quoted { + Ok(Value::quoted_string(pattern, expr.span)) + } else { + Ok(Value::string(pattern, expr.span)) + } } Expr::MatchBlock(_) // match blocks are handled by `match` | Expr::VarDecl(_) @@ -291,6 +298,7 @@ pub trait Eval { state: Self::State<'_>, mut_state: &mut Self::MutState, path: String, + quoted: bool, span: Span, ) -> Result; @@ -298,6 +306,7 @@ pub trait Eval { state: Self::State<'_>, mut_state: &mut Self::MutState, path: String, + quoted: bool, span: Span, ) -> Result; @@ -370,6 +379,7 @@ pub trait Eval { state: Self::State<'_>, mut_state: &mut Self::MutState, pattern: String, + quoted: bool, span: Span, ) -> Result; diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index edbca1c322..e6e1b58ba8 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -282,6 +282,7 @@ impl Eval for EvalConst { _: &StateWorkingSet, _: &mut (), path: String, + _: bool, span: Span, ) -> Result { Ok(Value::string(path, span)) @@ -291,6 +292,7 @@ impl Eval for EvalConst { _: &StateWorkingSet, _: &mut (), _: String, + _: bool, span: Span, ) -> Result { Err(ShellError::NotAConstant { span }) @@ -392,6 +394,7 @@ impl Eval for EvalConst { _: &StateWorkingSet, _: &mut (), _: String, + _: bool, span: Span, ) -> Result { Err(ShellError::NotAConstant { span }) diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index a95e5fcfc9..53e29d4777 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -64,6 +64,9 @@ pub enum SyntaxShape { /// A glob pattern is allowed, eg `foo*` GlobPattern, + /// A special glob pattern for ls. + LsGlobPattern, + /// Only an integer value is allowed Int, @@ -151,6 +154,7 @@ impl SyntaxShape { SyntaxShape::Filesize => Type::Filesize, SyntaxShape::FullCellPath => Type::Any, SyntaxShape::GlobPattern => Type::String, + SyntaxShape::LsGlobPattern => Type::String, SyntaxShape::Error => Type::Error, SyntaxShape::ImportPattern => Type::Any, SyntaxShape::Int => Type::Int, @@ -201,6 +205,7 @@ impl Display for SyntaxShape { SyntaxShape::Filepath => write!(f, "path"), SyntaxShape::Directory => write!(f, "directory"), SyntaxShape::GlobPattern => write!(f, "glob"), + SyntaxShape::LsGlobPattern => write!(f, "glob"), SyntaxShape::ImportPattern => write!(f, "import"), SyntaxShape::Block => write!(f, "block"), SyntaxShape::Closure(args) => { diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index b7e43ff67e..451d66ded0 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use super::NuPath; use crate::ast::{CellPath, PathMember}; use crate::engine::{Block, Closure}; use crate::{Range, Record, ShellError, Spanned, Value}; @@ -203,6 +204,45 @@ impl FromValue for Spanned { } } +impl FromValue for NuPath { + fn from_value(v: Value) -> Result { + // FIXME: we may want to fail a little nicer here + match v { + Value::CellPath { val, .. } => Ok(NuPath::UnQuoted(val.to_string())), + Value::String { val, .. } => Ok(NuPath::UnQuoted(val)), + Value::QuotedString { val, .. } => Ok(NuPath::Quoted(val)), + v => Err(ShellError::CantConvert { + to_type: "string".into(), + from_type: v.get_type().to_string(), + span: v.span(), + help: None, + }), + } + } +} + +impl FromValue for Spanned { + fn from_value(v: Value) -> Result { + let span = v.span(); + Ok(Spanned { + item: match v { + Value::CellPath { val, .. } => NuPath::UnQuoted(val.to_string()), + Value::String { val, .. } => NuPath::UnQuoted(val), + Value::QuotedString { val, .. } => NuPath::Quoted(val), + v => { + return Err(ShellError::CantConvert { + to_type: "string".into(), + from_type: v.get_type().to_string(), + span: v.span(), + help: None, + }) + } + }, + span, + }) + } +} + impl FromValue for Vec { fn from_value(v: Value) -> Result { // FIXME: we may want to fail a little nicer here diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 3f4574735b..af77f8e6d5 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -2,6 +2,7 @@ mod custom_value; mod from; mod from_value; mod lazy_record; +mod path; mod range; mod record; mod stream; @@ -24,6 +25,7 @@ use nu_utils::{ contains_emoji, get_system_locale, locale::get_system_locale_string, IgnoreCaseExt, }; use num_format::ToFormattedString; +pub use path::*; pub use range::*; pub use record::Record; use serde::{Deserialize, Serialize}; @@ -90,6 +92,12 @@ pub enum Value { // please use .span() instead of matching this span value internal_span: Span, }, + QuotedString { + val: String, + // note: spans are being refactored out of Value + // please use .span() instead of matching this span value + internal_span: Span, + }, Record { val: Record, // note: spans are being refactored out of Value @@ -179,6 +187,10 @@ impl Clone for Value { val: val.clone(), internal_span: *internal_span, }, + Value::QuotedString { val, internal_span } => Value::QuotedString { + val: val.clone(), + internal_span: *internal_span, + }, Value::Record { val, internal_span } => Value::Record { val: val.clone(), internal_span: *internal_span, @@ -509,6 +521,7 @@ impl Value { | Value::Date { internal_span, .. } | Value::Range { internal_span, .. } | Value::String { internal_span, .. } + | Value::QuotedString { internal_span, .. } | Value::Record { internal_span, .. } | Value::List { internal_span, .. } | Value::Block { internal_span, .. } @@ -533,6 +546,7 @@ impl Value { | Value::Date { internal_span, .. } | Value::Range { internal_span, .. } | Value::String { internal_span, .. } + | Value::QuotedString { internal_span, .. } | Value::Record { internal_span, .. } | Value::LazyRecord { internal_span, .. } | Value::List { internal_span, .. } @@ -559,6 +573,7 @@ impl Value { Value::Date { .. } => Type::Date, Value::Range { .. } => Type::Range, Value::String { .. } => Type::String, + Value::QuotedString { .. } => Type::String, Value::Record { val, .. } => { Type::Record(val.iter().map(|(x, y)| (x.clone(), y.get_type())).collect()) } @@ -672,6 +687,7 @@ impl Value { ) } Value::String { val, .. } => val.clone(), + Value::QuotedString { val, .. } => val.clone(), Value::List { vals: val, .. } => format!( "[{}]", val.iter() @@ -726,6 +742,7 @@ impl Value { ) } Value::String { val, .. } => val.to_string(), + Value::QuotedString { val, .. } => val.to_string(), Value::List { ref vals, .. } => { if !vals.is_empty() && vals.iter().all(|x| matches!(x, Value::Record { .. })) { format!( @@ -852,6 +869,7 @@ impl Value { ) } Value::String { val, .. } => val.clone(), + Value::QuotedString { val, .. } => val.clone(), Value::List { vals: val, .. } => format!( "[{}]", val.iter() @@ -1750,6 +1768,13 @@ impl Value { } } + pub fn quoted_string(val: impl Into, span: Span) -> Value { + Value::QuotedString { + val: val.into(), + internal_span: span, + } + } + pub fn record(val: Record, span: Span) -> Value { Value::Record { val, @@ -1955,6 +1980,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Less), Value::Range { .. } => Some(Ordering::Less), Value::String { .. } => Some(Ordering::Less), + Value::QuotedString { .. } => Some(Ordering::Less), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -1975,6 +2001,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Less), Value::Range { .. } => Some(Ordering::Less), Value::String { .. } => Some(Ordering::Less), + Value::QuotedString { .. } => Some(Ordering::Less), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -1995,6 +2022,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Less), Value::Range { .. } => Some(Ordering::Less), Value::String { .. } => Some(Ordering::Less), + Value::QuotedString { .. } => Some(Ordering::Less), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -2015,6 +2043,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Less), Value::Range { .. } => Some(Ordering::Less), Value::String { .. } => Some(Ordering::Less), + Value::QuotedString { .. } => Some(Ordering::Less), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -2035,6 +2064,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Less), Value::Range { .. } => Some(Ordering::Less), Value::String { .. } => Some(Ordering::Less), + Value::QuotedString { .. } => Some(Ordering::Less), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -2055,6 +2085,7 @@ impl PartialOrd for Value { Value::Date { val: rhs, .. } => lhs.partial_cmp(rhs), Value::Range { .. } => Some(Ordering::Less), Value::String { .. } => Some(Ordering::Less), + Value::QuotedString { .. } => Some(Ordering::Less), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -2075,6 +2106,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { val: rhs, .. } => lhs.partial_cmp(rhs), Value::String { .. } => Some(Ordering::Less), + Value::QuotedString { .. } => Some(Ordering::Less), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -2095,6 +2127,28 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { val: rhs, .. } => lhs.partial_cmp(rhs), + Value::QuotedString { val: rhs, .. } => lhs.partial_cmp(rhs), + Value::Record { .. } => Some(Ordering::Less), + Value::LazyRecord { .. } => Some(Ordering::Less), + Value::List { .. } => Some(Ordering::Less), + Value::Block { .. } => Some(Ordering::Less), + Value::Closure { .. } => Some(Ordering::Less), + Value::Nothing { .. } => Some(Ordering::Less), + Value::Error { .. } => Some(Ordering::Less), + Value::Binary { .. } => Some(Ordering::Less), + Value::CellPath { .. } => Some(Ordering::Less), + Value::CustomValue { .. } => Some(Ordering::Less), + }, + (Value::QuotedString { val: lhs, .. }, rhs) => match rhs { + Value::Bool { .. } => Some(Ordering::Greater), + Value::Int { .. } => Some(Ordering::Greater), + Value::Float { .. } => Some(Ordering::Greater), + Value::Filesize { .. } => Some(Ordering::Greater), + Value::Duration { .. } => Some(Ordering::Greater), + Value::Date { .. } => Some(Ordering::Greater), + Value::Range { .. } => Some(Ordering::Greater), + Value::String { val: rhs, .. } => lhs.partial_cmp(rhs), + Value::QuotedString { val: rhs, .. } => lhs.partial_cmp(rhs), Value::Record { .. } => Some(Ordering::Less), Value::LazyRecord { .. } => Some(Ordering::Less), Value::List { .. } => Some(Ordering::Less), @@ -2115,6 +2169,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { val: rhs, .. } => { // reorder cols and vals to make more logically compare. // more general, if two record have same col and values, @@ -2154,6 +2209,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { .. } => Some(Ordering::Greater), Value::LazyRecord { .. } => Some(Ordering::Greater), Value::List { vals: rhs, .. } => lhs.partial_cmp(rhs), @@ -2174,6 +2230,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { .. } => Some(Ordering::Greater), Value::List { .. } => Some(Ordering::Greater), Value::LazyRecord { .. } => Some(Ordering::Greater), @@ -2194,6 +2251,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { .. } => Some(Ordering::Greater), Value::LazyRecord { .. } => Some(Ordering::Greater), Value::List { .. } => Some(Ordering::Greater), @@ -2214,6 +2272,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { .. } => Some(Ordering::Greater), Value::LazyRecord { .. } => Some(Ordering::Greater), Value::List { .. } => Some(Ordering::Greater), @@ -2234,6 +2293,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { .. } => Some(Ordering::Greater), Value::LazyRecord { .. } => Some(Ordering::Greater), Value::List { .. } => Some(Ordering::Greater), @@ -2254,6 +2314,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { .. } => Some(Ordering::Greater), Value::LazyRecord { .. } => Some(Ordering::Greater), Value::List { .. } => Some(Ordering::Greater), @@ -2274,6 +2335,7 @@ impl PartialOrd for Value { Value::Date { .. } => Some(Ordering::Greater), Value::Range { .. } => Some(Ordering::Greater), Value::String { .. } => Some(Ordering::Greater), + Value::QuotedString { .. } => Some(Ordering::Greater), Value::Record { .. } => Some(Ordering::Greater), Value::LazyRecord { .. } => Some(Ordering::Greater), Value::List { .. } => Some(Ordering::Greater), diff --git a/crates/nu-protocol/src/value/path.rs b/crates/nu-protocol/src/value/path.rs new file mode 100644 index 0000000000..9c18b68565 --- /dev/null +++ b/crates/nu-protocol/src/value/path.rs @@ -0,0 +1,19 @@ +/// A simple wrapper to String. +/// +/// But it tracks if the string is originally quoted. +/// So commands can make decision on path auto-expanding behavior. +#[derive(Debug, Clone)] +pub enum NuPath { + /// A quoted path(except backtick), in this case, nushell shouldn't auto-expand path. + Quoted(String), + /// An unquoted path, in this case, nushell should auto-expand path. + UnQuoted(String), +} + +impl AsRef for NuPath { + fn as_ref(&self) -> &str { + match self { + NuPath::Quoted(s) | NuPath::UnQuoted(s) => s, + } + } +} diff --git a/src/tests/test_custom_commands.rs b/src/tests/test_custom_commands.rs index 93b2c83628..48e575a14b 100644 --- a/src/tests/test_custom_commands.rs +++ b/src/tests/test_custom_commands.rs @@ -229,3 +229,13 @@ fn type_check_for_during_eval2() -> TestResult { "can't convert nothing to string", ) } + +#[test] +fn path_argument_dont_auto_expand_if_single_quoted() -> TestResult { + run_test("def spam [foo: path] { echo $foo }; spam '~/aa'", "~/aa") +} + +#[test] +fn path_argument_dont_auto_expand_if_double_quoted() -> TestResult { + run_test(r#"def spam [foo: path] { echo $foo }; spam "~/aa""#, "~/aa") +}