pathwalker/
lib.rs

1use direntryfilter::{DirEntryFilter, DirectoryOnlyFilter, FileOnlyFilter, IgnoreDirEntry};
2use moar_options::*;
3#[cfg(feature = "pathfilter")]
4use pathfilter::{IgnorePath, PathFilter};
5use std::{
6    fs::{self, DirEntry},
7    path::PathBuf,
8};
9
10pub struct PathWalker {
11    directories: Vec<PathBuf>,
12    items: Vec<DirEntry>,
13    follow_symlinks: bool,
14    max_depth: Option<u64>,
15    current_depth: u64,
16    direntry_filters: Vec<DirEntryFilter>,
17    #[cfg(feature = "pathfilter")]
18    path_filters: Vec<PathFilter>,
19}
20
21impl PathWalker {
22    pub fn new<T: Into<PathBuf>>(path: T) -> Self {
23        Self {
24            directories: vec![path.into()],
25            items: Vec::new(),
26            follow_symlinks: false,
27            max_depth: None,
28            current_depth: 0,
29            direntry_filters: Vec::new(),
30            #[cfg(feature = "pathfilter")]
31            path_filters: Vec::new(),
32        }
33    }
34
35    pub fn follow_symlinks(mut self) -> Self {
36        self.follow_symlinks = true;
37        self
38    }
39
40    pub fn with_max_depth<T: Into<Option<u64>>>(mut self, max_depth: T) -> Self {
41        self.max_depth = max_depth.into();
42        self
43    }
44}
45
46impl Default for PathWalker {
47    fn default() -> Self {
48        Self::new(".")
49    }
50}
51
52#[cfg(feature = "pathfilter")]
53impl PathWalker {
54    pub fn with_filter(mut self, filter: PathFilter) -> Self {
55        self.path_filters.push(filter);
56        self
57    }
58
59    pub fn with_filters<T: AsRef<[PathFilter]>>(mut self, filters: T) -> Self {
60        self.path_filters.extend_from_slice(filters.as_ref());
61        self
62    }
63}
64
65impl PathWalker {
66    pub fn files_only(mut self) -> Self {
67        self.direntry_filters.push(FileOnlyFilter::default().into());
68        self
69    }
70
71    pub fn directories_only(mut self) -> Self {
72        self.direntry_filters
73            .push(DirectoryOnlyFilter::default().into());
74        self
75    }
76}
77
78impl PathWalker {
79    fn handle_entry(&mut self, entry: DirEntry) {
80        let entry_path = entry.path();
81
82        #[cfg(feature = "pathfilter")]
83        if self.path_filters.ignore(&entry_path) {
84            return;
85        }
86
87        if let Ok(file_type) = entry.file_type() {
88            if self.follow_symlinks || !file_type.is_symlink() {
89                if file_type.is_dir()
90                    && self
91                        .max_depth
92                        .owned_is_none_or(|max_depth| self.current_depth < max_depth)
93                {
94                    self.directories.push(entry_path);
95                    self.current_depth += 1;
96                }
97
98                if self.direntry_filters.ignore(&entry) {
99                    return;
100                }
101
102                self.items.push(entry);
103            }
104        };
105    }
106}
107
108impl Iterator for PathWalker {
109    type Item = DirEntry;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        match self.items.pop() {
113            Some(entry) => Some(entry),
114            None => {
115                while self.items.is_empty() && !self.directories.is_empty() {
116                    self.directories
117                        .pop()
118                        .map(fs::read_dir)
119                        .into_iter()
120                        .flatten()
121                        .flatten()
122                        .flatten()
123                        .for_each(|entry| self.handle_entry(entry))
124                }
125
126                self.items.pop()
127            }
128        }
129    }
130}