From 95596297a910455171647dce00781563e4a19d8a Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 30 Oct 2016 14:43:33 +0000 Subject: [PATCH] Basic glob ignoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See #97 and recently #130 too. This allows the user to pass in options such as "--ignore '*.pyc'" to not list any files ending in '.pyc' in the output. It uses the Rust glob crate and currently does a simple split on pipe, without any escaping, so it’s not really *complete*, but is at least something. --- Cargo.lock | 7 ++++++ Cargo.toml | 1 + src/exa.rs | 5 +++- src/options/filter.rs | 54 ++++++++++++++++++++++++++++++++++++++---- src/options/misfire.rs | 11 +++++++++ src/options/mod.rs | 9 +++---- src/output/details.rs | 2 +- xtests/ignores_ogg | 1 + xtests/run.sh | 4 ++++ 9 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 xtests/ignores_ogg diff --git a/Cargo.lock b/Cargo.lock index 82dbfa7..e2e75a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ dependencies = [ "datetime 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "locale 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -85,6 +86,11 @@ dependencies = [ "url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "iso8601" version = "0.1.1" @@ -390,6 +396,7 @@ dependencies = [ "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" "checksum git2 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "123c3149e1d558792dae893ac01495919ca46b8734a7cf246ee34bf475860c9b" +"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum iso8601 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "11dc464f8c6f17595d191447c9c6559298b2d023d6f846a4a23ac7ea3c46c477" "checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" "checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d" diff --git a/Cargo.toml b/Cargo.toml index ec548fc..251104c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ ansi_term = "0.7.1" bitflags = "0.1" datetime = "0.4.3" getopts = "0.2.14" +glob = "0.2" lazy_static = "0.1.*" libc = "0.2.9" locale = "0.2.1" diff --git a/src/exa.rs b/src/exa.rs index e02ec1a..50515ec 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -4,6 +4,7 @@ extern crate ansi_term; extern crate datetime; extern crate getopts; +extern crate glob; extern crate libc; extern crate locale; extern crate natord; @@ -94,7 +95,9 @@ impl<'w, W: Write + 'w> Exa<'w, W> { let no_files = files.is_empty(); let is_only_dir = dirs.len() == 1 && no_files; + self.options.filter.filter_argument_files(&mut files); try!(self.print_files(None, files)); + self.print_dirs(dirs, no_files, is_only_dir) } @@ -122,7 +125,7 @@ impl<'w, W: Write + 'w> Exa<'w, W> { } }; - self.options.filter.filter_files(&mut children); + self.options.filter.filter_child_files(&mut children); self.options.filter.sort_files(&mut children); if let Some(recurse_opts) = self.options.dir_action.recurse_options() { diff --git a/src/options/filter.rs b/src/options/filter.rs index 1ff8647..ce7d4ba 100644 --- a/src/options/filter.rs +++ b/src/options/filter.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use std::os::unix::fs::MetadataExt; use getopts; +use glob; use natord; use fs::File; @@ -60,6 +61,10 @@ pub struct FileFilter { /// evaluation that goes through my home directory is slowed down by /// this accumulated sludge. show_invisibles: bool, + + /// Glob patterns to ignore. Any file name that matches *any* of these + /// patterns won't be displayed in the list. + ignore_patterns: IgnorePatterns, } impl FileFilter { @@ -67,22 +72,36 @@ impl FileFilter { /// Determines the set of file filter options to use, based on the user’s /// command-line arguments. pub fn deduce(matches: &getopts::Matches) -> Result { - let sort_field = try!(SortField::deduce(&matches)); - Ok(FileFilter { list_dirs_first: matches.opt_present("group-directories-first"), reverse: matches.opt_present("reverse"), + sort_field: try!(SortField::deduce(matches)), show_invisibles: matches.opt_present("all"), - sort_field: sort_field, + ignore_patterns: try!(IgnorePatterns::deduce(matches)), }) } /// Remove every file in the given vector that does *not* pass the - /// filter predicate. - pub fn filter_files(&self, files: &mut Vec) { + /// filter predicate for files found inside a directory. + pub fn filter_child_files(&self, files: &mut Vec) { if !self.show_invisibles { files.retain(|f| !f.is_dotfile()); } + + files.retain(|f| !self.ignore_patterns.is_ignored(f)); + } + + /// Remove every file in the given vector that does *not* pass the + /// filter predicate for file names specified on the command-line. + /// + /// The rules are different for these types of files than the other + /// type because the ignore rules can be used with globbing. For + /// example, running "exa -I='*.tmp' .vimrc" shouldn't filter out the + /// dotfile, because it's been directly specified. But running + /// "exa -I='*.ogg' music/*" should filter out the ogg files obtained + /// from the glob, even though the globbing is done by the shell! + pub fn filter_argument_files(&self, files: &mut Vec) { + files.retain(|f| !self.ignore_patterns.is_ignored(f)); } /// Sort the files in the given vector based on the sort field option. @@ -229,3 +248,28 @@ impl SortField { } } } + + +#[derive(PartialEq, Default, Debug, Clone)] +struct IgnorePatterns { + patterns: Vec, +} + +impl IgnorePatterns { + /// Determines the set of file filter options to use, based on the user’s + /// command-line arguments. + pub fn deduce(matches: &getopts::Matches) -> Result { + let patterns = match matches.opt_str("ignore-glob") { + None => Ok(Vec::new()), + Some(is) => is.split('|').map(|a| glob::Pattern::new(a)).collect(), + }; + + Ok(IgnorePatterns { + patterns: try!(patterns), + }) + } + + fn is_ignored(&self, file: &File) -> bool { + self.patterns.iter().any(|p| p.matches(&file.name)) + } +} diff --git a/src/options/misfire.rs b/src/options/misfire.rs index 0d7ab73..1ee437d 100644 --- a/src/options/misfire.rs +++ b/src/options/misfire.rs @@ -2,6 +2,7 @@ use std::fmt; use std::num::ParseIntError; use getopts; +use glob; /// A list of legal choices for an argument-taking option @@ -45,6 +46,9 @@ pub enum Misfire { /// A numeric option was given that failed to be parsed as a number. FailedParse(ParseIntError), + + /// A glob ignore was given that failed to be parsed as a pattern. + FailedGlobPattern(String), } impl Misfire { @@ -66,6 +70,12 @@ impl Misfire { } } +impl From for Misfire { + fn from(error: glob::PatternError) -> Misfire { + Misfire::FailedGlobPattern(error.to_string()) + } +} + impl fmt::Display for Misfire { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Misfire::*; @@ -80,6 +90,7 @@ impl fmt::Display for Misfire { Useless(a, true, b) => write!(f, "Option --{} is useless given option --{}.", a, b), Useless2(a, b1, b2) => write!(f, "Option --{} is useless without options --{} or --{}.", a, b1, b2), FailedParse(ref e) => write!(f, "Failed to parse number: {}", e), + FailedGlobPattern(ref e) => write!(f, "Failed to parse glob pattern: {}", e), } } } diff --git a/src/options/mod.rs b/src/options/mod.rs index bcac05f..7dd329f 100644 --- a/src/options/mod.rs +++ b/src/options/mod.rs @@ -60,10 +60,11 @@ impl Options { // Filtering and sorting options opts.optflag("", "group-directories-first", "list directories before other files"); - opts.optflag("a", "all", "show dot-files"); - opts.optflag("d", "list-dirs", "list directories as regular files"); - opts.optflag("r", "reverse", "reverse order of files"); - opts.optopt ("s", "sort", "field to sort by", "WORD"); + opts.optflag("a", "all", "show dot-files"); + opts.optflag("d", "list-dirs", "list directories as regular files"); + opts.optflag("r", "reverse", "reverse order of files"); + opts.optopt ("s", "sort", "field to sort by", "WORD"); + opts.optopt ("I", "ignore-glob", "patterns (|-separated) of names to ignore", "GLOBS"); // Long view options opts.optflag("b", "binary", "use binary prefixes in file sizes"); diff --git a/src/output/details.rs b/src/output/details.rs index f71296b..dba804c 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -333,7 +333,7 @@ impl Details { } } - self.filter.filter_files(&mut files); + self.filter.filter_child_files(&mut files); if !files.is_empty() { for xattr in egg.xattrs { diff --git a/xtests/ignores_ogg b/xtests/ignores_ogg new file mode 100644 index 0000000..5b350a7 --- /dev/null +++ b/xtests/ignores_ogg @@ -0,0 +1 @@ +/home/vagrant/testcases/file-types/music.mp3 diff --git a/xtests/run.sh b/xtests/run.sh index ea30312..998852d 100755 --- a/xtests/run.sh +++ b/xtests/run.sh @@ -46,6 +46,10 @@ $exa $testcases/permissions -lghR 2>&1 | diff -q - $results/permissions || exit # File types $exa $testcases/file-types -1 2>&1 | diff -q - $results/file-types || exit 1 +# Ignores +$exa $testcases/file-types/music.* -I "*.ogg" -1 2>&1 | diff -q - $results/ignores_ogg || exit 1 +$exa $testcases/file-types/music.* -I "*.ogg|*.mp3" -1 2>&1 | diff -q - $results/empty || exit 1 + # Links $exa $testcases/links -1 2>&1 | diff -q - $results/links_1 || exit 1 $exa $testcases/links -T 2>&1 | diff -q - $results/links_T || exit 1