1use crate::system::SystemPathBuf;
2use std::fmt::{Display, Formatter};
3use std::path::PathBuf;
4
5use super::{FileType, SystemPath};
6
7pub trait IgnoreIncremental {
9 fn is_ignored(&mut self, path: &SystemPath, is_directory: bool) -> bool;
11}
12
13pub struct WalkDirectoryBuilder {
15 walker: Box<dyn DirectoryWalker>,
17
18 paths: Vec<SystemPathBuf>,
20
21 ignore_hidden: bool,
22
23 standard_filters: bool,
24}
25
26impl WalkDirectoryBuilder {
27 pub fn new<W>(path: impl AsRef<SystemPath>, walker: W) -> Self
28 where
29 W: DirectoryWalker + 'static,
30 {
31 Self {
32 walker: Box::new(walker),
33 paths: vec![path.as_ref().to_path_buf()],
34 ignore_hidden: true,
35 standard_filters: true,
36 }
37 }
38
39 #[expect(clippy::should_implement_trait)]
45 pub fn add(mut self, path: impl AsRef<SystemPath>) -> Self {
46 self.paths.push(path.as_ref().to_path_buf());
47 self
48 }
49
50 pub fn ignore_hidden(mut self, hidden: bool) -> Self {
56 self.ignore_hidden = hidden;
57 self
58 }
59
60 pub fn standard_filters(mut self, standard_filters: bool) -> Self {
68 self.standard_filters = standard_filters;
69 self.ignore_hidden = standard_filters;
70
71 self
72 }
73
74 pub fn incremental_matcher(self) -> Box<dyn IgnoreIncremental> {
76 let configuration = WalkDirectoryConfiguration {
77 paths: self.paths,
78 ignore_hidden: self.ignore_hidden,
79 standard_filters: self.standard_filters,
80 };
81 self.walker.incremental_matcher(configuration)
82 }
83
84 pub fn run<'s, F>(self, builder: F)
87 where
88 F: FnMut() -> FnVisitor<'s>,
89 {
90 self.visit(&mut FnBuilder { builder });
91 }
92
93 pub fn visit(self, builder: &mut dyn WalkDirectoryVisitorBuilder) {
96 let configuration = WalkDirectoryConfiguration {
97 paths: self.paths,
98 ignore_hidden: self.ignore_hidden,
99 standard_filters: self.standard_filters,
100 };
101
102 self.walker.walk(builder, configuration);
103 }
104}
105
106pub trait DirectoryWalker {
108 fn walk(
109 &self,
110 builder: &mut dyn WalkDirectoryVisitorBuilder,
111 configuration: WalkDirectoryConfiguration,
112 );
113
114 fn incremental_matcher(
116 &self,
117 configuration: WalkDirectoryConfiguration,
118 ) -> Box<dyn IgnoreIncremental>;
119}
120
121pub trait WalkDirectoryVisitorBuilder<'s> {
123 fn build(&mut self) -> Box<dyn WalkDirectoryVisitor + 's>;
124}
125
126pub trait WalkDirectoryVisitor: Send {
128 fn visit(&mut self, entry: std::result::Result<DirectoryEntry, Error>) -> WalkState;
129}
130
131struct FnBuilder<F> {
132 builder: F,
133}
134
135impl<'s, F> WalkDirectoryVisitorBuilder<'s> for FnBuilder<F>
136where
137 F: FnMut() -> FnVisitor<'s>,
138{
139 fn build(&mut self) -> Box<dyn WalkDirectoryVisitor + 's> {
140 let visitor = (self.builder)();
141 Box::new(FnVisitorImpl(visitor))
142 }
143}
144
145type FnVisitor<'s> =
146 Box<dyn FnMut(std::result::Result<DirectoryEntry, Error>) -> WalkState + Send + 's>;
147
148struct FnVisitorImpl<'s>(FnVisitor<'s>);
149
150impl WalkDirectoryVisitor for FnVisitorImpl<'_> {
151 fn visit(&mut self, entry: std::result::Result<DirectoryEntry, Error>) -> WalkState {
152 (self.0)(entry)
153 }
154}
155
156pub struct WalkDirectoryConfiguration {
157 pub paths: Vec<SystemPathBuf>,
158 pub ignore_hidden: bool,
159 pub standard_filters: bool,
160}
161
162#[derive(Debug, Clone)]
164pub struct DirectoryEntry {
165 pub(super) path: SystemPathBuf,
166 pub(super) file_type: FileType,
167 pub(super) depth: usize,
168}
169
170impl DirectoryEntry {
171 pub fn path(&self) -> &SystemPath {
173 &self.path
174 }
175
176 pub fn into_path(self) -> SystemPathBuf {
179 self.path
180 }
181
182 pub fn file_type(&self) -> FileType {
184 self.file_type
185 }
186
187 pub fn depth(&self) -> usize {
189 self.depth
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
194pub enum WalkState {
195 Continue,
197
198 Skip,
201
202 Quit,
207}
208
209pub struct Error {
210 pub(super) depth: Option<usize>,
211 pub(super) kind: ErrorKind,
212}
213
214impl Error {
215 pub fn depth(&self) -> Option<usize> {
216 self.depth
217 }
218
219 pub fn kind(&self) -> &ErrorKind {
220 &self.kind
221 }
222}
223
224impl Display for Error {
225 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
226 match &self.kind {
227 ErrorKind::Loop { ancestor, child } => {
228 write!(
229 f,
230 "File system loop found: {child} points to an ancestor {ancestor}",
231 )
232 }
233 ErrorKind::Io {
234 path: Some(path),
235 err,
236 } => {
237 write!(f, "IO error for operation on {path}: {err}")
238 }
239 ErrorKind::Io { path: None, err } => err.fmt(f),
240 ErrorKind::NonUtf8Path { path } => {
241 write!(f, "Non-UTF8 path: {}", path.display())
242 }
243 }
244 }
245}
246
247impl std::fmt::Debug for Error {
248 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
249 std::fmt::Display::fmt(self, f)
250 }
251}
252
253impl std::error::Error for Error {}
254
255#[derive(Debug)]
256pub enum ErrorKind {
257 Loop {
260 ancestor: SystemPathBuf,
261 child: SystemPathBuf,
262 },
263
264 Io {
266 path: Option<SystemPathBuf>,
267 err: std::io::Error,
268 },
269
270 NonUtf8Path { path: PathBuf },
272}
273
274#[cfg(test)]
275pub(super) mod tests {
276 use crate::system::walk_directory::{DirectoryEntry, Error};
277 use crate::system::{FileType, SystemPathBuf};
278 use std::collections::BTreeMap;
279
280 pub(crate) struct DirectoryEntryToString {
282 root_path: SystemPathBuf,
283 inner: std::sync::Mutex<DirectoryEntryToStringInner>,
284 }
285
286 impl DirectoryEntryToString {
287 pub(crate) fn new(root_path: SystemPathBuf) -> Self {
288 Self {
289 root_path,
290 inner: std::sync::Mutex::new(DirectoryEntryToStringInner::default()),
291 }
292 }
293
294 pub(crate) fn write_entry(&self, entry: Result<DirectoryEntry, Error>) {
295 let mut inner = self.inner.lock().unwrap();
296 let DirectoryEntryToStringInner { errors, visited } = &mut *inner;
297
298 match entry {
299 Ok(entry) => {
300 let relative_path = entry
301 .path()
302 .strip_prefix(&self.root_path)
303 .unwrap_or(entry.path());
304
305 let unix_path = relative_path
306 .components()
307 .map(|component| component.as_str())
308 .collect::<Vec<_>>()
309 .join("/");
310
311 visited.insert(unix_path, (entry.file_type, entry.depth));
312 }
313 Err(error) => {
314 errors.push_str(&error.to_string());
315 errors.push('\n');
316 }
317 }
318 }
319 }
320
321 impl std::fmt::Display for DirectoryEntryToString {
322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323 let inner = self.inner.lock().unwrap();
324 write!(f, "{paths:#?}", paths = inner.visited)?;
325
326 if !inner.errors.is_empty() {
327 writeln!(f, "\n\n{errors}", errors = inner.errors).unwrap();
328 }
329
330 Ok(())
331 }
332 }
333
334 #[derive(Default)]
335 struct DirectoryEntryToStringInner {
336 errors: String,
337 visited: BTreeMap<String, (FileType, usize)>,
339 }
340}