Skip to main content

walkthrough/sync/
state.rs

1use std::{cmp::Ordering, fmt, fs, path::PathBuf, vec};
2
3use crate::{Ancestor, DirEntry, Error, Result, WalkDir, iter::WalkDirOptions};
4
5/// Synchronous state.
6#[derive(Debug)]
7pub struct Sync;
8
9struct LiveDirIter {
10    rd: fs::ReadDir,
11    path: PathBuf,
12    depth: usize,
13    follow_links: bool,
14}
15
16impl Iterator for LiveDirIter {
17    type Item = Result<DirEntry<Sync>>;
18
19    fn next(&mut self) -> Option<Self::Item> {
20        match self.rd.next()? {
21            Ok(raw) => Some(DirEntry::<Sync>::from_std(
22                &raw,
23                self.depth,
24                self.follow_links,
25            )),
26            Err(err) => Some(Err(Error::new_io_error(self.path.clone(), self.depth, err))),
27        }
28    }
29}
30
31enum DirStream {
32    Live(Box<LiveDirIter>),
33    Sorted(vec::IntoIter<Result<DirEntry<Sync>>>),
34}
35
36impl Iterator for DirStream {
37    type Item = Result<DirEntry<Sync>>;
38
39    fn next(&mut self) -> Option<Self::Item> {
40        match self {
41            DirStream::Live(iter) => iter.next(),
42            DirStream::Sorted(iter) => iter.next(),
43        }
44    }
45}
46
47impl fmt::Debug for DirStream {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            DirStream::Live(_) => f.write_str("DirStream::Live"),
51            DirStream::Sorted(_) => f.write_str("DirStream::Sorted"),
52        }
53    }
54}
55
56/// Stateful iterator produced by [`WalkDir`].
57#[derive(Debug)]
58pub struct Walker {
59    opts: WalkDirOptions<Sync>,
60    ancestors: Vec<Ancestor>,
61    stack: Vec<DirStream>,
62    start: Option<Result<DirEntry<Sync>>>,
63}
64
65impl IntoIterator for WalkDir {
66    type IntoIter = Walker;
67    type Item = Result<DirEntry<Sync>>;
68
69    fn into_iter(self) -> Self::IntoIter {
70        let start = DirEntry::<Sync>::from_path(self.root, 0, self.opts.follow_links);
71        Walker {
72            start: Some(start),
73            stack: vec![],
74            ancestors: vec![],
75            opts: self.opts,
76        }
77    }
78}
79
80impl Walker {
81    fn push_dir(&mut self, entry: &DirEntry<Sync>) -> Result<()> {
82        let depth = entry.depth();
83        // Truncating to `depth` evicts ancestors from any previous subtree at
84        // this level — the correct way to handle backtracking without explicit pops.
85        self.ancestors.truncate(depth);
86
87        if let Some(ancestor) = entry.ancestor() {
88            if self.ancestors.iter().any(|a| a == &ancestor) {
89                return Err(Error::loop_detected(entry.path().to_path_buf(), depth));
90            }
91            self.ancestors.push(ancestor);
92        }
93
94        let path = entry.path().to_path_buf();
95        let child_depth = depth + 1;
96        let follow_links = self.opts.follow_links;
97
98        if self.opts.sort_by.is_some() || self.opts.group_dir {
99            let entries = self.collect_sorted(&path, child_depth)?;
100            self.stack.push(DirStream::Sorted(entries.into_iter()));
101        } else {
102            let rd = fs::read_dir(&path)
103                .map_err(|err| Error::new_io_error(path.clone(), child_depth, err))?;
104            self.stack.push(DirStream::Live(Box::new(LiveDirIter {
105                rd,
106                path,
107                depth: child_depth,
108                follow_links,
109            })));
110        }
111        Ok(())
112    }
113
114    fn collect_sorted(
115        &mut self,
116        path: &std::path::Path,
117        depth: usize,
118    ) -> Result<Vec<Result<DirEntry<Sync>>>> {
119        let follow_links = self.opts.follow_links;
120
121        let rd = fs::read_dir(path)
122            .map_err(|err| Error::new_io_error(path.to_path_buf(), depth, err))?;
123
124        let mut entries: Vec<Result<DirEntry<Sync>>> = rd
125            .map(|res| {
126                res.map_err(|err| Error::new_io_error(path.to_path_buf(), depth, err))
127                    .and_then(|raw| DirEntry::<Sync>::from_std(&raw, depth, follow_links))
128            })
129            .collect();
130
131        if let Some(ref mut sorter) = self.opts.sort_by {
132            entries.sort_by(|a, b| match (a, b) {
133                (Ok(a), Ok(b)) => sorter.cmp(a, b),
134                (Err(_), Ok(_)) => Ordering::Less,
135                (Ok(_), Err(_)) => Ordering::Greater,
136                (Err(_), Err(_)) => Ordering::Equal,
137            });
138        }
139
140        if self.opts.group_dir {
141            entries.sort_by(|a, b| {
142                let a_dir = a.as_ref().is_ok_and(DirEntry::is_dir);
143                let b_dir = b.as_ref().is_ok_and(DirEntry::is_dir);
144                b_dir.cmp(&a_dir)
145            });
146        }
147
148        Ok(entries)
149    }
150}
151
152impl Iterator for Walker {
153    type Item = Result<DirEntry<Sync>>;
154
155    fn next(&mut self) -> Option<Self::Item> {
156        // Yield the root entry on the first call, descending into it if it is
157        // a directory and within the depth limit.
158        if let Some(res) = self.start.take() {
159            let entry = match res {
160                Err(err) => return Some(Err(err)),
161                Ok(e) => e,
162            };
163            if entry.is_dir()
164                && entry.depth() < self.opts.max_depth
165                && let Err(err) = self.push_dir(&entry)
166            {
167                return Some(Err(err));
168            }
169            if entry.depth() >= self.opts.min_depth {
170                return Some(Ok(entry));
171            }
172        }
173
174        loop {
175            let res = {
176                let stream = self.stack.last_mut()?;
177                match stream.next() {
178                    Some(res) => res,
179                    None => {
180                        self.stack.pop();
181                        continue;
182                    }
183                }
184            };
185
186            let entry = match res {
187                Err(err) => return Some(Err(err)),
188                Ok(e) => e,
189            };
190
191            if self.opts.skip_hidden && entry.is_hidden() {
192                continue;
193            }
194
195            if entry.is_dir()
196                && entry.depth() < self.opts.max_depth
197                && let Err(err) = self.push_dir(&entry)
198            {
199                return Some(Err(err));
200            }
201
202            if entry.depth() >= self.opts.min_depth {
203                return Some(Ok(entry));
204            }
205        }
206    }
207}