diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index e1d9b7990..bf3860bf3 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -22,6 +22,7 @@ term_grid = "0.1.5" termsize = "0.1.6" time = "0.1.40" unicode-width = "0.1.5" +glob = "0.3.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index ece497bdb..e011b9e52 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -17,6 +17,7 @@ mod quoting_style; mod version_cmp; use clap::{App, Arg}; +use glob; use number_prefix::NumberPrefix; use quoting_style::{escape_name, QuotingStyle}; #[cfg(unix)] @@ -138,6 +139,8 @@ pub mod options { pub static COLOR: &str = "color"; pub static PATHS: &str = "paths"; pub static INDICATOR_STYLE: &str = "indicator-style"; + pub static HIDE: &str = "hide"; + pub static IGNORE: &str = "ignore"; } #[derive(PartialEq, Eq)] @@ -191,7 +194,7 @@ struct Config { recursive: bool, reverse: bool, dereference: bool, - ignore_backups: bool, + ignore_patterns: Vec, size_format: SizeFormat, directory: bool, time: Time, @@ -437,6 +440,28 @@ impl Config { IndicatorStyle::None }; + let mut ignore_patterns = Vec::new(); + if options.is_present(options::IGNORE_BACKUPS) { + ignore_patterns.push(glob::Pattern::new("*~").unwrap()); + ignore_patterns.push(glob::Pattern::new(".*~").unwrap()); + } + + for pattern in options.values_of(options::IGNORE).into_iter().flatten() { + match glob::Pattern::new(pattern) { + Ok(p) => ignore_patterns.push(p), + Err(e) => show_error!("{}", e), + } + } + + if files == Files::Normal { + for pattern in options.values_of(options::HIDE).into_iter().flatten() { + match glob::Pattern::new(pattern) { + Ok(p) => ignore_patterns.push(p), + Err(e) => show_error!("{}", e), + } + } + } + Config { format, files, @@ -444,7 +469,7 @@ impl Config { recursive: options.is_present(options::RECURSIVE), reverse: options.is_present(options::REVERSE), dereference: options.is_present(options::DEREFERENCE), - ignore_backups: options.is_present(options::IGNORE_BACKUPS), + ignore_patterns, size_format, directory: options.is_present(options::DIRECTORY), time, @@ -664,6 +689,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ]) ) + // Hide and ignore + .arg( + Arg::with_name(options::HIDE) + .long(options::HIDE) + .takes_value(true) + .multiple(true) + ) + .arg( + Arg::with_name(options::IGNORE) + .long(options::IGNORE) + .takes_value(true) + .multiple(true) + ) + .arg( + Arg::with_name(options::IGNORE_BACKUPS) + .short("B") + .long(options::IGNORE_BACKUPS) + .help("Ignore entries which end with ~."), + ) + // Sort arguments .arg( Arg::with_name(options::SORT) @@ -761,12 +806,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { '.' and '..'.", ), ) - .arg( - Arg::with_name(options::IGNORE_BACKUPS) - .short("B") - .long(options::IGNORE_BACKUPS) - .help("Ignore entries which end with ~."), - ) .arg( Arg::with_name(options::DIRECTORY) .short("d") @@ -985,10 +1024,12 @@ fn is_hidden(file_path: &DirEntry) -> bool { fn should_display(entry: &DirEntry, config: &Config) -> bool { let ffi_name = entry.file_name(); let name = ffi_name.to_string_lossy(); + if config.files == Files::Normal && is_hidden(entry) { return false; } - if config.ignore_backups && name.ends_with('~') { + + if config.ignore_patterns.iter().any(|p| p.matches(&name)) { return false; } true diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index d403e5577..7d678acea 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1209,3 +1209,83 @@ fn test_ls_quoting_style() { assert_eq!(result.stdout, format!("{}\n", correct)); } } + +#[test] +fn test_ls_ignore_hide() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("README.md"); + at.touch("CONTRIBUTING.md"); + at.touch("some_other_file"); + at.touch("READMECAREFULLY.md"); + + scene.ucmd().arg("--hide").arg("*").succeeds().stdout_is(""); + + scene + .ucmd() + .arg("--ignore") + .arg("*") + .succeeds() + .stdout_is(""); + + scene + .ucmd() + .arg("--ignore") + .arg("irrelevant pattern") + .succeeds() + .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("--ignore") + .arg("README*.md") + .succeeds() + .stdout_is("CONTRIBUTING.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("--hide") + .arg("README*.md") + .succeeds() + .stdout_is("CONTRIBUTING.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("--ignore") + .arg("*.md") + .succeeds() + .stdout_is("some_other_file\n"); + + scene + .ucmd() + .arg("-a") + .arg("--ignore") + .arg("*.md") + .succeeds() + .stdout_is(".\n..\nsome_other_file\n"); + + scene + .ucmd() + .arg("-a") + .arg("--hide") + .arg("*.md") + .succeeds() + .stdout_is(".\n..\nCONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); + + scene + .ucmd() + .arg("-A") + .arg("--ignore") + .arg("*.md") + .succeeds() + .stdout_is("some_other_file\n"); + + scene + .ucmd() + .arg("-A") + .arg("--hide") + .arg("*.md") + .succeeds() + .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); +}