mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-05-19 09:40:22 -07:00
ignore: allow filtering with predicate
Adds `WalkBuilder::filter_entry` that takes a predicate to be applied to all entries. If the predicate returns `false` on a given entry, that entry and all children will be skipped. Fixes #1555, Closes #1557
This commit is contained in:
parent
df7a3bfc7f
commit
793c1179cc
@ -489,6 +489,7 @@ pub struct WalkBuilder {
|
|||||||
sorter: Option<Sorter>,
|
sorter: Option<Sorter>,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
skip: Option<Arc<Handle>>,
|
skip: Option<Arc<Handle>>,
|
||||||
|
filter: Option<Filter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -499,6 +500,9 @@ enum Sorter {
|
|||||||
ByPath(Arc<dyn Fn(&Path, &Path) -> cmp::Ordering + Send + Sync + 'static>),
|
ByPath(Arc<dyn Fn(&Path, &Path) -> cmp::Ordering + Send + Sync + 'static>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Filter(Arc<dyn Fn(&DirEntry) -> bool + Send + Sync + 'static>);
|
||||||
|
|
||||||
impl fmt::Debug for WalkBuilder {
|
impl fmt::Debug for WalkBuilder {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.debug_struct("WalkBuilder")
|
f.debug_struct("WalkBuilder")
|
||||||
@ -531,6 +535,7 @@ impl WalkBuilder {
|
|||||||
sorter: None,
|
sorter: None,
|
||||||
threads: 0,
|
threads: 0,
|
||||||
skip: None,
|
skip: None,
|
||||||
|
filter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,6 +584,7 @@ impl WalkBuilder {
|
|||||||
ig: ig_root.clone(),
|
ig: ig_root.clone(),
|
||||||
max_filesize: self.max_filesize,
|
max_filesize: self.max_filesize,
|
||||||
skip: self.skip.clone(),
|
skip: self.skip.clone(),
|
||||||
|
filter: self.filter.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -597,6 +603,7 @@ impl WalkBuilder {
|
|||||||
same_file_system: self.same_file_system,
|
same_file_system: self.same_file_system,
|
||||||
threads: self.threads,
|
threads: self.threads,
|
||||||
skip: self.skip.clone(),
|
skip: self.skip.clone(),
|
||||||
|
filter: self.filter.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -878,6 +885,23 @@ impl WalkBuilder {
|
|||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Yields only entries which satisfy the given predicate and skips
|
||||||
|
/// descending into directories that do not satisfy the given predicate.
|
||||||
|
///
|
||||||
|
/// The predicate is applied to all entries. If the predicate is
|
||||||
|
/// true, iteration carries on as normal. If the predicate is false, the
|
||||||
|
/// entry is ignored and if it is a directory, it is not descended into.
|
||||||
|
///
|
||||||
|
/// Note that the errors for reading entries that may not satisfy the
|
||||||
|
/// predicate will still be yielded.
|
||||||
|
pub fn filter_entry<P>(&mut self, filter: P) -> &mut WalkBuilder
|
||||||
|
where
|
||||||
|
P: Fn(&DirEntry) -> bool + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.filter = Some(Filter(Arc::new(filter)));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walk is a recursive directory iterator over file paths in one or more
|
/// Walk is a recursive directory iterator over file paths in one or more
|
||||||
@ -893,6 +917,7 @@ pub struct Walk {
|
|||||||
ig: Ignore,
|
ig: Ignore,
|
||||||
max_filesize: Option<u64>,
|
max_filesize: Option<u64>,
|
||||||
skip: Option<Arc<Handle>>,
|
skip: Option<Arc<Handle>>,
|
||||||
|
filter: Option<Filter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Walk {
|
impl Walk {
|
||||||
@ -925,6 +950,11 @@ impl Walk {
|
|||||||
&ent.metadata().ok(),
|
&ent.metadata().ok(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if let Some(Filter(filter)) = &self.filter {
|
||||||
|
if !filter(ent) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1157,6 +1187,7 @@ pub struct WalkParallel {
|
|||||||
same_file_system: bool,
|
same_file_system: bool,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
skip: Option<Arc<Handle>>,
|
skip: Option<Arc<Handle>>,
|
||||||
|
filter: Option<Filter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WalkParallel {
|
impl WalkParallel {
|
||||||
@ -1255,6 +1286,7 @@ impl WalkParallel {
|
|||||||
max_filesize: self.max_filesize,
|
max_filesize: self.max_filesize,
|
||||||
follow_links: self.follow_links,
|
follow_links: self.follow_links,
|
||||||
skip: self.skip.clone(),
|
skip: self.skip.clone(),
|
||||||
|
filter: self.filter.clone(),
|
||||||
};
|
};
|
||||||
handles.push(s.spawn(|_| worker.run()));
|
handles.push(s.spawn(|_| worker.run()));
|
||||||
}
|
}
|
||||||
@ -1380,6 +1412,9 @@ struct Worker<'s> {
|
|||||||
/// A file handle to skip, currently is either `None` or stdout, if it's
|
/// A file handle to skip, currently is either `None` or stdout, if it's
|
||||||
/// a file and it has been requested to skip files identical to stdout.
|
/// a file and it has been requested to skip files identical to stdout.
|
||||||
skip: Option<Arc<Handle>>,
|
skip: Option<Arc<Handle>>,
|
||||||
|
/// A predicate applied to dir entries. If true, the entry and all
|
||||||
|
/// children will be skipped.
|
||||||
|
filter: Option<Filter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Worker<'s> {
|
impl<'s> Worker<'s> {
|
||||||
@ -1534,8 +1569,14 @@ impl<'s> Worker<'s> {
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
let should_skip_filtered =
|
||||||
if !should_skip_path && !should_skip_filesize {
|
if let Some(Filter(predicate)) = &self.filter {
|
||||||
|
!predicate(&dent)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if !should_skip_path && !should_skip_filesize && !should_skip_filtered
|
||||||
|
{
|
||||||
self.send(Work { dent, ignore: ig.clone(), root_device });
|
self.send(Work { dent, ignore: ig.clone(), root_device });
|
||||||
}
|
}
|
||||||
WalkState::Continue
|
WalkState::Continue
|
||||||
@ -1793,6 +1834,7 @@ fn device_num<P: AsRef<Path>>(_: P) -> io::Result<u64> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -2174,4 +2216,26 @@ mod tests {
|
|||||||
let builder = WalkBuilder::new(&dir_path);
|
let builder = WalkBuilder::new(&dir_path);
|
||||||
assert_paths(dir_path.parent().unwrap(), &builder, &["root"]);
|
assert_paths(dir_path.parent().unwrap(), &builder, &["root"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter() {
|
||||||
|
let td = tmpdir();
|
||||||
|
mkdirp(td.path().join("a/b/c"));
|
||||||
|
mkdirp(td.path().join("x/y"));
|
||||||
|
wfile(td.path().join("a/b/foo"), "");
|
||||||
|
wfile(td.path().join("x/y/foo"), "");
|
||||||
|
|
||||||
|
assert_paths(
|
||||||
|
td.path(),
|
||||||
|
&WalkBuilder::new(td.path()),
|
||||||
|
&["x", "x/y", "x/y/foo", "a", "a/b", "a/b/foo", "a/b/c"],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_paths(
|
||||||
|
td.path(),
|
||||||
|
&WalkBuilder::new(td.path())
|
||||||
|
.filter_entry(|entry| entry.file_name() != OsStr::new("a")),
|
||||||
|
&["x", "x/y", "x/y/foo"],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user