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.
This commit is contained in:
Ben S 2016-10-30 14:43:33 +00:00
parent a6712994c5
commit 95596297a9
9 changed files with 83 additions and 11 deletions

7
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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() {

View File

@ -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 users /// Determines the set of file filter options to use, based on the users
/// 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. /// filter predicate for files found inside a directory.
pub fn filter_files(&self, files: &mut Vec<File>) { 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 users
/// 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))
}
}

View File

@ -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),
} }
} }
} }

View File

@ -64,6 +64,7 @@ impl Options {
opts.optflag("d", "list-dirs", "list directories as regular files"); opts.optflag("d", "list-dirs", "list directories as regular files");
opts.optflag("r", "reverse", "reverse order of files"); opts.optflag("r", "reverse", "reverse order of files");
opts.optopt ("s", "sort", "field to sort by", "WORD"); 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");

View File

@ -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
xtests/ignores_ogg Normal file
View File

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

View File

@ -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