mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-26 05:47:32 +00:00
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:
parent
a6712994c5
commit
95596297a9
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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"
|
||||||
|
@ -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,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() {
|
||||||
|
@ -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.
|
/// 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 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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("a", "all", "show dot-files");
|
||||||
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");
|
||||||
|
@ -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
1
xtests/ignores_ogg
Normal file
@ -0,0 +1 @@
|
|||||||
|
[36m/home/vagrant/testcases/file-types/[38;5;92mmusic.mp3[0m
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user