Просмотр исходного кода

Basic glob ignoring

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.
Ben S 9 лет назад
Родитель
Сommit
95596297a9
9 измененных файлов с 83 добавлено и 11 удалено
  1. 7 0
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 4 1
      src/exa.rs
  4. 49 5
      src/options/filter.rs
  5. 11 0
      src/options/misfire.rs
  6. 5 4
      src/options/mod.rs
  7. 1 1
      src/output/details.rs
  8. 1 0
      xtests/ignores_ogg
  9. 4 0
      xtests/run.sh

+ 7 - 0
Cargo.lock

@@ -7,6 +7,7 @@ dependencies = [
  "datetime 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "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)",
  "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)",
  "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)",
  "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)",
  "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)",
  "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)",
  "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]]
 [[package]]
 name = "iso8601"
 name = "iso8601"
 version = "0.1.1"
 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 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 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 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 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 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"
 "checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d"

+ 1 - 0
Cargo.toml

@@ -16,6 +16,7 @@ ansi_term = "0.7.1"
 bitflags = "0.1"
 bitflags = "0.1"
 datetime = "0.4.3"
 datetime = "0.4.3"
 getopts = "0.2.14"
 getopts = "0.2.14"
+glob = "0.2"
 lazy_static = "0.1.*"
 lazy_static = "0.1.*"
 libc = "0.2.9"
 libc = "0.2.9"
 locale = "0.2.1"
 locale = "0.2.1"

+ 4 - 1
src/exa.rs

@@ -4,6 +4,7 @@
 extern crate ansi_term;
 extern crate ansi_term;
 extern crate datetime;
 extern crate datetime;
 extern crate getopts;
 extern crate getopts;
+extern crate glob;
 extern crate libc;
 extern crate libc;
 extern crate locale;
 extern crate locale;
 extern crate natord;
 extern crate natord;
@@ -94,7 +95,9 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
         let no_files = files.is_empty();
         let no_files = files.is_empty();
         let is_only_dir = dirs.len() == 1 && no_files;
         let is_only_dir = dirs.len() == 1 && no_files;
 
 
+        self.options.filter.filter_argument_files(&mut files);
         try!(self.print_files(None, files));
         try!(self.print_files(None, files));
+
         self.print_dirs(dirs, no_files, is_only_dir)
         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);
             self.options.filter.sort_files(&mut children);
 
 
             if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
             if let Some(recurse_opts) = self.options.dir_action.recurse_options() {

+ 49 - 5
src/options/filter.rs

@@ -2,6 +2,7 @@ use std::cmp::Ordering;
 use std::os::unix::fs::MetadataExt;
 use std::os::unix::fs::MetadataExt;
 
 
 use getopts;
 use getopts;
+use glob;
 use natord;
 use natord;
 
 
 use fs::File;
 use fs::File;
@@ -60,6 +61,10 @@ pub struct FileFilter {
     ///   evaluation that goes through my home directory is slowed down by
     ///   evaluation that goes through my home directory is slowed down by
     ///   this accumulated sludge.
     ///   this accumulated sludge.
     show_invisibles: bool,
     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 {
 impl FileFilter {
@@ -67,22 +72,36 @@ impl FileFilter {
     /// Determines the set of file filter options to use, based on the user’s
     /// Determines the set of file filter options to use, based on the user’s
     /// command-line arguments.
     /// command-line arguments.
     pub fn deduce(matches: &getopts::Matches) -> Result<FileFilter, Misfire> {
     pub fn deduce(matches: &getopts::Matches) -> Result<FileFilter, Misfire> {
-        let sort_field = try!(SortField::deduce(&matches));
-
         Ok(FileFilter {
         Ok(FileFilter {
             list_dirs_first: matches.opt_present("group-directories-first"),
             list_dirs_first: matches.opt_present("group-directories-first"),
             reverse:         matches.opt_present("reverse"),
             reverse:         matches.opt_present("reverse"),
+            sort_field:      try!(SortField::deduce(matches)),
             show_invisibles: matches.opt_present("all"),
             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
     /// Remove every file in the given vector that does *not* pass the
-    /// filter predicate.
-    pub fn filter_files(&self, files: &mut Vec<File>) {
+    /// filter predicate for files found inside a directory.
+    pub fn filter_child_files(&self, files: &mut Vec<File>) {
         if !self.show_invisibles {
         if !self.show_invisibles {
             files.retain(|f| !f.is_dotfile());
             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<File>) {
+        files.retain(|f| !self.ignore_patterns.is_ignored(f));
     }
     }
 
 
     /// Sort the files in the given vector based on the sort field option.
     /// 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<glob::Pattern>,
+}
+
+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<IgnorePatterns, Misfire> {
+        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))
+    }
+}

+ 11 - 0
src/options/misfire.rs

@@ -2,6 +2,7 @@ use std::fmt;
 use std::num::ParseIntError;
 use std::num::ParseIntError;
 
 
 use getopts;
 use getopts;
+use glob;
 
 
 
 
 /// A list of legal choices for an argument-taking option
 /// 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.
     /// A numeric option was given that failed to be parsed as a number.
     FailedParse(ParseIntError),
     FailedParse(ParseIntError),
+
+    /// A glob ignore was given that failed to be parsed as a pattern.
+    FailedGlobPattern(String),
 }
 }
 
 
 impl Misfire {
 impl Misfire {
@@ -66,6 +70,12 @@ impl Misfire {
     }
     }
 }
 }
 
 
+impl From<glob::PatternError> for Misfire {
+    fn from(error: glob::PatternError) -> Misfire {
+        Misfire::FailedGlobPattern(error.to_string())
+    }
+}
+
 impl fmt::Display for Misfire {
 impl fmt::Display for Misfire {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use self::Misfire::*;
         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),
             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),
             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),
             FailedParse(ref e)         => write!(f, "Failed to parse number: {}", e),
+            FailedGlobPattern(ref e)   => write!(f, "Failed to parse glob pattern: {}", e),
         }
         }
     }
     }
 }
 }

+ 5 - 4
src/options/mod.rs

@@ -60,10 +60,11 @@ impl Options {
 
 
         // Filtering and sorting options
         // Filtering and sorting options
         opts.optflag("",  "group-directories-first", "list directories before other files");
         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
         // Long view options
         opts.optflag("b", "binary",    "use binary prefixes in file sizes");
         opts.optflag("b", "binary",    "use binary prefixes in file sizes");

+ 1 - 1
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() {
                 if !files.is_empty() {
                     for xattr in egg.xattrs {
                     for xattr in egg.xattrs {

+ 1 - 0
xtests/ignores_ogg

@@ -0,0 +1 @@
+/home/vagrant/testcases/file-types/music.mp3

+ 4 - 0
xtests/run.sh

@@ -46,6 +46,10 @@ $exa $testcases/permissions -lghR 2>&1 | diff -q - $results/permissions  || exit
 # File types
 # File types
 $exa $testcases/file-types -1 2>&1 | diff -q - $results/file-types  || exit 1
 $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
 # Links
 $exa $testcases/links -1 2>&1 | diff -q - $results/links_1  || exit 1
 $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
 $exa $testcases/links -T 2>&1 | diff -q - $results/links_T  || exit 1