From 5ad69fb2fbcb211c6d651c6ddacefb19520f6242 Mon Sep 17 00:00:00 2001 From: sharkdp Date: Sun, 22 Oct 2017 23:00:19 +0200 Subject: [PATCH] Add support for exclude-patterns * Add `--exclude`/`-E` option. * Support for multiple exclude patterns Example: ``` bash > fd --exclude 'tests/**/*.rs' mod src/exec/mod.rs src/fshelper/mod.rs src/lscolors/mod.rs ``` Closes #89 --- src/app.rs | 35 ++++++++++++++++++++++++----------- src/internal.rs | 3 +++ src/main.rs | 4 ++++ src/walk.rs | 14 ++++++++++++++ tests/tests.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2333209..9365e75 100644 --- a/src/app.rs +++ b/src/app.rs @@ -81,6 +81,22 @@ pub fn build_app() -> App<'static, 'static> { .takes_value(true) .value_name("ext"), ) + .arg( + arg("exec") + .long("exec") + .short("x") + .takes_value(true) + .value_name("cmd"), + ) + .arg( + arg("exclude") + .long("exclude") + .short("E") + .takes_value(true) + .value_name("pattern") + .number_of_values(1) + .multiple(true), + ) .arg( arg("color") .long("color") @@ -103,13 +119,6 @@ pub fn build_app() -> App<'static, 'static> { .takes_value(true) .hidden(true), ) - .arg( - arg("exec") - .long("exec") - .short("x") - .takes_value(true) - .value_name("cmd"), - ) .arg(arg("pattern")) .arg(arg("path")) } @@ -158,6 +167,9 @@ fn usage() -> HashMap<&'static str, Help> { 'f' or 'file': regular files\n \ 'd' or 'directory': directories\n \ 'l' or 'symlink': symbolic links"); + doc!(h, "extension" + , "Filter by file extension" + , "(Additionally) filter search results by their file extension."); doc!(h, "exec" , "Execute the given command for each search result" , "Execute the given command for each search result.\n\ @@ -167,10 +179,11 @@ fn usage() -> HashMap<&'static str, Help> { '{.}': removes the extension from the input\n \ '{/}': places the basename of the input\n \ '{//}': places the parent of the input\n \ - '{/.}': places the basename of the input, without the extension\n"); - doc!(h, "extension" - , "Filter by file extension" - , "(Additionally) filter search results by their file extension."); + '{/.}': places the basename of the input, without the extension"); + doc!(h, "exclude" + , "Exclude entries that match the given glob pattern." + , "Exclude files/directories that match the given glob pattern. This overrides any \ + other ignore logic. Multiple exclude patterns can be specified."); doc!(h, "color" , "When to use colors: never, *auto*, always" , "Declare when to use color for the pattern match output:\n \ diff --git a/src/internal.rs b/src/internal.rs index a4c21f0..972c796 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -77,6 +77,9 @@ pub struct FdOptions { /// If a value is supplied, each item found will be used to generate and execute commands. pub command: Option, + + /// A list of glob patterns that should be excluded from the search. + pub exclude_patterns: Vec, } /// Print error message to stderr and exit with status `1`. diff --git a/src/main.rs b/src/main.rs index 736a9f8..da0cfff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,6 +141,10 @@ fn main() { e.trim_left_matches('.').to_lowercase() }), command, + exclude_patterns: matches + .values_of("exclude") + .map(|v| v.map(|p| String::from("!") + p).collect()) + .unwrap_or(vec![]), }; match RegexBuilder::new(pattern) diff --git a/src/walk.rs b/src/walk.rs index bf6fe0c..f908bb4 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -18,6 +18,7 @@ use std::thread; use std::time; use ignore::{self, WalkBuilder}; +use ignore::overrides::OverrideBuilder; use regex::Regex; /// The receiver thread can either be buffering results or directly streaming to the console. @@ -48,6 +49,18 @@ pub fn scan(root: &Path, pattern: Arc, config: Arc) { let (tx, rx) = channel(); let threads = config.threads; + let mut override_builder = OverrideBuilder::new(root); + + for pattern in config.exclude_patterns.iter() { + let res = override_builder.add(pattern); + if res.is_err() { + error(&format!("Error: malformed exclude pattern '{}'", pattern)); + } + } + let overrides = override_builder.build().unwrap_or_else(|_| { + error("Mismatch in exclude patterns"); + }); + let walker = WalkBuilder::new(root) .hidden(config.ignore_hidden) .ignore(config.read_ignore) @@ -55,6 +68,7 @@ pub fn scan(root: &Path, pattern: Arc, config: Arc) { .parents(config.read_ignore) .git_global(config.read_ignore) .git_exclude(config.read_ignore) + .overrides(overrides) .follow_links(config.follow_links) .max_depth(config.max_depth) .threads(threads) diff --git a/tests/tests.rs b/tests/tests.rs index 28420ad..96d3f4a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -538,3 +538,50 @@ fn test_symlink() { ), ); } + +/// Exclude patterns (--exclude) +#[test] +fn test_excludes() { + let te = TestEnv::new(); + + te.assert_output( + &["--exclude", "*.foo"], + "one + one/two + one/two/C.Foo2 + one/two/three + one/two/three/directory_foo + symlink", + ); + + te.assert_output( + &["--exclude", "*.foo", "--exclude", "*.Foo2"], + "one + one/two + one/two/three + one/two/three/directory_foo + symlink", + ); + + te.assert_output( + &["--exclude", "*.foo", "--exclude", "*.Foo2", "foo"], + "one/two/three/directory_foo", + ); + + te.assert_output( + &["--exclude", "one/two", "foo"], + "a.foo + one/b.foo", + ); + + te.assert_output( + &["--exclude", "one/**/*.foo"], + "a.foo + one + one/two + one/two/C.Foo2 + one/two/three + one/two/three/directory_foo + symlink", + ); +}