recv_dir/
filter.rs

1//! # Filter
2//!
3//! Commony types for filtering paths to visit.
4//!
5//! ## Note
6//! There is a branch (`unstable/fn-traits`) that adds support for rust
7//! [`unboxed_closures`](https://doc.rust-lang.org/beta/unstable-book/language-features/unboxed-closures.html)
8//! and [`fn_traits`](https://doc.rust-lang.org/beta/unstable-book/library-features/fn-traits.html)
9//! which will solve the need for those separated types.
10//!
11
12use std::ffi::OsStr;
13use std::num::NonZeroUsize;
14use std::path::Path;
15
16/// Accepts all directories.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct Accept;
19
20/// Accepts all directories which are not symlinks.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct NoSymlink;
23
24/// Accepts directories up to a certain depth.
25///
26/// The depth 1 means that only the base directory is visited.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct MaxDepth(pub(crate) NonZeroUsize);
29
30impl MaxDepth {
31    const SINGLE_DEPTH: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1) };
32
33    /// Creates a max depth filter.
34    ///
35    /// This filter is dependant on the starting `dir`, it first retrieves
36    /// the depth of the `dir` (right from the root ancestor) and then
37    /// ensures that visited directories does not exceed the given depth
38    /// (`path to visit ancestor count - dir ancestor count`).
39    ///
40    /// You can also use [`Path::canonicalize`] to get the absolute path, which will also
41    /// produce a higher depth (if it is not already canonical), however, ensure to only pass
42    /// the canonicalized version to the visitor, otherwise, their depths will never match.
43    pub fn new(max_depth: NonZeroUsize) -> Self {
44        Self(max_depth)
45    }
46
47    /// Creates a single depth filter (i.e, only visits files of provided directory).
48    ///
49    /// This filter is dependant on the starting `dir`, it first retrieves
50    /// the depth of the `dir` (right from the root ancestor) and then
51    /// ensures that visited directories does not exceed the given depth
52    /// (`path to visit ancestor count - dir ancestor count`).
53    ///
54    /// You can also use [`Path::canonicalize`] to get the absolute path, which will also
55    /// produce a higher depth (if it is not already canonical), however, ensure to only pass
56    /// the canonicalized version to the visitor, otherwise, their depths will never match.
57    pub fn single_depth() -> Self {
58        Self(Self::SINGLE_DEPTH)
59    }
60}
61
62/// Closure based predicate.
63///
64/// ## Create
65///
66/// This type can be created through [`From`] and [`Into`] traits for any type,
67/// however, it only implements [`Filter`] when `F: Fn(&Path) -> bool`
68///
69/// You can also use [`Closure::new`] to create a closure for filter composition, or [`Closure::from_fn`]
70/// to avoid the need to introduce explicit types.
71#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
72pub struct Closure<F> {
73    closure: F,
74}
75
76/// Emits directories with the provided extension.
77///
78/// Note that this does not filter directories and only applies to the emission of paths
79/// from the [`Iterator`], in other words, directories that do not have the provided
80/// extension will be visited, but not emitted.
81#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
82pub struct Extension<S>(pub(crate) S);
83
84impl<S> Extension<S> {
85    pub fn new(extension: S) -> Self {
86        Self(extension)
87    }
88}
89
90pub trait Filter {
91    /// Checks whether `path_to_visit` should be visited or not. The `base_path` is
92    /// the initial directory provided to the visitor.
93    ///
94    /// This filter is called only for directories, if it returns `true`
95    /// it means that the directory should be visited, otherwise, it will
96    /// be skipped.
97    ///
98    /// The directory will be emitted by the iterator regardless of the
99    /// return value of this function.
100    fn filter(&self, base_path: &Path, path_to_visit: &Path) -> bool;
101
102    /// Checks whether `dir` should be emitted by the iterator.
103    fn should_emit(&self, _: &Path) -> bool {
104        true
105    }
106
107    /// Creates a filter that only accepts both this and the `other` condition.
108    fn and<B>(self, other: B) -> And<Self, B>
109    where
110        Self: Sized,
111        B: Filter,
112    {
113        And { a: self, b: other }
114    }
115
116    /// Creates a filter that accepts this or the `other` condition.
117    fn or<B>(self, other: B) -> Or<Self, B>
118    where
119        Self: Sized,
120        B: Filter,
121    {
122        Or { a: self, b: other }
123    }
124
125    /// Creates a filter that negates when this filter accepts.
126    fn not(self) -> Not<Self>
127    where
128        Self: Sized,
129    {
130        Not { a: self }
131    }
132}
133
134impl Filter for Accept {
135    fn filter(&self, _: &Path, _: &Path) -> bool {
136        true
137    }
138}
139
140impl Filter for NoSymlink {
141    fn filter(&self, _: &Path, p: &Path) -> bool {
142        !p.is_dir() || !p.is_symlink()
143    }
144}
145
146impl Filter for MaxDepth {
147    fn filter(&self, base_path: &Path, p: &Path) -> bool {
148        let depth = base_path.ancestors().count();
149        let MaxDepth(max_depth) = *self;
150        let ancestors = p.ancestors().count();
151        if ancestors >= depth {
152            ancestors - depth < max_depth.get()
153        } else {
154            false
155        }
156    }
157}
158
159impl<S> Filter for Extension<S>
160where
161    S: AsRef<OsStr>,
162{
163    fn filter(&self, _: &Path, _: &Path) -> bool {
164        true
165    }
166
167    fn should_emit(&self, dir: &Path) -> bool {
168        dir.extension() == Some(self.0.as_ref())
169    }
170}
171
172impl<F> Closure<F> {
173    pub fn new(closure: F) -> Self {
174        Self { closure }
175    }
176
177    pub fn from_fn(closure: F) -> Self
178    where
179        F: Fn(&Path) -> bool,
180    {
181        Self { closure }
182    }
183}
184
185impl<F> From<F> for Closure<F> {
186    fn from(f: F) -> Self {
187        Self { closure: f }
188    }
189}
190
191impl<F> Filter for Closure<F>
192where
193    F: Fn(&Path, &Path) -> bool,
194{
195    fn filter(&self, base_dir: &Path, directory: &Path) -> bool {
196        (self.closure)(base_dir, directory)
197    }
198}
199
200#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
201pub struct And<A, B> {
202    a: A,
203    b: B,
204}
205
206#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
207pub struct Or<A, B> {
208    a: A,
209    b: B,
210}
211
212#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
213pub struct Not<A> {
214    a: A,
215}
216
217#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
218pub struct This<A> {
219    a: A,
220}
221
222impl<A> This<A> {
223    pub fn new(a: A) -> Self {
224        Self { a }
225    }
226}
227
228impl<A, B> Filter for And<A, B>
229where
230    A: Filter,
231    B: Filter,
232{
233    fn filter(&self, base_path: &Path, directory: &Path) -> bool {
234        self.a.filter(base_path, directory) && self.b.filter(base_path, directory)
235    }
236
237    fn should_emit(&self, dir: &Path) -> bool {
238        self.a.should_emit(dir) && self.b.should_emit(dir)
239    }
240}
241
242impl<A, B> Filter for Or<A, B>
243where
244    A: Filter,
245    B: Filter,
246{
247    fn filter(&self, base_path: &Path, directory: &Path) -> bool {
248        self.a.filter(base_path, directory) || self.b.filter(base_path, directory)
249    }
250
251    fn should_emit(&self, dir: &Path) -> bool {
252        self.a.should_emit(dir) || self.b.should_emit(dir)
253    }
254}
255
256impl<A> Filter for Not<A>
257where
258    A: Filter,
259{
260    fn filter(&self, base_path: &Path, directory: &Path) -> bool {
261        !self.a.filter(base_path, directory)
262    }
263
264    fn should_emit(&self, dir: &Path) -> bool {
265        self.a.should_emit(dir)
266    }
267}
268
269impl<A> Filter for This<A>
270where
271    A: Filter,
272{
273    fn filter(&self, base_path: &Path, directory: &Path) -> bool {
274        self.a.filter(base_path, directory)
275    }
276
277    fn should_emit(&self, dir: &Path) -> bool {
278        self.a.should_emit(dir)
279    }
280}