-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow filesystem commands to access files with glob metachars in name #10694
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
6e962e9
Add Paths::single() and ability to preload a single file into iterator.;
bobhy 803b3ac
nu-cmd-base::arg_glob() -- centralize globbing of
bobhy 57e9759
test cases with glob metachars in file name
bobhy e11578c
windows filds can't have asterisk or question mark
bobhy 75e69e5
fix the previous fix (for non windows)
bobhy f7965fe
same fix for cp, mv, rm, du
bobhy 52446c5
Update test to match new error text.
bobhy 5c1199c
Merge branch 'main' into fix_10571
bobhy 456af6c
merge main
bobhy 1a364d9
Merge branch 'main' into fix_10571
fdncred File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
// 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( | ||
pat_err.msg.into(), | ||
pattern.span, // improve specificity | ||
)) | ||
} | ||
} | ||
} | ||
|
||
#[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")); | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, @bobhy
Sorry for a simple question, why are you using
glob_with_parent
rather thanglob_with
?