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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use std::ffi::OsString;
use std::fs::{FileType, Metadata};
use std::path::{Path, PathBuf};
use crate::pre::*;
/// Recursively walk a directory
pub fn walk(path: impl AsRef<Path>) -> crate::Result<Walk> {
walk_with(path, AlwaysRecurse)
}
pub fn walk_with<F>(path: impl AsRef<Path>, should_recurse: F) -> crate::Result<Walk<F>>
where
F: for<'a> WalkShouldRecursePredicate<WalkEntry<'a>>,
{
let path = path.as_ref().to_path_buf();
crate::trace!("walk '{}'", path.display());
let reader = crate::check!(
std::fs::read_dir(&path),
"cannot read directory '{}'",
path.display()
)?;
// reserve with initial capacity
#[allow(clippy::vec_init_then_push)]
let mut stack = Vec::with_capacity(4);
stack.push((reader, 1));
Ok(Walk {
root: path,
rel_containing: PathBuf::new(),
stack,
should_recurse,
})
}
pub struct Walk<F = AlwaysRecurse> {
root: PathBuf,
/// The path of the containing directory
/// of the current entry, relative from the root of the walk
rel_containing: PathBuf,
/// last element of the stack is the current directory being read
/// (dir, depth)
stack: Vec<(std::fs::ReadDir, usize)>,
should_recurse: F,
}
impl<F> Walk<F>
where
F: for<'b> WalkShouldRecursePredicate<WalkEntry<'b>>,
{
#[allow(clippy::should_implement_trait)]
// ^ iterator does not allow returning items referencing data from the iterator
pub fn next(&mut self) -> Option<crate::Result<WalkEntry<'_>>> {
loop {
let (dir, depth) = self.stack.last_mut()?;
// find next item in the current dir
let entry = match dir.next() {
None => {
// current directory is done, go back to parent
self.stack.pop();
self.rel_containing.pop();
continue;
}
Some(Err(e)) => {
return Some(Err(e).context(format!(
"failed to read directory entry while walking '{}'",
self.root.display()
)));
}
Some(Ok(entry)) => entry,
};
let file_type = match entry.file_type() {
Err(e) => {
return Some(Err(e).context(format!(
"failed to read directory entry type while walking '{}'",
self.root.display()
)));
}
Ok(x) => x,
};
let file_name = entry.file_name();
let depth = *depth;
if file_type.is_dir() {
let entry = WalkEntry {
root: &self.root,
file_type,
rel_containing: &self.rel_containing,
file_name,
depth,
entry,
};
if !self.should_recurse.should_recurse(&entry) {
continue;
}
// enter the directory
self.rel_containing.push(entry.file_name);
let dir = self.root.join(&self.rel_containing);
let read_dir = match std::fs::read_dir(dir) {
Err(e) => {
let rel_containing2 = self.rel_containing.display().to_string();
self.rel_containing.pop();
return Some(Err(e).context(format!(
"failed to read nested directory '{}' while walking '{}'",
rel_containing2,
self.root.display()
)));
}
Ok(read_dir) => read_dir,
};
self.stack.push((read_dir, depth + 1));
continue;
}
let entry = WalkEntry {
root: &self.root,
file_type,
rel_containing: &self.rel_containing,
file_name,
depth,
entry,
};
return Some(Ok(entry));
}
}
}
pub struct WalkEntry<'a> {
/// Root path of the walk
pub root: &'a Path,
/// Type of the entry
pub file_type: FileType,
/// The directory that contains the current entry, relative
/// to the root where the walk started, without the leading `./`.
pub rel_containing: &'a Path,
/// File name of the current entry being visited
pub file_name: OsString,
/// Depth of the current entry, compared to root.
/// This equals the number of segments in the relative path,
/// minimum 1 (when the entry is directly under root).
pub depth: usize,
/// Inner entry
entry: std::fs::DirEntry,
}
impl WalkEntry<'_> {
/// Get the path by joining the walk root and the relative
/// path of the entry
#[inline(always)]
pub fn path(&self) -> PathBuf {
self.entry.path()
}
/// Get the relative path of this entry, from the walk root
#[inline(always)]
pub fn rel_path(&self) -> PathBuf {
self.rel_containing.join(&self.file_name)
}
/// Check if the entry is a file. Convenience wrapper for `self.file_type.is_file()`
#[inline(always)]
pub fn is_file(&self) -> bool {
self.file_type.is_file()
}
/// Check if the entry is a directory. Convenience wrapper for `self.file_type.is_dir()`
#[inline(always)]
pub fn is_dir(&self) -> bool {
self.file_type.is_dir()
}
/// Check if the entry is a symlink. Convenience wrapper for `self.file_type.is_symlink()`
#[inline(always)]
pub fn is_symlink(&self) -> bool {
self.file_type.is_symlink()
}
/// Get the entry metadata
pub fn metadata(&self) -> crate::Result<Metadata> {
crate::check!(
self.entry.metadata(),
"failed to get metadata for file '{}' while walking directory '{}'",
self.rel_path().display(),
self.root.display()
)
}
}
pub trait WalkShouldRecursePredicate<E> {
fn should_recurse(&mut self, entry: &E) -> bool;
}
pub struct AlwaysRecurse;
impl<E> WalkShouldRecursePredicate<E> for AlwaysRecurse {
fn should_recurse(&mut self, _: &E) -> bool {
true
}
}
impl<'a, F> WalkShouldRecursePredicate<WalkEntry<'a>> for F
where
F: for<'b> Fn(&WalkEntry<'b>) -> bool,
{
fn should_recurse(&mut self, entry: &WalkEntry<'a>) -> bool {
(self)(entry)
}
}