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
This commit is contained in:
sharkdp 2017-10-22 23:00:19 +02:00 committed by David Peter
parent 19afb15a98
commit 5ad69fb2fb
5 changed files with 92 additions and 11 deletions

View File

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

View File

@ -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<TokenizedCommand>,
/// A list of glob patterns that should be excluded from the search.
pub exclude_patterns: Vec<String>,
}
/// Print error message to stderr and exit with status `1`.

View File

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

View File

@ -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<Regex>, config: Arc<FdOptions>) {
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<Regex>, config: Arc<FdOptions>) {
.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)

View File

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