mirror of
https://github.com/nushell/nushell.git
synced 2025-02-16 18:41:44 +01:00
Unify glob behavior on open
, rm
, cp-old
, mv
, umv
, cp
and du
commands (#11621)
# Description This pr is a follow up to [#11569](https://github.com/nushell/nushell/pull/11569#issuecomment-1902279587) > Revert the logic in https://github.com/nushell/nushell/pull/10694 and apply the logic in this pr to mv, cp, rv will require a larger change, I need to think how to achieve the bahavior And sorry @bobhy for reverting some of your changes. This pr is going to unify glob behavior on the given commands: * open * rm * cp-old * mv * umv * cp * du So they have the same behavior to `ls`, which is: If given parameter is quoted by single quote(`'`) or double quote(`"`), don't auto-expand the glob pattern. If not quoted, auto-expand the glob pattern. Fixes: #9558 Fixes: #10211 Fixes: #9310 Fixes: #10364 # TODO But there is one thing remains: if we give a variable to the command, it will always auto-expand the glob pattern, e.g: ```nushell let path = "a[123]b" rm $path ``` I don't think it's expected. But I also think user might want to auto-expand the glob pattern in variables. So I'll introduce a new command called `glob escape`, then if user doesn't want to auto-expand the glob pattern, he can just do this: `rm ($path | glob escape)` # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # Tests + Formatting Done # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. --> ## NOTE This pr changes the semantic of `GlobPattern`, before this pr, it will `expand path` after evaluated, this makes `nu_engine::glob_from` have no chance to glob things right if a path contains glob pattern. e.g: [#9310 ](https://github.com/nushell/nushell/issues/9310#issuecomment-1886824030) #10211 I think changing the semantic is fine, because it makes glob works if path contains something like '*'. It maybe a breaking change if a custom command's argument are annotated by `: glob`.
This commit is contained in:
parent
e43d893ea3
commit
d646903161
@ -336,7 +336,6 @@ fn find_matching_block_end_in_expr(
|
|||||||
Expr::Filepath(_, _) => None,
|
Expr::Filepath(_, _) => None,
|
||||||
Expr::Directory(_, _) => None,
|
Expr::Directory(_, _) => None,
|
||||||
Expr::GlobPattern(_, _) => None,
|
Expr::GlobPattern(_, _) => None,
|
||||||
Expr::LsGlobPattern(_, _) => None,
|
|
||||||
Expr::String(_) => None,
|
Expr::String(_) => None,
|
||||||
Expr::CellPath(_) => None,
|
Expr::CellPath(_) => None,
|
||||||
Expr::ImportPattern(_) => None,
|
Expr::ImportPattern(_) => None,
|
||||||
|
@ -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<String>, // alleged path or glob
|
|
||||||
cwd: &Path, // current working directory
|
|
||||||
) -> Result<Paths, ShellError> {
|
|
||||||
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<String>, cwd: &Path) -> Result<Paths, ShellError> {
|
|
||||||
arg_glob_opt(
|
|
||||||
pattern,
|
|
||||||
cwd,
|
|
||||||
MatchOptions {
|
|
||||||
require_literal_leading_dot: true,
|
|
||||||
..GLOB_PARAMS
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arg_glob_opt(
|
|
||||||
pattern: &Spanned<String>,
|
|
||||||
cwd: &Path,
|
|
||||||
options: MatchOptions,
|
|
||||||
) -> Result<Paths, ShellError> {
|
|
||||||
// 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<String> {
|
|
||||||
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<GlobResult> = 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::<Vec<GlobResult>>();
|
|
||||||
|
|
||||||
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"));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,4 @@
|
|||||||
mod arg_glob;
|
|
||||||
pub mod formats;
|
pub mod formats;
|
||||||
pub mod hook;
|
pub mod hook;
|
||||||
pub mod input_handler;
|
pub mod input_handler;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub use arg_glob::arg_glob;
|
|
||||||
pub use arg_glob::arg_glob_leading_dot;
|
|
||||||
|
@ -41,7 +41,7 @@ impl Command for Ls {
|
|||||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||||
// LsGlobPattern is similar to string, it won't auto-expand
|
// LsGlobPattern is similar to string, it won't auto-expand
|
||||||
// and we use it to track if the user input is quoted.
|
// 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("all", "Show hidden files", Some('a'))
|
||||||
.switch(
|
.switch(
|
||||||
"long",
|
"long",
|
||||||
@ -165,7 +165,8 @@ impl Command for Ls {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let hidden_dir_specified = is_hidden_dir(&path);
|
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`
|
// so we can do ls for a file or directory like `a[123]b`
|
||||||
let path = if quoted {
|
let path = if quoted {
|
||||||
let p = path.display().to_string();
|
let p = path.display().to_string();
|
||||||
@ -185,7 +186,8 @@ impl Command for Ls {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let glob_path = Spanned {
|
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,
|
span: p_tag,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use super::util::try_interaction;
|
use super::util::try_interaction;
|
||||||
use nu_cmd_base::arg_glob;
|
|
||||||
use nu_engine::env::current_dir;
|
use nu_engine::env::current_dir;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature,
|
||||||
Spanned, SyntaxShape, Type, Value,
|
Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -63,7 +62,8 @@ impl Command for Mv {
|
|||||||
_input: PipelineData,
|
_input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
// TODO: handle invalid directory or insufficient permissions when moving
|
// TODO: handle invalid directory or insufficient permissions when moving
|
||||||
let spanned_source: Spanned<String> = call.req(engine_state, stack, 0)?;
|
let mut spanned_source: Spanned<NuPath> = call.req(engine_state, stack, 0)?;
|
||||||
|
spanned_source.item = spanned_source.item.strip_ansi_string_unlikely();
|
||||||
let spanned_destination: Spanned<String> = call.req(engine_state, stack, 1)?;
|
let spanned_destination: Spanned<String> = call.req(engine_state, stack, 1)?;
|
||||||
let verbose = call.has_flag(engine_state, stack, "verbose")?;
|
let verbose = call.has_flag(engine_state, stack, "verbose")?;
|
||||||
let interactive = call.has_flag(engine_state, stack, "interactive")?;
|
let interactive = call.has_flag(engine_state, stack, "interactive")?;
|
||||||
@ -73,11 +73,11 @@ impl Command for Mv {
|
|||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
let path = current_dir(engine_state, stack)?;
|
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 destination = path.join(spanned_destination.item.as_str());
|
||||||
|
|
||||||
let mut sources =
|
let mut sources = nu_engine::glob_from(&spanned_source, &path, call.head, None)
|
||||||
arg_glob(&spanned_source, &path).map_or_else(|_| Vec::new(), Iterator::collect);
|
.map(|p| p.1)
|
||||||
|
.map_or_else(|_| Vec::new(), Iterator::collect);
|
||||||
|
|
||||||
if sources.is_empty() {
|
if sources.is_empty() {
|
||||||
return Err(ShellError::FileNotFound {
|
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
|
// Second, the destination doesn't exist, so we can only rename a single source. Otherwise
|
||||||
// it's an error.
|
// it's an error.
|
||||||
|
let source = path.join(spanned_source.item.as_ref());
|
||||||
if destination.exists() && !force && !destination.is_dir() && !source.is_dir() {
|
if destination.exists() && !force && !destination.is_dir() && !source.is_dir() {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: "Destination file already exists".into(),
|
error: "Destination file already exists".into(),
|
||||||
|
@ -4,8 +4,8 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::util::BufferedReader;
|
use nu_protocol::util::BufferedReader;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, DataSource, Example, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata,
|
Category, DataSource, Example, IntoInterruptiblePipelineData, NuPath, PipelineData,
|
||||||
RawStream, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
PipelineMetadata, RawStream, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
|
||||||
@ -42,10 +42,10 @@ impl Command for Open {
|
|||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("open")
|
Signature::build("open")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Any), (Type::String, Type::Any)])
|
.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(
|
.rest(
|
||||||
"filenames",
|
"filenames",
|
||||||
SyntaxShape::Filepath,
|
SyntaxShape::GlobPattern,
|
||||||
"Optional additional files to open.",
|
"Optional additional files to open.",
|
||||||
)
|
)
|
||||||
.switch("raw", "open file as raw binary", Some('r'))
|
.switch("raw", "open file as raw binary", Some('r'))
|
||||||
@ -63,8 +63,8 @@ impl Command for Open {
|
|||||||
let call_span = call.head;
|
let call_span = call.head;
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
let cwd = current_dir(engine_state, stack)?;
|
let cwd = current_dir(engine_state, stack)?;
|
||||||
let req_path = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
|
let req_path = call.opt::<Spanned<NuPath>>(engine_state, stack, 0)?;
|
||||||
let mut path_params = call.rest::<Spanned<String>>(engine_state, stack, 1)?;
|
let mut path_params = call.rest::<Spanned<NuPath>>(engine_state, stack, 1)?;
|
||||||
|
|
||||||
// FIXME: JT: what is this doing here?
|
// 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![];
|
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
|
//FIXME: `open` should not have to do this
|
||||||
let path = {
|
path.item = path.item.strip_ansi_string_unlikely();
|
||||||
Spanned {
|
|
||||||
item: nu_utils::strip_ansi_string_unlikely(path.item),
|
|
||||||
span: path.span,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let arg_span = path.span;
|
let arg_span = path.span;
|
||||||
// let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
// let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
||||||
|
@ -7,14 +7,15 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use super::util::try_interaction;
|
use super::util::try_interaction;
|
||||||
|
|
||||||
use nu_cmd_base::arg_glob_leading_dot;
|
|
||||||
use nu_engine::env::current_dir;
|
use nu_engine::env::current_dir;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
|
use nu_glob::MatchOptions;
|
||||||
|
use nu_path::expand_path_with;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature,
|
||||||
Spanned, SyntaxShape, Type, Value,
|
Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TRASH_SUPPORTED: bool = cfg!(all(
|
const TRASH_SUPPORTED: bool = cfg!(all(
|
||||||
@ -134,7 +135,7 @@ fn rm(
|
|||||||
|
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
let mut targets: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let mut targets: Vec<Spanned<NuPath>> = call.rest(engine_state, stack, 0)?;
|
||||||
|
|
||||||
let mut unique_argument_check = None;
|
let mut unique_argument_check = None;
|
||||||
|
|
||||||
@ -157,12 +158,19 @@ fn rm(
|
|||||||
|
|
||||||
for (idx, path) in targets.clone().into_iter().enumerate() {
|
for (idx, path) in targets.clone().into_iter().enumerate() {
|
||||||
if let Some(ref home) = home {
|
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);
|
unique_argument_check = Some(path.span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let corrected_path = Spanned {
|
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,
|
span: path.span,
|
||||||
};
|
};
|
||||||
let _ = std::mem::replace(&mut targets[idx], corrected_path);
|
let _ = std::mem::replace(&mut targets[idx], corrected_path);
|
||||||
@ -233,7 +241,8 @@ fn rm(
|
|||||||
let mut all_targets: HashMap<PathBuf, Span> = HashMap::new();
|
let mut all_targets: HashMap<PathBuf, Span> = HashMap::new();
|
||||||
|
|
||||||
for target in targets {
|
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))
|
|| currentdir_path.starts_with(format!("{}{}", target.item, std::path::MAIN_SEPARATOR))
|
||||||
{
|
{
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
@ -245,10 +254,18 @@ fn rm(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = currentdir_path.join(&target.item);
|
// let path = currentdir_path.join(target.item.as_ref());
|
||||||
match arg_glob_leading_dot(&target, ¤tdir_path) {
|
match nu_engine::glob_from(
|
||||||
|
&target,
|
||||||
|
¤tdir_path,
|
||||||
|
call.head,
|
||||||
|
Some(MatchOptions {
|
||||||
|
require_literal_leading_dot: true,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
) {
|
||||||
Ok(files) => {
|
Ok(files) => {
|
||||||
for file in files {
|
for file in files.1 {
|
||||||
match file {
|
match file {
|
||||||
Ok(f) => {
|
Ok(f) => {
|
||||||
if !target_exists {
|
if !target_exists {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use nu_cmd_base::arg_glob;
|
|
||||||
use nu_engine::{current_dir, CallExt};
|
use nu_engine::{current_dir, CallExt};
|
||||||
use nu_glob::GlobResult;
|
use nu_protocol::NuPath;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
@ -62,7 +61,7 @@ impl Command for UCp {
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
.switch("debug", "explain how a file is copied. Implies -v", 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)
|
.allow_variants_without_examples(true)
|
||||||
.category(Category::FileSystem)
|
.category(Category::FileSystem)
|
||||||
}
|
}
|
||||||
@ -146,14 +145,7 @@ impl Command for UCp {
|
|||||||
let reflink_mode = uu_cp::ReflinkMode::Auto;
|
let reflink_mode = uu_cp::ReflinkMode::Auto;
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
|
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
|
||||||
let reflink_mode = uu_cp::ReflinkMode::Never;
|
let reflink_mode = uu_cp::ReflinkMode::Never;
|
||||||
let paths: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let mut paths: Vec<Spanned<NuPath>> = call.rest(engine_state, stack, 0)?;
|
||||||
let mut paths: Vec<Spanned<String>> = paths
|
|
||||||
.into_iter()
|
|
||||||
.map(|p| Spanned {
|
|
||||||
item: nu_utils::strip_ansi_string_unlikely(p.item),
|
|
||||||
span: p.span,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: "Missing file operand".into(),
|
error: "Missing file operand".into(),
|
||||||
@ -167,15 +159,20 @@ impl Command for UCp {
|
|||||||
if paths.len() == 1 {
|
if paths.len() == 1 {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: "Missing destination path".into(),
|
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),
|
span: Some(paths[0].span),
|
||||||
help: None,
|
help: None,
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let target = paths.pop().expect("Should not be reached?");
|
let target = paths.pop().expect("Should not be reached?");
|
||||||
let target_path = PathBuf::from(&target.item);
|
let target_path = PathBuf::from(&nu_utils::strip_ansi_string_unlikely(
|
||||||
if target.item.ends_with(PATH_SEPARATOR) && !target_path.is_dir() {
|
target.item.to_string(),
|
||||||
|
));
|
||||||
|
if target.item.as_ref().ends_with(PATH_SEPARATOR) && !target_path.is_dir() {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: "is not a directory".into(),
|
error: "is not a directory".into(),
|
||||||
msg: "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 cwd = current_dir(engine_state, stack)?;
|
||||||
let mut sources: Vec<PathBuf> = Vec::new();
|
let mut sources: Vec<PathBuf> = Vec::new();
|
||||||
|
|
||||||
for p in paths {
|
for mut p in paths {
|
||||||
let exp_files = arg_glob(&p, &cwd)?.collect::<Vec<GlobResult>>();
|
p.item = p.item.strip_ansi_string_unlikely();
|
||||||
|
let exp_files: Vec<Result<PathBuf, ShellError>> =
|
||||||
|
nu_engine::glob_from(&p, &cwd, call.head, None)
|
||||||
|
.map(|f| f.1)?
|
||||||
|
.collect();
|
||||||
if exp_files.is_empty() {
|
if exp_files.is_empty() {
|
||||||
return Err(ShellError::FileNotFound { span: p.span });
|
return Err(ShellError::FileNotFound { span: p.span });
|
||||||
};
|
};
|
||||||
@ -212,12 +213,7 @@ impl Command for UCp {
|
|||||||
};
|
};
|
||||||
app_vals.push(path)
|
app_vals.push(path)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => return Err(e),
|
||||||
return Err(ShellError::ErrorExpandingGlob {
|
|
||||||
msg: format!("error {} in path {}", e.error(), e.path().display()),
|
|
||||||
span: p.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sources.append(&mut app_vals);
|
sources.append(&mut app_vals);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
use nu_cmd_base::arg_glob;
|
|
||||||
use nu_engine::current_dir;
|
use nu_engine::current_dir;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_glob::GlobResult;
|
|
||||||
use nu_path::{expand_path_with, expand_to_real_path};
|
use nu_path::{expand_path_with, expand_to_real_path};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{
|
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::ffi::OsString;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -58,7 +56,7 @@ impl Command for UMv {
|
|||||||
.switch("no-clobber", "do not overwrite an existing file", Some('n'))
|
.switch("no-clobber", "do not overwrite an existing file", Some('n'))
|
||||||
.rest(
|
.rest(
|
||||||
"paths",
|
"paths",
|
||||||
SyntaxShape::Filepath,
|
SyntaxShape::GlobPattern,
|
||||||
"Rename SRC to DST, or move SRC to DIR.",
|
"Rename SRC to DST, or move SRC to DIR.",
|
||||||
)
|
)
|
||||||
.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
@ -84,14 +82,8 @@ impl Command for UMv {
|
|||||||
uu_mv::OverwriteMode::Force
|
uu_mv::OverwriteMode::Force
|
||||||
};
|
};
|
||||||
|
|
||||||
let paths: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
let cwd = current_dir(engine_state, stack)?;
|
||||||
let paths: Vec<Spanned<String>> = paths
|
let mut paths: Vec<Spanned<NuPath>> = call.rest(engine_state, stack, 0)?;
|
||||||
.into_iter()
|
|
||||||
.map(|p| Spanned {
|
|
||||||
item: nu_utils::strip_ansi_string_unlikely(p.item),
|
|
||||||
span: p.span,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: "Missing file operand".into(),
|
error: "Missing file operand".into(),
|
||||||
@ -102,9 +94,13 @@ impl Command for UMv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if paths.len() == 1 {
|
if paths.len() == 1 {
|
||||||
|
// expand path for better error message
|
||||||
return Err(ShellError::GenericError {
|
return Err(ShellError::GenericError {
|
||||||
error: "Missing destination path".into(),
|
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),
|
span: Some(paths[0].span),
|
||||||
help: None,
|
help: None,
|
||||||
inner: Vec::new(),
|
inner: Vec::new(),
|
||||||
@ -112,11 +108,18 @@ impl Command for UMv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do not glob target
|
// Do not glob target
|
||||||
let sources = &paths[..paths.len() - 1];
|
let spanned_target = paths.pop().ok_or(ShellError::NushellFailedSpanned {
|
||||||
let cwd = current_dir(engine_state, stack)?;
|
msg: "Missing file operand".into(),
|
||||||
|
label: "Missing file operand".into(),
|
||||||
|
span: call.head,
|
||||||
|
})?;
|
||||||
let mut files: Vec<PathBuf> = Vec::new();
|
let mut files: Vec<PathBuf> = Vec::new();
|
||||||
for p in sources {
|
for mut p in paths {
|
||||||
let exp_files = arg_glob(p, &cwd)?.collect::<Vec<GlobResult>>();
|
p.item = p.item.strip_ansi_string_unlikely();
|
||||||
|
let exp_files: Vec<Result<PathBuf, ShellError>> =
|
||||||
|
nu_engine::glob_from(&p, &cwd, call.head, None)
|
||||||
|
.map(|f| f.1)?
|
||||||
|
.collect();
|
||||||
if exp_files.is_empty() {
|
if exp_files.is_empty() {
|
||||||
return Err(ShellError::FileNotFound { span: p.span });
|
return Err(ShellError::FileNotFound { span: p.span });
|
||||||
};
|
};
|
||||||
@ -126,12 +129,7 @@ impl Command for UMv {
|
|||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
app_vals.push(path);
|
app_vals.push(path);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => return Err(e),
|
||||||
return Err(ShellError::ErrorExpandingGlob {
|
|
||||||
msg: format!("error {} in path {}", e.error(), e.path().display()),
|
|
||||||
span: p.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
files.append(&mut app_vals);
|
files.append(&mut app_vals);
|
||||||
@ -146,12 +144,9 @@ impl Command for UMv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add back the target after globbing
|
// Add back the target after globbing
|
||||||
let spanned_target = paths.last().ok_or(ShellError::NushellFailedSpanned {
|
let expanded_target = expand_to_real_path(nu_utils::strip_ansi_string_unlikely(
|
||||||
msg: "Missing file operand".into(),
|
spanned_target.item.to_string(),
|
||||||
label: "Missing file operand".into(),
|
));
|
||||||
span: call.head,
|
|
||||||
})?;
|
|
||||||
let expanded_target = expand_to_real_path(spanned_target.item.clone());
|
|
||||||
let abs_target_path = expand_path_with(expanded_target, &cwd);
|
let abs_target_path = expand_path_with(expanded_target, &cwd);
|
||||||
files.push(abs_target_path.clone());
|
files.push(abs_target_path.clone());
|
||||||
let files = files
|
let files = files
|
||||||
|
@ -243,7 +243,6 @@ fn convert_to_value(
|
|||||||
span: expr.span,
|
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 {
|
Expr::ImportPattern(..) => Err(ShellError::OutsideSpannedLabeledError {
|
||||||
src: original_text.to_string(),
|
src: original_text.to_string(),
|
||||||
error: "Error when loading".into(),
|
error: "Error when loading".into(),
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
use crate::{DirBuilder, DirInfo, FileInfo};
|
use crate::{DirBuilder, DirInfo, FileInfo};
|
||||||
use nu_cmd_base::arg_glob;
|
|
||||||
use nu_engine::{current_dir, CallExt};
|
use nu_engine::{current_dir, CallExt};
|
||||||
use nu_glob::{GlobError, Pattern};
|
use nu_glob::Pattern;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
Category, Example, IntoInterruptiblePipelineData, NuPath, PipelineData, ShellError, Signature,
|
||||||
Spanned, SyntaxShape, Type, Value,
|
Span, Spanned, SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
@ -15,7 +14,7 @@ pub struct Du;
|
|||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
pub struct DuArgs {
|
pub struct DuArgs {
|
||||||
path: Option<Spanned<String>>,
|
path: Option<Spanned<NuPath>>,
|
||||||
all: bool,
|
all: bool,
|
||||||
deref: bool,
|
deref: bool,
|
||||||
exclude: Option<Spanned<String>>,
|
exclude: Option<Spanned<String>>,
|
||||||
@ -116,28 +115,26 @@ impl Command for Du {
|
|||||||
|
|
||||||
let include_files = args.all;
|
let include_files = args.all;
|
||||||
let mut paths = match args.path {
|
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.
|
// The * pattern should never fail.
|
||||||
None => arg_glob(
|
None => nu_engine::glob_from(
|
||||||
&Spanned {
|
&Spanned {
|
||||||
item: "*".into(),
|
item: NuPath::UnQuoted("*".into()),
|
||||||
span: Span::unknown(),
|
span: Span::unknown(),
|
||||||
},
|
},
|
||||||
¤t_dir,
|
¤t_dir,
|
||||||
)?,
|
call.head,
|
||||||
|
None,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
.map(|f| f.1)?
|
||||||
.filter(move |p| {
|
.filter(move |p| {
|
||||||
if include_files {
|
if include_files {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
match p {
|
matches!(p, Ok(f) if f.is_dir())
|
||||||
Ok(f) if f.is_dir() => true,
|
|
||||||
Err(e) if e.path().is_dir() => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.map(|v| v.map_err(glob_err_into));
|
|
||||||
|
|
||||||
let all = args.all;
|
let all = args.all;
|
||||||
let deref = args.deref;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Du;
|
use super::Du;
|
||||||
|
@ -2,6 +2,7 @@ use nu_cmd_base::hook::eval_hook;
|
|||||||
use nu_engine::env_to_strings;
|
use nu_engine::env_to_strings;
|
||||||
use nu_engine::eval_expression;
|
use nu_engine::eval_expression;
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::NuPath;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, Expr},
|
ast::{Call, Expr},
|
||||||
did_you_mean,
|
did_you_mean,
|
||||||
@ -735,7 +736,12 @@ fn trim_expand_and_apply_arg(
|
|||||||
}
|
}
|
||||||
let cwd = PathBuf::from(cwd);
|
let cwd = PathBuf::from(cwd);
|
||||||
if arg.item.contains('*') && run_glob_expansion {
|
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();
|
let matches: Vec<_> = matches.collect();
|
||||||
|
|
||||||
// FIXME: do we want to special-case this further? We might accidentally expand when they don't
|
// FIXME: do we want to special-case this further? We might accidentally expand when they don't
|
||||||
|
@ -204,7 +204,7 @@ fn errors_if_source_doesnt_exist() {
|
|||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"umv non-existing-file test_folder/"
|
"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]
|
#[rstest]
|
||||||
#[case(r#"'a]c'"#)]
|
#[case("a]c")]
|
||||||
#[case(r#"'a[c'"#)]
|
#[case("a[c")]
|
||||||
#[case(r#"'a[bc]d'"#)]
|
#[case("a[bc]d")]
|
||||||
#[case(r#"'a][c'"#)]
|
#[case("a][c")]
|
||||||
fn mv_files_with_glob_metachars(#[case] src_name: &str) {
|
fn mv_files_with_glob_metachars(#[case] src_name: &str) {
|
||||||
Playground::setup("umv_test_16", |dirs, sandbox| {
|
Playground::setup("umv_test_16", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContent(
|
sandbox.with_files(vec![FileWithContent(
|
||||||
@ -574,7 +574,7 @@ fn mv_files_with_glob_metachars(#[case] src_name: &str) {
|
|||||||
|
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"umv {} {}",
|
"umv '{}' {}",
|
||||||
src.display(),
|
src.display(),
|
||||||
"hello_world_dest"
|
"hello_world_dest"
|
||||||
);
|
);
|
||||||
@ -586,8 +586,8 @@ fn mv_files_with_glob_metachars(#[case] src_name: &str) {
|
|||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(r#"'a]?c'"#)]
|
#[case("a]?c")]
|
||||||
#[case(r#"'a*.?c'"#)]
|
#[case("a*.?c")]
|
||||||
// windows doesn't allow filename with `*`.
|
// windows doesn't allow filename with `*`.
|
||||||
fn mv_files_with_glob_metachars_nw(#[case] src_name: &str) {
|
fn mv_files_with_glob_metachars_nw(#[case] src_name: &str) {
|
||||||
mv_files_with_glob_metachars(src_name);
|
mv_files_with_glob_metachars(src_name);
|
||||||
@ -607,3 +607,25 @@ fn mv_with_cd() {
|
|||||||
assert!(actual.out.contains("body"));
|
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()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ use nu_test_support::fs::Stub::FileWithContent;
|
|||||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||||
use nu_test_support::playground::Playground;
|
use nu_test_support::playground::Playground;
|
||||||
use nu_test_support::{nu, pipeline};
|
use nu_test_support::{nu, pipeline};
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_file_with_uppercase_extension() {
|
fn parses_file_with_uppercase_extension() {
|
||||||
@ -336,3 +337,52 @@ fn open_no_parameter() {
|
|||||||
|
|
||||||
assert!(actual.err.contains("needs filename"));
|
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"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -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]
|
#[test]
|
||||||
fn test_du_flag_min_size() {
|
fn test_du_flag_min_size() {
|
||||||
@ -41,3 +43,33 @@ fn test_du_flag_max_depth() {
|
|||||||
));
|
));
|
||||||
assert!(actual.err.is_empty());
|
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);
|
||||||
|
}
|
||||||
|
@ -42,7 +42,7 @@ fn removes_files_with_wildcard() {
|
|||||||
|
|
||||||
nu!(
|
nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
r#"rm "src/*/*/*.rs""#
|
r#"rm src/*/*/*.rs"#
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(!files_exist_at(
|
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)
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -986,10 +986,10 @@ fn test_cp_destination_after_cd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(r#"'a]c'"#)]
|
#[case("a]c")]
|
||||||
#[case(r#"'a[c'"#)]
|
#[case("a[c")]
|
||||||
#[case(r#"'a[bc]d'"#)]
|
#[case("a[bc]d")]
|
||||||
#[case(r#"'a][c'"#)]
|
#[case("a][c")]
|
||||||
fn copies_files_with_glob_metachars(#[case] src_name: &str) {
|
fn copies_files_with_glob_metachars(#[case] src_name: &str) {
|
||||||
Playground::setup("ucp_test_34", |dirs, sandbox| {
|
Playground::setup("ucp_test_34", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContent(
|
sandbox.with_files(vec![FileWithContent(
|
||||||
@ -1005,7 +1005,7 @@ fn copies_files_with_glob_metachars(#[case] src_name: &str) {
|
|||||||
|
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: dirs.test(),
|
cwd: dirs.test(),
|
||||||
"cp {} {}",
|
"cp '{}' {}",
|
||||||
src.display(),
|
src.display(),
|
||||||
TEST_HELLO_WORLD_DEST
|
TEST_HELLO_WORLD_DEST
|
||||||
);
|
);
|
||||||
@ -1100,3 +1100,25 @@ fn test_cp_preserve_nothing() {
|
|||||||
assert_eq!(actual.out, "true");
|
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()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1151,23 +1151,6 @@ impl Eval for EvalRuntime {
|
|||||||
Ok(Value::string(name, span))
|
Ok(Value::string(name, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_glob_pattern(
|
|
||||||
engine_state: Self::State<'_>,
|
|
||||||
stack: &mut Self::MutState,
|
|
||||||
pattern: String,
|
|
||||||
quoted: bool,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<Value, ShellError> {
|
|
||||||
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<Value, ShellError> {
|
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
||||||
Ok(Value::nothing(expr.span))
|
Ok(Value::nothing(expr.span))
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use std::{
|
|||||||
|
|
||||||
use nu_glob::MatchOptions;
|
use nu_glob::MatchOptions;
|
||||||
use nu_path::{canonicalize_with, expand_path_with};
|
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] = &['*', '?', '['];
|
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.
|
/// The second of the two values is an iterator over the matching filepaths.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn glob_from(
|
pub fn glob_from(
|
||||||
pattern: &Spanned<String>,
|
pattern: &Spanned<NuPath>,
|
||||||
cwd: &Path,
|
cwd: &Path,
|
||||||
span: Span,
|
span: Span,
|
||||||
options: Option<MatchOptions>,
|
options: Option<MatchOptions>,
|
||||||
@ -29,10 +29,11 @@ pub fn glob_from(
|
|||||||
),
|
),
|
||||||
ShellError,
|
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
|
// Pattern contains glob, split it
|
||||||
let mut p = PathBuf::new();
|
let mut p = PathBuf::new();
|
||||||
let path = PathBuf::from(&pattern.item);
|
let path = PathBuf::from(&pattern.item.as_ref());
|
||||||
let components = path.components();
|
let components = path.components();
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
|
|
||||||
@ -52,6 +53,9 @@ pub fn glob_from(
|
|||||||
just_pattern.push(comp);
|
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
|
// Now expand `p` to get full prefix
|
||||||
let path = expand_path_with(p, cwd);
|
let path = expand_path_with(p, cwd);
|
||||||
@ -59,7 +63,7 @@ pub fn glob_from(
|
|||||||
|
|
||||||
(Some(path), escaped_prefix.join(just_pattern))
|
(Some(path), escaped_prefix.join(just_pattern))
|
||||||
} else {
|
} else {
|
||||||
let path = PathBuf::from(&pattern.item);
|
let path = PathBuf::from(&pattern.item.as_ref());
|
||||||
let path = expand_path_with(path, cwd);
|
let path = expand_path_with(path, cwd);
|
||||||
let is_symlink = match fs::symlink_metadata(&path) {
|
let is_symlink = match fs::symlink_metadata(&path) {
|
||||||
Ok(attr) => attr.file_type().is_symlink(),
|
Ok(attr) => attr.file_type().is_symlink(),
|
||||||
@ -70,7 +74,14 @@ pub fn glob_from(
|
|||||||
(path.parent().map(|parent| parent.to_path_buf()), path)
|
(path.parent().map(|parent| parent.to_path_buf()), path)
|
||||||
} else {
|
} else {
|
||||||
let path = if let Ok(p) = canonicalize_with(path.clone(), cwd) {
|
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 {
|
} else {
|
||||||
return Err(ShellError::DirectoryNotFound {
|
return Err(ShellError::DirectoryNotFound {
|
||||||
dir: path.to_string_lossy().to_string(),
|
dir: path.to_string_lossy().to_string(),
|
||||||
|
@ -370,9 +370,6 @@ pub fn flatten_expression(
|
|||||||
Expr::GlobPattern(_, _) => {
|
Expr::GlobPattern(_, _) => {
|
||||||
vec![(expr.span, FlatShape::GlobPattern)]
|
vec![(expr.span, FlatShape::GlobPattern)]
|
||||||
}
|
}
|
||||||
Expr::LsGlobPattern(_, _) => {
|
|
||||||
vec![(expr.span, FlatShape::GlobPattern)]
|
|
||||||
}
|
|
||||||
Expr::List(list) => {
|
Expr::List(list) => {
|
||||||
let outer_span = expr.span;
|
let outer_span = expr.span;
|
||||||
let mut last_end = outer_span.start;
|
let mut last_end = outer_span.start;
|
||||||
|
@ -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<u8>, Option<ParseError>) {
|
pub fn unescape_string(bytes: &[u8], span: Span) -> (Vec<u8>, Option<ParseError>) {
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
@ -4575,7 +4553,7 @@ pub fn parse_value(
|
|||||||
| SyntaxShape::Signature
|
| SyntaxShape::Signature
|
||||||
| SyntaxShape::Filepath
|
| SyntaxShape::Filepath
|
||||||
| SyntaxShape::String
|
| SyntaxShape::String
|
||||||
| SyntaxShape::LsGlobPattern => {}
|
| SyntaxShape::GlobPattern => {}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::Expected("non-[] value", span));
|
working_set.error(ParseError::Expected("non-[] value", span));
|
||||||
return Expression::garbage(span);
|
return Expression::garbage(span);
|
||||||
@ -4600,7 +4578,6 @@ pub fn parse_value(
|
|||||||
SyntaxShape::Filepath => parse_filepath(working_set, span),
|
SyntaxShape::Filepath => parse_filepath(working_set, span),
|
||||||
SyntaxShape::Directory => parse_directory(working_set, span),
|
SyntaxShape::Directory => parse_directory(working_set, span),
|
||||||
SyntaxShape::GlobPattern => parse_glob_pattern(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::String => parse_string(working_set, span),
|
||||||
SyntaxShape::Binary => parse_binary(working_set, span),
|
SyntaxShape::Binary => parse_binary(working_set, span),
|
||||||
SyntaxShape::Signature => {
|
SyntaxShape::Signature => {
|
||||||
@ -6004,7 +5981,6 @@ pub fn discover_captures_in_expr(
|
|||||||
Expr::Garbage => {}
|
Expr::Garbage => {}
|
||||||
Expr::Nothing => {}
|
Expr::Nothing => {}
|
||||||
Expr::GlobPattern(_, _) => {}
|
Expr::GlobPattern(_, _) => {}
|
||||||
Expr::LsGlobPattern(_, _) => {}
|
|
||||||
Expr::Int(_) => {}
|
Expr::Int(_) => {}
|
||||||
Expr::Keyword(_, _, expr) => {
|
Expr::Keyword(_, _, expr) => {
|
||||||
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
|
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
|
||||||
|
@ -40,7 +40,6 @@ pub enum Expr {
|
|||||||
Filepath(String, bool),
|
Filepath(String, bool),
|
||||||
Directory(String, bool),
|
Directory(String, bool),
|
||||||
GlobPattern(String, bool),
|
GlobPattern(String, bool),
|
||||||
LsGlobPattern(String, bool),
|
|
||||||
String(String),
|
String(String),
|
||||||
CellPath(CellPath),
|
CellPath(CellPath),
|
||||||
FullCellPath(Box<FullCellPath>),
|
FullCellPath(Box<FullCellPath>),
|
||||||
|
@ -209,7 +209,6 @@ impl Expression {
|
|||||||
Expr::Garbage => false,
|
Expr::Garbage => false,
|
||||||
Expr::Nothing => false,
|
Expr::Nothing => false,
|
||||||
Expr::GlobPattern(_, _) => false,
|
Expr::GlobPattern(_, _) => false,
|
||||||
Expr::LsGlobPattern(_, _) => false,
|
|
||||||
Expr::Int(_) => false,
|
Expr::Int(_) => false,
|
||||||
Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set),
|
Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set),
|
||||||
Expr::List(list) => {
|
Expr::List(list) => {
|
||||||
@ -389,7 +388,6 @@ impl Expression {
|
|||||||
Expr::Garbage => {}
|
Expr::Garbage => {}
|
||||||
Expr::Nothing => {}
|
Expr::Nothing => {}
|
||||||
Expr::GlobPattern(_, _) => {}
|
Expr::GlobPattern(_, _) => {}
|
||||||
Expr::LsGlobPattern(_, _) => {}
|
|
||||||
Expr::MatchBlock(_) => {}
|
Expr::MatchBlock(_) => {}
|
||||||
Expr::Int(_) => {}
|
Expr::Int(_) => {}
|
||||||
Expr::Keyword(_, _, expr) => expr.replace_span(working_set, replaced, new_span),
|
Expr::Keyword(_, _, expr) => expr.replace_span(working_set, replaced, new_span),
|
||||||
|
@ -286,9 +286,8 @@ pub trait Eval {
|
|||||||
}
|
}
|
||||||
Expr::Overlay(_) => Self::eval_overlay(state, expr.span),
|
Expr::Overlay(_) => Self::eval_overlay(state, expr.span),
|
||||||
Expr::GlobPattern(pattern, quoted) => {
|
Expr::GlobPattern(pattern, quoted) => {
|
||||||
Self::eval_glob_pattern(state, mut_state, pattern.clone(), *quoted, expr.span)
|
// 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
|
||||||
Expr::LsGlobPattern(pattern, quoted) => {
|
|
||||||
if *quoted {
|
if *quoted {
|
||||||
Ok(Value::quoted_string(pattern, expr.span))
|
Ok(Value::quoted_string(pattern, expr.span))
|
||||||
} else {
|
} else {
|
||||||
@ -381,14 +380,6 @@ pub trait Eval {
|
|||||||
|
|
||||||
fn eval_overlay(state: Self::State<'_>, span: Span) -> Result<Value, ShellError>;
|
fn eval_overlay(state: Self::State<'_>, span: Span) -> Result<Value, ShellError>;
|
||||||
|
|
||||||
fn eval_glob_pattern(
|
|
||||||
state: Self::State<'_>,
|
|
||||||
mut_state: &mut Self::MutState,
|
|
||||||
pattern: String,
|
|
||||||
quoted: bool,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<Value, ShellError>;
|
|
||||||
|
|
||||||
/// For expressions that should never actually be evaluated
|
/// For expressions that should never actually be evaluated
|
||||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError>;
|
fn unreachable(expr: &Expression) -> Result<Value, ShellError>;
|
||||||
}
|
}
|
||||||
|
@ -388,16 +388,6 @@ impl Eval for EvalConst {
|
|||||||
Err(ShellError::NotAConstant { span })
|
Err(ShellError::NotAConstant { span })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_glob_pattern(
|
|
||||||
_: &StateWorkingSet,
|
|
||||||
_: &mut (),
|
|
||||||
_: String,
|
|
||||||
_: bool,
|
|
||||||
span: Span,
|
|
||||||
) -> Result<Value, ShellError> {
|
|
||||||
Err(ShellError::NotAConstant { span })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
fn unreachable(expr: &Expression) -> Result<Value, ShellError> {
|
||||||
Err(ShellError::NotAConstant { span: expr.span })
|
Err(ShellError::NotAConstant { span: expr.span })
|
||||||
}
|
}
|
||||||
|
@ -1264,23 +1264,6 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
|
|||||||
span: Span,
|
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.
|
/// Tried spreading a non-list inside a list or command call.
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
|
@ -64,9 +64,6 @@ pub enum SyntaxShape {
|
|||||||
/// A glob pattern is allowed, eg `foo*`
|
/// A glob pattern is allowed, eg `foo*`
|
||||||
GlobPattern,
|
GlobPattern,
|
||||||
|
|
||||||
/// A special glob pattern for ls.
|
|
||||||
LsGlobPattern,
|
|
||||||
|
|
||||||
/// Only an integer value is allowed
|
/// Only an integer value is allowed
|
||||||
Int,
|
Int,
|
||||||
|
|
||||||
@ -154,7 +151,6 @@ impl SyntaxShape {
|
|||||||
SyntaxShape::Filesize => Type::Filesize,
|
SyntaxShape::Filesize => Type::Filesize,
|
||||||
SyntaxShape::FullCellPath => Type::Any,
|
SyntaxShape::FullCellPath => Type::Any,
|
||||||
SyntaxShape::GlobPattern => Type::String,
|
SyntaxShape::GlobPattern => Type::String,
|
||||||
SyntaxShape::LsGlobPattern => Type::String,
|
|
||||||
SyntaxShape::Error => Type::Error,
|
SyntaxShape::Error => Type::Error,
|
||||||
SyntaxShape::ImportPattern => Type::Any,
|
SyntaxShape::ImportPattern => Type::Any,
|
||||||
SyntaxShape::Int => Type::Int,
|
SyntaxShape::Int => Type::Int,
|
||||||
@ -205,7 +201,6 @@ impl Display for SyntaxShape {
|
|||||||
SyntaxShape::Filepath => write!(f, "path"),
|
SyntaxShape::Filepath => write!(f, "path"),
|
||||||
SyntaxShape::Directory => write!(f, "directory"),
|
SyntaxShape::Directory => write!(f, "directory"),
|
||||||
SyntaxShape::GlobPattern => write!(f, "glob"),
|
SyntaxShape::GlobPattern => write!(f, "glob"),
|
||||||
SyntaxShape::LsGlobPattern => write!(f, "glob"),
|
|
||||||
SyntaxShape::ImportPattern => write!(f, "import"),
|
SyntaxShape::ImportPattern => write!(f, "import"),
|
||||||
SyntaxShape::Block => write!(f, "block"),
|
SyntaxShape::Block => write!(f, "block"),
|
||||||
SyntaxShape::Closure(args) => {
|
SyntaxShape::Closure(args) => {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
/// A simple wrapper to String.
|
/// A simple wrapper to String.
|
||||||
///
|
///
|
||||||
/// But it tracks if the string is originally quoted.
|
/// But it tracks if the string is originally quoted.
|
||||||
/// So commands can make decision on path auto-expanding behavior.
|
/// So commands can make decision on path auto-expanding behavior.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub enum NuPath {
|
pub enum NuPath {
|
||||||
/// A quoted path(except backtick), in this case, nushell shouldn't auto-expand path.
|
/// A quoted path(except backtick), in this case, nushell shouldn't auto-expand path.
|
||||||
Quoted(String),
|
Quoted(String),
|
||||||
@ -10,6 +13,15 @@ pub enum NuPath {
|
|||||||
UnQuoted(String),
|
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<str> for NuPath {
|
impl AsRef<str> for NuPath {
|
||||||
fn as_ref(&self) -> &str {
|
fn as_ref(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
@ -17,3 +29,9 @@ impl AsRef<str> for NuPath {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for NuPath {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user