dir_iterator/
lib.rs

1//! Directory Iterator
2
3#![allow(dead_code)]
4
5mod filter;
6#[cfg(test)]
7mod test;
8
9use std::*;
10
11/// We deal with std::io::Error
12type Result<T> = io::Result<T>;
13
14#[derive(Default)]
15/// scan a directory recursively and access with iterator
16pub struct DirIterator {
17    /// current subdirectory (last is current folder)
18    stack: Vec<fs::ReadDir>,
19    /// configuration
20    config: DirIteratorConfig,
21}
22
23impl DirIterator {
24    /// Return an iterator builder aiming on current directory
25    pub fn current() -> DirIteratorBuilder {
26        Self::try_current().expect("invalid current directory")
27    }
28
29    /// Return an iterator builder aiming on current directory
30    pub fn try_current() -> Result<DirIteratorBuilder> {
31        Self::from_path(env::current_dir()?)
32    }
33
34    /// Scan current directory and return iterator
35    pub fn build_current() -> impl Iterator<Item = fs::DirEntry> {
36        Self::current().build()
37    }
38
39    /// Scan current directory and return iterator
40    pub fn try_build_current() -> Result<impl Iterator<Item = fs::DirEntry>> {
41        Ok(Self::try_current()?.build())
42    }
43
44    /// Return an iterator builder aiming on given directory
45    pub fn from_path(path: impl AsRef<path::Path>) -> Result<DirIteratorBuilder> {
46        Ok(DirIteratorBuilder(Self {
47            stack: vec![fs::read_dir(path)?],
48            ..Default::default()
49        }))
50    }
51
52    /// Scan given `path`` and return iterator
53    pub fn build_from_path(
54        path: impl AsRef<path::Path>,
55    ) -> Result<impl Iterator<Item = fs::DirEntry>> {
56        Ok(Self::from_path(path)?.build())
57    }
58
59    /// Create from `DirIteratorBuilder`
60    fn from_builder(builder: DirIteratorBuilder) -> Self {
61        builder.0
62    }
63}
64
65impl Iterator for DirIterator {
66    type Item = Result<fs::DirEntry>;
67
68    fn next(&mut self) -> Option<Self::Item> {
69        loop {
70            // get current `read_dir()` result on the stack
71            if let Some(it) = self.stack.last_mut() {
72                // get next item
73                match it.next() {
74                    // got one
75                    Some(Ok(item)) => {
76                        // check file type
77                        match item.file_type() {
78                            Ok(file_type) => {
79                                // ignore folders if configured
80                                if self.config.ignore.iter().any(|ignore| {
81                                    ignore.is_match(item.file_name().as_encoded_bytes())
82                                }) {
83                                    continue;
84                                }
85                                // Push new item on stack when the file entry is a directory
86                                if file_type.is_dir() {
87                                    match fs::read_dir(item.path()) {
88                                        Ok(dir_entry) => self.stack.push(dir_entry),
89                                        Err(err) => return Some(Err(err)),
90                                    }
91                                }
92                                // return next item
93                                return Some(Ok(item));
94                            }
95                            // report error in item
96                            Err(err) => return Some(Err(err)),
97                        }
98                    }
99                    None => {
100                        // finished with current `read_dir()` result
101                        self.stack.pop()?;
102                    }
103                    err => return err,
104                }
105            } else {
106                return None;
107            }
108        }
109    }
110}
111
112/// Configuration of a `DirIterator`
113#[derive(Default)]
114struct DirIteratorConfig {
115    /// If set do not scan directories which file name matches this wildcard
116    ignore: Vec<wc::Wildcard<'static>>,
117}
118
119/// Builder for configuration of `DirIterator`
120#[derive(Default)]
121pub struct DirIteratorBuilder(DirIterator);
122
123impl DirIteratorBuilder {
124    /// finish configuration and build a `DirIterator`
125    pub fn build(self) -> impl Iterator<Item = fs::DirEntry> {
126        DirIterator::from_builder(self).flatten()
127    }
128
129    /// configures to ignore folders by wildcard
130    pub fn ignore(mut self, wildcard: &'static str) -> Self {
131        self.0
132            .config
133            .ignore
134            .push(wc::Wildcard::new(wildcard.as_bytes()).expect("misformed wildcard"));
135        self
136    }
137}