diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 432db0eee5..078dfcc5d2 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -336,7 +336,6 @@ fn find_matching_block_end_in_expr( 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-base/src/arg_glob.rs b/crates/nu-cmd-base/src/arg_glob.rs deleted file mode 100644 index 83df87fa49..0000000000 --- a/crates/nu-cmd-base/src/arg_glob.rs +++ /dev/null @@ -1,205 +0,0 @@ -// utilities for expanding globs in command arguments - -use nu_glob::{glob_with_parent, MatchOptions, Paths}; -use nu_protocol::{ShellError, Spanned}; -use std::fs; -use std::path::{Path, PathBuf}; - -// standard glob options to use for filesystem command arguments - -const GLOB_PARAMS: MatchOptions = MatchOptions { - case_sensitive: true, - require_literal_separator: false, - require_literal_leading_dot: false, - recursive_match_hidden_dir: true, -}; - -// handle an argument that could be a literal path or a glob. -// if literal path, return just that (whether user can access it or not). -// if glob, expand into matching paths, using GLOB_PARAMS options. -pub fn arg_glob( - pattern: &Spanned, // alleged path or glob - cwd: &Path, // current working directory -) -> Result { - arg_glob_opt(pattern, cwd, GLOB_PARAMS) -} - -// variant of [arg_glob] that requires literal dot prefix in pattern to match dot-prefixed path. -pub fn arg_glob_leading_dot(pattern: &Spanned, cwd: &Path) -> Result { - arg_glob_opt( - pattern, - cwd, - MatchOptions { - require_literal_leading_dot: true, - ..GLOB_PARAMS - }, - ) -} - -fn arg_glob_opt( - pattern: &Spanned, - cwd: &Path, - options: MatchOptions, -) -> Result { - // remove ansi coloring (?) - let pattern = { - Spanned { - item: nu_utils::strip_ansi_string_unlikely(pattern.item.clone()), - span: pattern.span, - } - }; - - // if there's a file with same path as the pattern, just return that. - let pp = cwd.join(&pattern.item); - let md = fs::metadata(pp); - #[allow(clippy::single_match)] - match md { - Ok(_metadata) => { - return Ok(Paths::single(&PathBuf::from(pattern.item), cwd)); - } - // file not found, but also "invalid chars in file" (e.g * on Windows). Fall through and glob - Err(_) => {} - } - - // user wasn't referring to a specific thing in filesystem, try to glob it. - match glob_with_parent(&pattern.item, options, cwd) { - Ok(p) => Ok(p), - Err(pat_err) => Err(ShellError::InvalidGlobPattern { - msg: pat_err.msg.into(), - span: pattern.span, - }), - } -} - -#[cfg(test)] -mod test { - use super::*; - use nu_glob::GlobResult; - use nu_protocol::{Span, Spanned}; - use nu_test_support::fs::Stub::EmptyFile; - use nu_test_support::playground::Playground; - use rstest::rstest; - - fn spanned_string(str: &str) -> Spanned { - Spanned { - item: str.to_string(), - span: Span::test_data(), - } - } - - #[test] - fn does_something() { - let act = arg_glob(&spanned_string("*"), &PathBuf::from(".")); - assert!(act.is_ok()); - for f in act.expect("checked ok") { - match f { - Ok(p) => { - assert!(!p.to_str().unwrap().is_empty()); - } - Err(e) => panic!("unexpected error {:?}", e), - }; - } - } - - #[test] - fn glob_format_error() { - let act = arg_glob(&spanned_string(r#"ab]c[def"#), &PathBuf::from(".")); - assert!(act.is_err()); - } - - #[rstest] - #[case("*", 4, "no dirs")] - #[case("**/*", 7, "incl dirs")] - fn glob_subdirs(#[case] pat: &str, #[case] exp_count: usize, #[case] case: &str) { - Playground::setup("glob_subdirs", |dirs, sandbox| { - sandbox.with_files(vec![ - EmptyFile("yehuda.txt"), - EmptyFile("jttxt"), - EmptyFile("andres.txt"), - ]); - sandbox.mkdir(".children"); - sandbox.within(".children").with_files(vec![ - EmptyFile("timothy.txt"), - EmptyFile("tiffany.txt"), - EmptyFile("trish.txt"), - ]); - - let p: Vec = arg_glob(&spanned_string(pat), &dirs.test) - .expect("no error") - .collect(); - assert_eq!( - exp_count, - p.iter().filter(|i| i.is_ok()).count(), - " case: {case} ", - ); - - // expected behavior -- that directories are included in results (if name matches pattern) - let t = p - .iter() - .any(|i| i.as_ref().unwrap().to_string_lossy().contains(".children")); - assert!(t, "check for dir, case {case}"); - }) - } - - #[rstest] - #[case("yehuda.txt", true, 1, "matches literal path")] - #[case("*", false, 3, "matches glob")] - #[case(r#"bad[glob.foo"#, true, 1, "matches literal, would be bad glob pat")] - fn exact_vs_glob( - #[case] pat: &str, - #[case] exp_matches_input: bool, - #[case] exp_count: usize, - #[case] case: &str, - ) { - Playground::setup("exact_vs_glob", |dirs, sandbox| { - sandbox.with_files(vec![ - EmptyFile("yehuda.txt"), - EmptyFile("jttxt"), - EmptyFile("bad[glob.foo"), - ]); - - let res = arg_glob(&spanned_string(pat), &dirs.test) - .expect("no error") - .collect::>(); - - eprintln!("res: {:?}", res); - if exp_matches_input { - assert_eq!( - exp_count, - res.len(), - " case {case}: matches input, but count not 1? " - ); - assert_eq!( - &res[0].as_ref().unwrap().to_string_lossy(), - pat, // todo: is it OK for glob to return relative paths (not to current cwd, but to arg cwd of arg_glob)? - ); - } else { - assert_eq!(exp_count, res.len(), " case: {}: matched glob", case); - } - }) - } - - #[rstest] - #[case(r#"realbad[glob.foo"#, true, 1, "error, bad glob")] - fn exact_vs_bad_glob( - // if path doesn't exist but pattern is not valid glob, should get error. - #[case] pat: &str, - #[case] _exp_matches_input: bool, - #[case] _exp_count: usize, - #[case] _tag: &str, - ) { - Playground::setup("exact_vs_bad_glob", |dirs, sandbox| { - sandbox.with_files(vec![ - EmptyFile("yehuda.txt"), - EmptyFile("jttxt"), - EmptyFile("bad[glob.foo"), - ]); - - let res = arg_glob(&spanned_string(pat), &dirs.test); - assert!(res - .expect_err("expected error") - .to_string() - .contains("Invalid glob pattern")); - }) - } -} diff --git a/crates/nu-cmd-base/src/lib.rs b/crates/nu-cmd-base/src/lib.rs index c77d7f58e9..250a57d741 100644 --- a/crates/nu-cmd-base/src/lib.rs +++ b/crates/nu-cmd-base/src/lib.rs @@ -1,7 +1,4 @@ -mod arg_glob; pub mod formats; pub mod hook; pub mod input_handler; pub mod util; -pub use arg_glob::arg_glob; -pub use arg_glob::arg_glob_leading_dot; diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 7732533ea3..a00cab81b2 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -41,7 +41,7 @@ impl Command for Ls { .input_output_types(vec![(Type::Nothing, Type::Table(vec![]))]) // 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.") + .optional("pattern", SyntaxShape::GlobPattern, "The glob pattern to use.") .switch("all", "Show hidden files", Some('a')) .switch( "long", @@ -165,7 +165,8 @@ impl Command for Ls { }; let hidden_dir_specified = is_hidden_dir(&path); - // when it's quoted, we need to escape our glob pattern + // when it's quoted, we need to escape our glob pattern(but without the last extra + // start which may be added under given directory) // so we can do ls for a file or directory like `a[123]b` let path = if quoted { let p = path.display().to_string(); @@ -185,7 +186,8 @@ impl Command for Ls { }; let glob_path = Spanned { - item: path.clone(), + // It needs to be un-quoted, the relative logic is handled previously + item: NuPath::UnQuoted(path.clone()), span: p_tag, }; diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index e0c3f6c870..e082bcd95c 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -1,14 +1,13 @@ use std::path::{Path, PathBuf}; use super::util::try_interaction; -use nu_cmd_base::arg_glob; use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, - Spanned, SyntaxShape, Type, Value, + Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature, + Span, Spanned, SyntaxShape, Type, Value, }; #[derive(Clone)] @@ -63,7 +62,8 @@ impl Command for Mv { _input: PipelineData, ) -> Result { // TODO: handle invalid directory or insufficient permissions when moving - let spanned_source: Spanned = call.req(engine_state, stack, 0)?; + let mut spanned_source: Spanned = call.req(engine_state, stack, 0)?; + spanned_source.item = spanned_source.item.strip_ansi_string_unlikely(); let spanned_destination: Spanned = call.req(engine_state, stack, 1)?; let verbose = call.has_flag(engine_state, stack, "verbose")?; let interactive = call.has_flag(engine_state, stack, "interactive")?; @@ -73,11 +73,11 @@ impl Command for Mv { let ctrlc = engine_state.ctrlc.clone(); let path = current_dir(engine_state, stack)?; - let source = path.join(spanned_source.item.as_str()); let destination = path.join(spanned_destination.item.as_str()); - let mut sources = - arg_glob(&spanned_source, &path).map_or_else(|_| Vec::new(), Iterator::collect); + let mut sources = nu_engine::glob_from(&spanned_source, &path, call.head, None) + .map(|p| p.1) + .map_or_else(|_| Vec::new(), Iterator::collect); if sources.is_empty() { return Err(ShellError::FileNotFound { @@ -94,7 +94,7 @@ impl Command for Mv { // // Second, the destination doesn't exist, so we can only rename a single source. Otherwise // it's an error. - + let source = path.join(spanned_source.item.as_ref()); if destination.exists() && !force && !destination.is_dir() && !source.is_dir() { return Err(ShellError::GenericError { error: "Destination file already exists".into(), diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 847628ab41..7611212a5e 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -4,8 +4,8 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::util::BufferedReader; use nu_protocol::{ - Category, DataSource, Example, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata, - RawStream, ShellError, Signature, Spanned, SyntaxShape, Type, Value, + Category, DataSource, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, + PipelineMetadata, RawStream, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use std::io::BufReader; @@ -42,10 +42,10 @@ impl Command for Open { fn signature(&self) -> nu_protocol::Signature { Signature::build("open") .input_output_types(vec![(Type::Nothing, Type::Any), (Type::String, Type::Any)]) - .optional("filename", SyntaxShape::Filepath, "The filename to use.") + .optional("filename", SyntaxShape::GlobPattern, "The filename to use.") .rest( "filenames", - SyntaxShape::Filepath, + SyntaxShape::GlobPattern, "Optional additional files to open.", ) .switch("raw", "open file as raw binary", Some('r')) @@ -63,8 +63,8 @@ impl Command for Open { let call_span = call.head; let ctrlc = engine_state.ctrlc.clone(); let cwd = current_dir(engine_state, stack)?; - let req_path = call.opt::>(engine_state, stack, 0)?; - let mut path_params = call.rest::>(engine_state, stack, 1)?; + let req_path = call.opt::>(engine_state, stack, 0)?; + let mut path_params = call.rest::>(engine_state, stack, 1)?; // FIXME: JT: what is this doing here? @@ -87,19 +87,20 @@ impl Command for Open { } }; - path_params.insert(0, filename); + path_params.insert( + 0, + Spanned { + item: NuPath::UnQuoted(filename.item), + span: filename.span, + }, + ); } let mut output = vec![]; - for path in path_params.into_iter() { + for mut path in path_params.into_iter() { //FIXME: `open` should not have to do this - let path = { - Spanned { - item: nu_utils::strip_ansi_string_unlikely(path.item), - span: path.span, - } - }; + path.item = path.item.strip_ansi_string_unlikely(); let arg_span = path.span; // let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d')); diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 929d16f3f0..d44f278762 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -7,14 +7,15 @@ use std::path::PathBuf; use super::util::try_interaction; -use nu_cmd_base::arg_glob_leading_dot; use nu_engine::env::current_dir; use nu_engine::CallExt; +use nu_glob::MatchOptions; +use nu_path::expand_path_with; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, - Spanned, SyntaxShape, Type, Value, + Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature, + Span, Spanned, SyntaxShape, Type, Value, }; const TRASH_SUPPORTED: bool = cfg!(all( @@ -134,7 +135,7 @@ fn rm( let ctrlc = engine_state.ctrlc.clone(); - let mut targets: Vec> = call.rest(engine_state, stack, 0)?; + let mut targets: Vec> = call.rest(engine_state, stack, 0)?; let mut unique_argument_check = None; @@ -157,12 +158,19 @@ fn rm( for (idx, path) in targets.clone().into_iter().enumerate() { if let Some(ref home) = home { - if &path.item == home { + if expand_path_with(path.item.as_ref(), ¤tdir_path) + .to_string_lossy() + .as_ref() + == home.as_str() + { unique_argument_check = Some(path.span); } } let corrected_path = Spanned { - item: nu_utils::strip_ansi_string_unlikely(path.item), + item: match path.item { + NuPath::Quoted(s) => NuPath::Quoted(nu_utils::strip_ansi_string_unlikely(s)), + NuPath::UnQuoted(s) => NuPath::UnQuoted(nu_utils::strip_ansi_string_unlikely(s)), + }, span: path.span, }; let _ = std::mem::replace(&mut targets[idx], corrected_path); @@ -233,7 +241,8 @@ fn rm( let mut all_targets: HashMap = HashMap::new(); for target in targets { - if currentdir_path.to_string_lossy() == target.item + let path = expand_path_with(target.item.as_ref(), ¤tdir_path); + if currentdir_path.to_string_lossy() == path.to_string_lossy() || currentdir_path.starts_with(format!("{}{}", target.item, std::path::MAIN_SEPARATOR)) { return Err(ShellError::GenericError { @@ -245,10 +254,18 @@ fn rm( }); } - let path = currentdir_path.join(&target.item); - match arg_glob_leading_dot(&target, ¤tdir_path) { + // let path = currentdir_path.join(target.item.as_ref()); + match nu_engine::glob_from( + &target, + ¤tdir_path, + call.head, + Some(MatchOptions { + require_literal_leading_dot: true, + ..Default::default() + }), + ) { Ok(files) => { - for file in files { + for file in files.1 { match file { Ok(f) => { if !target_exists { diff --git a/crates/nu-command/src/filesystem/ucp.rs b/crates/nu-command/src/filesystem/ucp.rs index 8bb662375d..d3e6dcf23e 100644 --- a/crates/nu-command/src/filesystem/ucp.rs +++ b/crates/nu-command/src/filesystem/ucp.rs @@ -1,6 +1,5 @@ -use nu_cmd_base::arg_glob; use nu_engine::{current_dir, CallExt}; -use nu_glob::GlobResult; +use nu_protocol::NuPath; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, @@ -62,7 +61,7 @@ impl Command for UCp { None ) .switch("debug", "explain how a file is copied. Implies -v", None) - .rest("paths", SyntaxShape::Filepath, "Copy SRC file/s to DEST.") + .rest("paths", SyntaxShape::GlobPattern, "Copy SRC file/s to DEST.") .allow_variants_without_examples(true) .category(Category::FileSystem) } @@ -146,14 +145,7 @@ impl Command for UCp { let reflink_mode = uu_cp::ReflinkMode::Auto; #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))] let reflink_mode = uu_cp::ReflinkMode::Never; - let paths: Vec> = call.rest(engine_state, stack, 0)?; - let mut paths: Vec> = paths - .into_iter() - .map(|p| Spanned { - item: nu_utils::strip_ansi_string_unlikely(p.item), - span: p.span, - }) - .collect(); + let mut paths: Vec> = call.rest(engine_state, stack, 0)?; if paths.is_empty() { return Err(ShellError::GenericError { error: "Missing file operand".into(), @@ -167,15 +159,20 @@ impl Command for UCp { if paths.len() == 1 { return Err(ShellError::GenericError { error: "Missing destination path".into(), - msg: format!("Missing destination path operand after {}", paths[0].item), + msg: format!( + "Missing destination path operand after {}", + paths[0].item.as_ref() + ), span: Some(paths[0].span), help: None, inner: vec![], }); } let target = paths.pop().expect("Should not be reached?"); - let target_path = PathBuf::from(&target.item); - if target.item.ends_with(PATH_SEPARATOR) && !target_path.is_dir() { + let target_path = PathBuf::from(&nu_utils::strip_ansi_string_unlikely( + target.item.to_string(), + )); + if target.item.as_ref().ends_with(PATH_SEPARATOR) && !target_path.is_dir() { return Err(ShellError::GenericError { error: "is not a directory".into(), msg: "is not a directory".into(), @@ -190,8 +187,12 @@ impl Command for UCp { let cwd = current_dir(engine_state, stack)?; let mut sources: Vec = Vec::new(); - for p in paths { - let exp_files = arg_glob(&p, &cwd)?.collect::>(); + for mut p in paths { + p.item = p.item.strip_ansi_string_unlikely(); + let exp_files: Vec> = + nu_engine::glob_from(&p, &cwd, call.head, None) + .map(|f| f.1)? + .collect(); if exp_files.is_empty() { return Err(ShellError::FileNotFound { span: p.span }); }; @@ -212,12 +213,7 @@ impl Command for UCp { }; app_vals.push(path) } - Err(e) => { - return Err(ShellError::ErrorExpandingGlob { - msg: format!("error {} in path {}", e.error(), e.path().display()), - span: p.span, - }); - } + Err(e) => return Err(e), } } sources.append(&mut app_vals); diff --git a/crates/nu-command/src/filesystem/umv.rs b/crates/nu-command/src/filesystem/umv.rs index 7ee77e5b6c..beb8304b16 100644 --- a/crates/nu-command/src/filesystem/umv.rs +++ b/crates/nu-command/src/filesystem/umv.rs @@ -1,12 +1,10 @@ -use nu_cmd_base::arg_glob; use nu_engine::current_dir; use nu_engine::CallExt; -use nu_glob::GlobResult; use nu_path::{expand_path_with, expand_to_real_path}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, + Category, Example, NuPath, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, }; use std::ffi::OsString; use std::path::PathBuf; @@ -58,7 +56,7 @@ impl Command for UMv { .switch("no-clobber", "do not overwrite an existing file", Some('n')) .rest( "paths", - SyntaxShape::Filepath, + SyntaxShape::GlobPattern, "Rename SRC to DST, or move SRC to DIR.", ) .allow_variants_without_examples(true) @@ -84,14 +82,8 @@ impl Command for UMv { uu_mv::OverwriteMode::Force }; - let paths: Vec> = call.rest(engine_state, stack, 0)?; - let paths: Vec> = paths - .into_iter() - .map(|p| Spanned { - item: nu_utils::strip_ansi_string_unlikely(p.item), - span: p.span, - }) - .collect(); + let cwd = current_dir(engine_state, stack)?; + let mut paths: Vec> = call.rest(engine_state, stack, 0)?; if paths.is_empty() { return Err(ShellError::GenericError { error: "Missing file operand".into(), @@ -102,9 +94,13 @@ impl Command for UMv { }); } if paths.len() == 1 { + // expand path for better error message return Err(ShellError::GenericError { error: "Missing destination path".into(), - msg: format!("Missing destination path operand after {}", paths[0].item), + msg: format!( + "Missing destination path operand after {}", + expand_path_with(paths[0].item.as_ref(), cwd).to_string_lossy() + ), span: Some(paths[0].span), help: None, inner: Vec::new(), @@ -112,11 +108,18 @@ impl Command for UMv { } // Do not glob target - let sources = &paths[..paths.len() - 1]; - let cwd = current_dir(engine_state, stack)?; + let spanned_target = paths.pop().ok_or(ShellError::NushellFailedSpanned { + msg: "Missing file operand".into(), + label: "Missing file operand".into(), + span: call.head, + })?; let mut files: Vec = Vec::new(); - for p in sources { - let exp_files = arg_glob(p, &cwd)?.collect::>(); + for mut p in paths { + p.item = p.item.strip_ansi_string_unlikely(); + let exp_files: Vec> = + nu_engine::glob_from(&p, &cwd, call.head, None) + .map(|f| f.1)? + .collect(); if exp_files.is_empty() { return Err(ShellError::FileNotFound { span: p.span }); }; @@ -126,12 +129,7 @@ impl Command for UMv { Ok(path) => { app_vals.push(path); } - Err(e) => { - return Err(ShellError::ErrorExpandingGlob { - msg: format!("error {} in path {}", e.error(), e.path().display()), - span: p.span, - }); - } + Err(e) => return Err(e), } } files.append(&mut app_vals); @@ -146,12 +144,9 @@ impl Command for UMv { } // Add back the target after globbing - let spanned_target = paths.last().ok_or(ShellError::NushellFailedSpanned { - msg: "Missing file operand".into(), - label: "Missing file operand".into(), - span: call.head, - })?; - let expanded_target = expand_to_real_path(spanned_target.item.clone()); + let expanded_target = expand_to_real_path(nu_utils::strip_ansi_string_unlikely( + spanned_target.item.to_string(), + )); let abs_target_path = expand_path_with(expanded_target, &cwd); files.push(abs_target_path.clone()); let files = files diff --git a/crates/nu-command/src/formats/from/nuon.rs b/crates/nu-command/src/formats/from/nuon.rs index 014cd2419c..37bd5f6b53 100644 --- a/crates/nu-command/src/formats/from/nuon.rs +++ b/crates/nu-command/src/formats/from/nuon.rs @@ -243,7 +243,6 @@ fn convert_to_value( span: expr.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/platform/du.rs b/crates/nu-command/src/platform/du.rs index 7c349ac202..461f9ef42f 100644 --- a/crates/nu-command/src/platform/du.rs +++ b/crates/nu-command/src/platform/du.rs @@ -1,12 +1,11 @@ use crate::{DirBuilder, DirInfo, FileInfo}; -use nu_cmd_base::arg_glob; use nu_engine::{current_dir, CallExt}; -use nu_glob::{GlobError, Pattern}; +use nu_glob::Pattern; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, - Spanned, SyntaxShape, Type, Value, + Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature, + Span, Spanned, SyntaxShape, Type, Value, }; use serde::Deserialize; @@ -15,7 +14,7 @@ pub struct Du; #[derive(Deserialize, Clone, Debug)] pub struct DuArgs { - path: Option>, + path: Option>, all: bool, deref: bool, exclude: Option>, @@ -116,28 +115,26 @@ impl Command for Du { let include_files = args.all; let mut paths = match args.path { - Some(p) => arg_glob(&p, ¤t_dir)?, + Some(p) => nu_engine::glob_from(&p, ¤t_dir, call.head, None), // The * pattern should never fail. - None => arg_glob( + None => nu_engine::glob_from( &Spanned { - item: "*".into(), + item: NuPath::UnQuoted("*".into()), span: Span::unknown(), }, ¤t_dir, - )?, + call.head, + None, + ), } + .map(|f| f.1)? .filter(move |p| { if include_files { true } else { - match p { - Ok(f) if f.is_dir() => true, - Err(e) if e.path().is_dir() => true, - _ => false, - } + matches!(p, Ok(f) if f.is_dir()) } - }) - .map(|v| v.map_err(glob_err_into)); + }); let all = args.all; let deref = args.deref; @@ -182,11 +179,6 @@ impl Command for Du { } } -fn glob_err_into(e: GlobError) -> ShellError { - let e = e.into_error(); - ShellError::from(e) -} - #[cfg(test)] mod tests { use super::Du; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 5be3d09374..43c81eba9b 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -2,6 +2,7 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::env_to_strings; use nu_engine::eval_expression; use nu_engine::CallExt; +use nu_protocol::NuPath; use nu_protocol::{ ast::{Call, Expr}, did_you_mean, @@ -735,7 +736,12 @@ fn trim_expand_and_apply_arg( } let cwd = PathBuf::from(cwd); if arg.item.contains('*') && run_glob_expansion { - if let Ok((prefix, matches)) = nu_engine::glob_from(&arg, &cwd, arg.span, None) { + // we need to run glob expansion, so it's unquoted. + let path = Spanned { + item: NuPath::UnQuoted(arg.item.clone()), + span: arg.span, + }; + if let Ok((prefix, matches)) = nu_engine::glob_from(&path, &cwd, arg.span, None) { let matches: Vec<_> = matches.collect(); // FIXME: do we want to special-case this further? We might accidentally expand when they don't diff --git a/crates/nu-command/tests/commands/move_/umv.rs b/crates/nu-command/tests/commands/move_/umv.rs index 05184c7d2d..95f9927e50 100644 --- a/crates/nu-command/tests/commands/move_/umv.rs +++ b/crates/nu-command/tests/commands/move_/umv.rs @@ -204,7 +204,7 @@ fn errors_if_source_doesnt_exist() { cwd: dirs.test(), "umv non-existing-file test_folder/" ); - assert!(actual.err.contains("file not found")); + assert!(actual.err.contains("Directory not found")); }) } @@ -559,10 +559,10 @@ fn mv_with_no_target() { } #[rstest] -#[case(r#"'a]c'"#)] -#[case(r#"'a[c'"#)] -#[case(r#"'a[bc]d'"#)] -#[case(r#"'a][c'"#)] +#[case("a]c")] +#[case("a[c")] +#[case("a[bc]d")] +#[case("a][c")] fn mv_files_with_glob_metachars(#[case] src_name: &str) { Playground::setup("umv_test_16", |dirs, sandbox| { sandbox.with_files(vec![FileWithContent( @@ -574,7 +574,7 @@ fn mv_files_with_glob_metachars(#[case] src_name: &str) { let actual = nu!( cwd: dirs.test(), - "umv {} {}", + "umv '{}' {}", src.display(), "hello_world_dest" ); @@ -586,8 +586,8 @@ fn mv_files_with_glob_metachars(#[case] src_name: &str) { #[cfg(not(windows))] #[rstest] -#[case(r#"'a]?c'"#)] -#[case(r#"'a*.?c'"#)] +#[case("a]?c")] +#[case("a*.?c")] // windows doesn't allow filename with `*`. fn mv_files_with_glob_metachars_nw(#[case] src_name: &str) { mv_files_with_glob_metachars(src_name); @@ -607,3 +607,25 @@ fn mv_with_cd() { assert!(actual.out.contains("body")); }); } + +#[test] +fn test_cp_inside_glob_metachars_dir() { + Playground::setup("open_files_inside_glob_metachars_dir", |dirs, sandbox| { + let sub_dir = "test[]"; + sandbox + .within(sub_dir) + .with_files(vec![FileWithContent("test_file.txt", "hello")]); + + let actual = nu!( + cwd: dirs.test().join(sub_dir), + "mv test_file.txt ../", + ); + + assert!(actual.err.is_empty()); + assert!(!files_exist_at( + vec!["test_file.txt"], + dirs.test().join(sub_dir) + )); + assert!(files_exist_at(vec!["test_file.txt"], dirs.test())); + }); +} diff --git a/crates/nu-command/tests/commands/open.rs b/crates/nu-command/tests/commands/open.rs index 68f7f5777f..dc5379ca22 100644 --- a/crates/nu-command/tests/commands/open.rs +++ b/crates/nu-command/tests/commands/open.rs @@ -3,6 +3,7 @@ use nu_test_support::fs::Stub::FileWithContent; use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; use nu_test_support::playground::Playground; use nu_test_support::{nu, pipeline}; +use rstest::rstest; #[test] fn parses_file_with_uppercase_extension() { @@ -336,3 +337,52 @@ fn open_no_parameter() { assert!(actual.err.contains("needs filename")); } + +#[rstest] +#[case("a]c")] +#[case("a[c")] +#[case("a[bc]d")] +#[case("a][c")] +fn open_files_with_glob_metachars(#[case] src_name: &str) { + Playground::setup("open_test_with_glob_metachars", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent(src_name, "hello")]); + + let src = dirs.test().join(src_name); + + let actual = nu!( + cwd: dirs.test(), + "open '{}'", + src.display(), + ); + + assert!(actual.err.is_empty()); + assert!(actual.out.contains("hello")); + }); +} + +#[cfg(not(windows))] +#[rstest] +#[case("a]?c")] +#[case("a*.?c")] +// windows doesn't allow filename with `*`. +fn open_files_with_glob_metachars_nw(#[case] src_name: &str) { + open_files_with_glob_metachars(src_name); +} + +#[test] +fn open_files_inside_glob_metachars_dir() { + Playground::setup("open_files_inside_glob_metachars_dir", |dirs, sandbox| { + let sub_dir = "test[]"; + sandbox + .within(sub_dir) + .with_files(vec![FileWithContent("test_file.txt", "hello")]); + + let actual = nu!( + cwd: dirs.test().join(sub_dir), + "open test_file.txt", + ); + + assert!(actual.err.is_empty()); + assert!(actual.out.contains("hello")); + }); +} diff --git a/crates/nu-command/tests/commands/platform/du.rs b/crates/nu-command/tests/commands/platform/du.rs index ab35f5c265..dd6daab1bd 100644 --- a/crates/nu-command/tests/commands/platform/du.rs +++ b/crates/nu-command/tests/commands/platform/du.rs @@ -1,4 +1,6 @@ -use nu_test_support::{nu, pipeline}; +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::{nu, pipeline, playground::Playground}; +use rstest::rstest; #[test] fn test_du_flag_min_size() { @@ -41,3 +43,33 @@ fn test_du_flag_max_depth() { )); assert!(actual.err.is_empty()); } + +#[rstest] +#[case("a]c")] +#[case("a[c")] +#[case("a[bc]d")] +#[case("a][c")] +fn du_files_with_glob_metachars(#[case] src_name: &str) { + Playground::setup("umv_test_16", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile(src_name)]); + + let src = dirs.test().join(src_name); + + let actual = nu!( + cwd: dirs.test(), + "du -d 1 '{}'", + src.display(), + ); + + assert!(actual.err.is_empty()); + }); +} + +#[cfg(not(windows))] +#[rstest] +#[case("a]?c")] +#[case("a*.?c")] +// windows doesn't allow filename with `*`. +fn du_files_with_glob_metachars_nw(#[case] src_name: &str) { + du_files_with_glob_metachars(src_name); +} diff --git a/crates/nu-command/tests/commands/rm.rs b/crates/nu-command/tests/commands/rm.rs index 2b9345e2f8..9d7da4fc03 100644 --- a/crates/nu-command/tests/commands/rm.rs +++ b/crates/nu-command/tests/commands/rm.rs @@ -42,7 +42,7 @@ fn removes_files_with_wildcard() { nu!( cwd: dirs.test(), - r#"rm "src/*/*/*.rs""# + r#"rm src/*/*/*.rs"# ); assert!(!files_exist_at( @@ -459,3 +459,24 @@ fn rm_prints_filenames_on_error() { } }); } + +#[test] +fn rm_files_inside_glob_metachars_dir() { + Playground::setup("rm_files_inside_glob_metachars_dir", |dirs, sandbox| { + let sub_dir = "test[]"; + sandbox + .within(sub_dir) + .with_files(vec![EmptyFile("test_file.txt")]); + + let actual = nu!( + cwd: dirs.test().join(sub_dir), + "rm test_file.txt", + ); + + assert!(actual.err.is_empty()); + assert!(!files_exist_at( + vec!["test_file.txt"], + dirs.test().join(sub_dir) + )); + }); +} diff --git a/crates/nu-command/tests/commands/ucp.rs b/crates/nu-command/tests/commands/ucp.rs index df8222bf84..1d94e72958 100644 --- a/crates/nu-command/tests/commands/ucp.rs +++ b/crates/nu-command/tests/commands/ucp.rs @@ -986,10 +986,10 @@ fn test_cp_destination_after_cd() { } #[rstest] -#[case(r#"'a]c'"#)] -#[case(r#"'a[c'"#)] -#[case(r#"'a[bc]d'"#)] -#[case(r#"'a][c'"#)] +#[case("a]c")] +#[case("a[c")] +#[case("a[bc]d")] +#[case("a][c")] fn copies_files_with_glob_metachars(#[case] src_name: &str) { Playground::setup("ucp_test_34", |dirs, sandbox| { sandbox.with_files(vec![FileWithContent( @@ -1005,7 +1005,7 @@ fn copies_files_with_glob_metachars(#[case] src_name: &str) { let actual = nu!( cwd: dirs.test(), - "cp {} {}", + "cp '{}' {}", src.display(), TEST_HELLO_WORLD_DEST ); @@ -1100,3 +1100,25 @@ fn test_cp_preserve_nothing() { assert_eq!(actual.out, "true"); }); } + +#[test] +fn test_cp_inside_glob_metachars_dir() { + Playground::setup("open_files_inside_glob_metachars_dir", |dirs, sandbox| { + let sub_dir = "test[]"; + sandbox + .within(sub_dir) + .with_files(vec![FileWithContent("test_file.txt", "hello")]); + + let actual = nu!( + cwd: dirs.test().join(sub_dir), + "cp test_file.txt ../", + ); + + assert!(actual.err.is_empty()); + assert!(files_exist_at( + vec!["test_file.txt"], + dirs.test().join(sub_dir) + )); + assert!(files_exist_at(vec!["test_file.txt"], dirs.test())); + }); +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 68add01f37..cfa319d3e8 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1151,23 +1151,6 @@ impl Eval for EvalRuntime { Ok(Value::string(name, span)) } - fn eval_glob_pattern( - engine_state: Self::State<'_>, - stack: &mut Self::MutState, - pattern: String, - quoted: bool, - span: Span, - ) -> Result { - 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)) - } - } - fn unreachable(expr: &Expression) -> Result { Ok(Value::nothing(expr.span)) } diff --git a/crates/nu-engine/src/glob_from.rs b/crates/nu-engine/src/glob_from.rs index 01dcaf8162..7a99234023 100644 --- a/crates/nu-engine/src/glob_from.rs +++ b/crates/nu-engine/src/glob_from.rs @@ -5,7 +5,7 @@ use std::{ use nu_glob::MatchOptions; use nu_path::{canonicalize_with, expand_path_with}; -use nu_protocol::{ShellError, Span, Spanned}; +use nu_protocol::{NuPath, ShellError, Span, Spanned}; const GLOB_CHARS: &[char] = &['*', '?', '[']; @@ -18,7 +18,7 @@ const GLOB_CHARS: &[char] = &['*', '?', '[']; /// The second of the two values is an iterator over the matching filepaths. #[allow(clippy::type_complexity)] pub fn glob_from( - pattern: &Spanned, + pattern: &Spanned, cwd: &Path, span: Span, options: Option, @@ -29,10 +29,11 @@ pub fn glob_from( ), ShellError, > { - let (prefix, pattern) = if pattern.item.contains(GLOB_CHARS) { + let no_glob_for_pattern = matches!(pattern.item, NuPath::Quoted(_)); + let (prefix, pattern) = if pattern.item.as_ref().contains(GLOB_CHARS) { // Pattern contains glob, split it let mut p = PathBuf::new(); - let path = PathBuf::from(&pattern.item); + let path = PathBuf::from(&pattern.item.as_ref()); let components = path.components(); let mut counter = 0; @@ -52,6 +53,9 @@ pub fn glob_from( just_pattern.push(comp); } } + if no_glob_for_pattern { + just_pattern = PathBuf::from(nu_glob::Pattern::escape(&just_pattern.to_string_lossy())); + } // Now expand `p` to get full prefix let path = expand_path_with(p, cwd); @@ -59,7 +63,7 @@ pub fn glob_from( (Some(path), escaped_prefix.join(just_pattern)) } else { - let path = PathBuf::from(&pattern.item); + let path = PathBuf::from(&pattern.item.as_ref()); let path = expand_path_with(path, cwd); let is_symlink = match fs::symlink_metadata(&path) { Ok(attr) => attr.file_type().is_symlink(), @@ -70,7 +74,14 @@ pub fn glob_from( (path.parent().map(|parent| parent.to_path_buf()), path) } else { let path = if let Ok(p) = canonicalize_with(path.clone(), cwd) { - p + if p.to_string_lossy().contains(GLOB_CHARS) { + // our path might contains GLOB_CHARS too + // in such case, we need to escape our path to make + // glob work successfully + PathBuf::from(nu_glob::Pattern::escape(&p.to_string_lossy())) + } else { + p + } } else { return Err(ShellError::DirectoryNotFound { dir: path.to_string_lossy().to_string(), diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 0d585a982a..19bcfab54a 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -370,9 +370,6 @@ pub fn flatten_expression( Expr::GlobPattern(_, _) => { vec![(expr.span, FlatShape::GlobPattern)] } - Expr::LsGlobPattern(_, _) => { - vec![(expr.span, FlatShape::GlobPattern)] - } Expr::List(list) => { let outer_span = expr.span; let mut last_end = outer_span.start; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index a89b0dfdaa..eed3795f5f 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2489,28 +2489,6 @@ pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expr } } -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, - } - } else { - working_set.error(ParseError::Expected("glob pattern string", span)); - - garbage(span) - } -} - pub fn unescape_string(bytes: &[u8], span: Span) -> (Vec, Option) { let mut output = Vec::new(); let mut error = None; @@ -4575,7 +4553,7 @@ pub fn parse_value( | SyntaxShape::Signature | SyntaxShape::Filepath | SyntaxShape::String - | SyntaxShape::LsGlobPattern => {} + | SyntaxShape::GlobPattern => {} _ => { working_set.error(ParseError::Expected("non-[] value", span)); return Expression::garbage(span); @@ -4600,7 +4578,6 @@ 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 => { @@ -6004,7 +5981,6 @@ pub fn discover_captures_in_expr( Expr::Garbage => {} Expr::Nothing => {} 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 e46e88fba2..409b5b85b6 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -40,7 +40,6 @@ pub enum Expr { 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 3f30ac6461..c1f4c617e1 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -209,7 +209,6 @@ impl Expression { Expr::Garbage => false, Expr::Nothing => false, Expr::GlobPattern(_, _) => false, - Expr::LsGlobPattern(_, _) => false, Expr::Int(_) => false, Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set), Expr::List(list) => { @@ -389,7 +388,6 @@ impl Expression { Expr::Garbage => {} Expr::Nothing => {} 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 ca01f00056..f44aab6d74 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -286,9 +286,8 @@ pub trait Eval { } Expr::Overlay(_) => Self::eval_overlay(state, expr.span), Expr::GlobPattern(pattern, quoted) => { - Self::eval_glob_pattern(state, mut_state, pattern.clone(), *quoted, expr.span) - } - Expr::LsGlobPattern(pattern, quoted) => { + // GlobPattern is similar to Filepath + // But we don't want to expand path during eval time, it's required for `nu_engine::glob_from` to run correctly if *quoted { Ok(Value::quoted_string(pattern, expr.span)) } else { @@ -381,14 +380,6 @@ pub trait Eval { fn eval_overlay(state: Self::State<'_>, span: Span) -> Result; - fn eval_glob_pattern( - state: Self::State<'_>, - mut_state: &mut Self::MutState, - pattern: String, - quoted: bool, - span: Span, - ) -> Result; - /// For expressions that should never actually be evaluated fn unreachable(expr: &Expression) -> Result; } diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 995a806c28..b128ec29e6 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -388,16 +388,6 @@ impl Eval for EvalConst { Err(ShellError::NotAConstant { span }) } - fn eval_glob_pattern( - _: &StateWorkingSet, - _: &mut (), - _: String, - _: bool, - span: Span, - ) -> Result { - Err(ShellError::NotAConstant { span }) - } - fn unreachable(expr: &Expression) -> Result { Err(ShellError::NotAConstant { span: expr.span }) } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 25c1d0d86f..2fe0b94acf 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -1264,23 +1264,6 @@ This is an internal Nushell error, please file an issue https://github.com/nushe span: Span, }, - /// Error expanding glob pattern - /// - /// ## Resolution - /// - /// Correct glob pattern or file access issue - #[error("Error expanding glob pattern")] - #[diagnostic( - code(nu::shell::error_expanding_glob), - help("Correct glob pattern or file access issue") - )] - //todo: add error detail - ErrorExpandingGlob { - msg: String, - #[label("{msg}")] - span: Span, - }, - /// Tried spreading a non-list inside a list or command call. /// /// ## Resolution diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 53e29d4777..a95e5fcfc9 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -64,9 +64,6 @@ 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, @@ -154,7 +151,6 @@ 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, @@ -205,7 +201,6 @@ 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/path.rs b/crates/nu-protocol/src/value/path.rs index 9c18b68565..3764637c3f 100644 --- a/crates/nu-protocol/src/value/path.rs +++ b/crates/nu-protocol/src/value/path.rs @@ -1,8 +1,11 @@ +use serde::Deserialize; +use std::fmt::Display; + /// 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)] +#[derive(Debug, Clone, Deserialize)] pub enum NuPath { /// A quoted path(except backtick), in this case, nushell shouldn't auto-expand path. Quoted(String), @@ -10,6 +13,15 @@ pub enum NuPath { UnQuoted(String), } +impl NuPath { + pub fn strip_ansi_string_unlikely(self) -> Self { + match self { + NuPath::Quoted(s) => NuPath::Quoted(nu_utils::strip_ansi_string_unlikely(s)), + NuPath::UnQuoted(s) => NuPath::UnQuoted(nu_utils::strip_ansi_string_unlikely(s)), + } + } +} + impl AsRef for NuPath { fn as_ref(&self) -> &str { match self { @@ -17,3 +29,9 @@ impl AsRef for NuPath { } } } + +impl Display for NuPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_ref()) + } +}