dir_iterator/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Directory Iterator

#![allow(dead_code)]

mod filter;
#[cfg(test)]
mod test;

use std::*;

/// We deal with std::io::Error
type Result<T> = io::Result<T>;

/// scan a directory recursively and access with iterator
pub struct DirIterator {
    /// current subdirectory (last is current folder)
    stack: Vec<fs::ReadDir>,
    /// configuration
    config: DirIteratorConfig,
}

impl DirIterator {
    /// Return an iterator builder aiming on current directory
    pub fn current() -> DirIteratorBuilder {
        Self::from_path(env::current_dir().expect("could not retrieve current dir"))
    }

    /// Scan current directory and return result as iterator
    pub fn build_current() -> impl Iterator<Item = fs::DirEntry> {
        Self::current().build().expect("path not found")
    }

    /// Return an iterator builder aiming on given directory
    pub fn from_path(path: impl AsRef<path::Path>) -> DirIteratorBuilder {
        DirIteratorBuilder {
            path: path.as_ref().to_path_buf(),
            ..Default::default()
        }
    }

    /// Scan given `path`` and return result as iterator
    pub fn build_from_path(
        path: impl AsRef<path::Path>,
    ) -> Result<impl Iterator<Item = fs::DirEntry>> {
        Self::from_path(path).build()
    }

    /// Create from `DirIteratorBuilder`
    fn from_builder(builder: DirIteratorBuilder) -> Result<Self> {
        Ok(Self {
            stack: vec![fs::read_dir(builder.path)?],
            config: builder.config,
        })
    }
}

impl Iterator for DirIterator {
    type Item = Result<fs::DirEntry>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            // get current `read_dir()` result on the stack
            if let Some(it) = self.stack.last_mut() {
                // get next item
                match it.next() {
                    // got one
                    Some(Ok(item)) => {
                        // check file type
                        match item.file_type() {
                            Ok(file_type) => {
                                // ignore folders if configured
                                if self.config.ignore.iter().any(|ignore| {
                                    ignore.is_match(item.file_name().as_encoded_bytes())
                                }) {
                                    continue;
                                }
                                // Push new item on stack when the file entry is a directory
                                if file_type.is_dir() {
                                    match fs::read_dir(item.path()) {
                                        Ok(dir_entry) => self.stack.push(dir_entry),
                                        Err(err) => return Some(Err(err)),
                                    }
                                }
                                // return next item
                                return Some(Ok(item));
                            }
                            // report error in item
                            Err(err) => return Some(Err(err)),
                        }
                    }
                    None => {
                        // finished with current `read_dir()` result
                        self.stack.pop()?;
                    }
                    err => return err,
                }
            } else {
                return None;
            }
        }
    }
}

/// Configuration of a `DirIterator`
#[derive(Default)]
struct DirIteratorConfig {
    /// If set do not scan directories which file name matches this wildcard
    ignore: Vec<wc::Wildcard<'static>>,
}

/// Builder for configuration of `DirIterator`
#[derive(Default)]
pub struct DirIteratorBuilder {
    /// Path to scan
    path: path::PathBuf,
    /// Scanner configuration
    config: DirIteratorConfig,
}

impl DirIteratorBuilder {
    /// finish configuration and build a `DirIterator`
    pub fn build(self) -> Result<impl Iterator<Item = fs::DirEntry>> {
        Ok(DirIterator::from_builder(self)?.flatten())
    }

    /// configures to ignore folders by wildcard
    pub fn ignore(mut self, wildcard: &'static str) -> Self {
        self.config
            .ignore
            .push(wc::Wildcard::new(wildcard.as_bytes()).expect("misformed wildcard"));
        self
    }
}