summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCasey Rodarmor <casey@rodarmor.com>2020-04-20 18:40:22 -0700
committerAndrew Gallant <jamslam@gmail.com>2020-05-08 23:24:40 -0400
commit793c1179ccd7e755d635beee8be5c1e2202d404b (patch)
treec710d3b86edd6b9bc741d1e218b7eaa4b028eb54
parentdf7a3bfc7fe30f3e9e89d8775748b1239c5b5fc4 (diff)
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
-rw-r--r--crates/ignore/src/walk.rs68
1 files changed, 66 insertions, 2 deletions
diff --git a/crates/ignore/src/walk.rs b/crates/ignore/src/walk.rs
index 65606ee3..31c1d9f1 100644
--- a/crates/ignore/src/walk.rs
+++ b/crates/ignore/src/walk.rs
@@ -489,6 +489,7 @@ pub struct WalkBuilder {
sorter: Option<Sorter>,
threads: usize,
skip: Option<Arc<Handle>>,
+ filter: Option<Filter>,
}
#[derive(Clone)]
@@ -499,6 +500,9 @@ enum Sorter {
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("WalkBuilder")
@@ -531,6 +535,7 @@ impl WalkBuilder {
sorter: None,
threads: 0,
skip: None,
+ filter: None,
}
}
@@ -579,6 +584,7 @@ impl WalkBuilder {
ig: ig_root.clone(),
max_filesize: self.max_filesize,
skip: self.skip.clone(),
+ filter: self.filter.clone(),
}
}
@@ -597,6 +603,7 @@ impl WalkBuilder {
same_file_system: self.same_file_system,
threads: self.threads,
skip: self.skip.clone(),
+ filter: self.filter.clone(),
}
}
@@ -878,6 +885,23 @@ impl WalkBuilder {
}
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
@@ -893,6 +917,7 @@ pub struct Walk {
ig: Ignore,
max_filesize: Option<u64>,
skip: Option<Arc<Handle>>,
+ filter: Option<Filter>,
}
impl Walk {
@@ -925,6 +950,11 @@ impl Walk {
&ent.metadata().ok(),
));
}
+ if let Some(Filter(filter)) = &self.filter {
+ if !filter(ent) {
+ return Ok(true);
+ }
+ }
Ok(false)
}
}
@@ -1157,6 +1187,7 @@ pub struct WalkParallel {
same_file_system: bool,
threads: usize,
skip: Option<Arc<Handle>>,
+ filter: Option<Filter>,
}
impl WalkParallel {
@@ -1255,6 +1286,7 @@ impl WalkParallel {
max_filesize: self.max_filesize,
follow_links: self.follow_links,
skip: self.skip.clone(),
+ filter: self.filter.clone(),
};
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 and it has been requested to skip files identical to stdout.
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> {
@@ -1534,8 +1569,14 @@ impl<'s> Worker<'s> {
} else {
false
};
-
- if !should_skip_path && !should_skip_filesize {
+ let should_skip_filtered =
+ 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 });
}
WalkState::Continue
@@ -1793,6 +1834,7 @@ fn device_num<P: AsRef<Path>>(_: P) -> io::Result<u64> {
#[cfg(test)]
mod tests {
+ use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
@@ -2174,4 +2216,26 @@ mod tests {
let builder = WalkBuilder::new(&dir_path);
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"],
+ );
+ }
}