1use qp_trie::wrapper::{BStr, BString};
19
20type Trie<T> = qp_trie::Trie<BString, DirTrie<T>>;
21type DirTrie<T> = qp_trie::Trie<BString, Option<T>>;
22
23pub struct FileSystemTrieDirIter<'a, T>(FileSystemTrieDirIterInner<'a, T>);
24
25enum FileSystemTrieDirIterInner<'a, T> {
26 Direct(qp_trie::Iter<'a, BString, Option<T>>, usize),
27 Prefix(std::iter::Once<(&'a str, Option<&'a T>)>),
28}
29
30impl<'a, T> std::iter::FusedIterator for FileSystemTrieDirIter<'a, T> {}
31
32impl<'a, T> std::iter::ExactSizeIterator for FileSystemTrieDirIter<'a, T> {
33 fn len(&self) -> usize {
34 match &self.0 {
35 FileSystemTrieDirIterInner::Direct(_, len) => *len,
36 FileSystemTrieDirIterInner::Prefix(iter) => iter.len(),
37 }
38 }
39}
40
41impl<'a, T> Iterator for FileSystemTrieDirIter<'a, T> {
42 type Item = (&'a str, Option<&'a T>);
43 fn next(&mut self) -> Option<Self::Item> {
44 match &mut self.0 {
45 FileSystemTrieDirIterInner::Direct(iter, len) => {
46 *len = len.saturating_sub(1);
47 iter.next()
48 .map(|(key, value)| (key.as_str(), value.as_ref()))
49 }
50 FileSystemTrieDirIterInner::Prefix(iter) => iter.next(),
51 }
52 }
53}
54
55pub struct FileSystemTrieIter<'a, T> {
56 path: camino::Utf8PathBuf,
57 trie: &'a Trie<T>,
58 root_done: bool,
59 trie_iter: Option<qp_trie::Iter<'a, BString, DirTrie<T>>>,
60 dir_iter: Option<(camino::Utf8PathBuf, qp_trie::Iter<'a, BString, Option<T>>)>,
61}
62
63impl<'a, T> std::iter::FusedIterator for FileSystemTrieIter<'a, T> {}
64
65impl<'a, T> Iterator for FileSystemTrieIter<'a, T> {
66 type Item = (camino::Utf8PathBuf, &'a T);
67 fn next(&mut self) -> Option<Self::Item> {
68 loop {
69 if let Some((prefix, dir_iter)) = &mut self.dir_iter {
70 match dir_iter.next() {
71 Some((filename, Some(value))) => {
72 return Some((format!("{prefix}/{}", filename.as_str()).into(), value));
73 }
74 None => {
75 self.dir_iter = None;
76 self.root_done = true;
77 }
78 _ => {}
79 }
80 } else if let Some(trie_iter) = &mut self.trie_iter {
81 let (prefix, dir_trie) = trie_iter.next()?;
82 self.dir_iter = Some((prefix.as_str().to_owned().into(), dir_trie.iter()));
83 } else if self.path.as_str() == "" {
84 self.root_done = true;
85 self.trie_iter = Some(self.trie.iter())
86 } else if self.root_done {
87 self.trie_iter = Some(
88 self.trie
89 .iter_prefix::<BStr>(format!("{}/", self.path).as_str().into()),
90 );
91 } else {
92 self.dir_iter = self
93 .trie
94 .get_str(self.path.as_str())
95 .map(|t| (self.path.clone(), t.iter()));
96 }
97 }
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct FileSystemTrie<T>(Trie<T>);
104
105impl<T> Default for FileSystemTrie<T> {
106 fn default() -> Self {
107 Self(Default::default())
108 }
109}
110
111impl<T> FileSystemTrie<T> {
112 pub fn new() -> Self {
113 Default::default()
114 }
115
116 pub fn create_dir(&mut self, path: impl AsRef<camino::Utf8Path>) {
118 let path = path.as_ref();
119
120 if self.0.contains_key_str(path.as_str()) {
122 return;
123 }
124
125 let prefix = self.get_dir_prefix(path).as_str().to_string();
128
129 if !self.0.contains_key_str(&prefix) {
131 let mut dir_trie = DirTrie::new();
132 let prefix_with_trailing_slash = format!("{}/", &prefix);
133 if let Some((child_path, _)) = self
134 .0
135 .iter_prefix_str(if prefix.is_empty() {
136 ""
137 } else {
138 &prefix_with_trailing_slash
139 })
140 .next()
141 {
142 dir_trie.insert_str(
145 camino::Utf8Path::new(child_path.as_str())
146 .strip_prefix(&prefix)
147 .unwrap()
148 .iter()
149 .next()
150 .unwrap(),
151 None,
152 );
153 }
154 self.0.insert_str(&prefix, dir_trie);
155 }
156
157 if let Some(dirname) = path.strip_prefix(&prefix).unwrap().iter().next() {
159 let prefix_trie = self.0.get_mut_str(&prefix).unwrap();
160 prefix_trie.insert_str(dirname, None);
161 }
162
163 self.0.insert_str(path.as_str(), DirTrie::new());
165 }
166
167 pub fn create_file(&mut self, path: impl AsRef<camino::Utf8Path>, value: T) -> Option<T> {
171 let path = path.as_ref().to_owned();
172
173 let dir = path.parent().unwrap_or(camino::Utf8Path::new(""));
174 let filename = path.iter().next_back().unwrap();
175
176 self.create_dir(dir);
178 let dir_trie = self.0.get_mut_str(dir.as_str()).unwrap();
179
180 if let Some(option) = dir_trie.get_mut_str(filename) {
181 option.replace(value)
183 } else {
184 dir_trie.insert_str(filename, Some(value));
186 None
187 }
188 }
189
190 pub fn contains_dir(&self, path: impl AsRef<camino::Utf8Path>) -> bool {
192 let path = path.as_ref().as_str();
193 if path.is_empty() {
194 return true;
195 }
196 self.0.contains_key_str(path)
197 || (path.is_empty() && self.0.count() != 0)
198 || self
199 .0
200 .longest_common_prefix::<BStr>(format!("{path}/").as_str().into())
201 .as_str()
202 .len()
203 == path.len() + 1
204 }
205
206 pub fn contains_file(&self, path: impl AsRef<camino::Utf8Path>) -> bool {
208 let path = path.as_ref();
209 let Some(filename) = path.iter().next_back() else {
210 return false;
211 };
212 let dir = path.parent().unwrap_or(camino::Utf8Path::new(""));
213 self.0.get_str(dir.as_str()).map_or(false, |dir_trie| {
214 dir_trie
215 .get_str(filename)
216 .and_then(|o| o.as_ref())
217 .is_some()
218 })
219 }
220
221 pub fn contains(&self, path: impl AsRef<camino::Utf8Path>) -> bool {
223 self.contains_file(&path) || self.contains_dir(&path)
224 }
225
226 pub fn get_dir_size(&self, path: impl AsRef<camino::Utf8Path>) -> Option<usize> {
228 let path = path.as_ref().as_str();
229 if let Some(dir_trie) = self.0.get_str(path) {
230 Some(dir_trie.count())
231 } else if (path.is_empty() && self.0.count() != 0)
232 || self
233 .0
234 .longest_common_prefix::<BStr>(format!("{path}/").as_str().into())
235 .as_str()
236 .len()
237 == path.len() + 1
238 {
239 Some(1)
240 } else {
241 None
242 }
243 }
244
245 pub fn get_file(&self, path: impl AsRef<camino::Utf8Path>) -> Option<&T> {
247 let path = path.as_ref();
248 let filename = path.iter().next_back()?;
249 let dir = path.parent().unwrap_or(camino::Utf8Path::new(""));
250 self.0
251 .get_str(dir.as_str())
252 .and_then(|dir_trie| dir_trie.get_str(filename))
253 .and_then(|o| o.as_ref())
254 }
255
256 pub fn get_file_mut(&mut self, path: impl AsRef<camino::Utf8Path>) -> Option<&mut T> {
258 let path = path.as_ref();
259 let filename = path.iter().next_back()?;
260 let dir = path.parent().unwrap_or(camino::Utf8Path::new(""));
261 self.0
262 .get_mut_str(dir.as_str())
263 .and_then(|dir_trie| dir_trie.get_mut_str(filename))
264 .and_then(|o| o.as_mut())
265 }
266
267 pub fn get_or_create_file(&mut self, path: impl AsRef<camino::Utf8Path>, value: T) -> &T {
270 let path = path.as_ref();
271 if !self.contains_file(path) {
272 self.create_file(path, value);
273 }
274 self.get_file(path).unwrap()
275 }
276
277 pub fn get_or_create_file_with(
280 &mut self,
281 path: impl AsRef<camino::Utf8Path>,
282 f: impl FnOnce() -> T,
283 ) -> &T {
284 let path = path.as_ref();
285 if !self.contains_file(path) {
286 self.create_file(path, f());
287 }
288 self.get_file(path).unwrap()
289 }
290
291 pub fn get_or_create_file_mut(
294 &mut self,
295 path: impl AsRef<camino::Utf8Path>,
296 value: T,
297 ) -> &mut T {
298 let path = path.as_ref();
299 if !self.contains_file(path) {
300 self.create_file(path, value);
301 }
302 self.get_file_mut(path).unwrap()
303 }
304
305 pub fn get_or_create_file_with_mut(
308 &mut self,
309 path: impl AsRef<camino::Utf8Path>,
310 f: impl FnOnce() -> T,
311 ) -> &mut T {
312 let path = path.as_ref();
313 if !self.contains_file(path) {
314 self.create_file(path, f());
315 }
316 self.get_file_mut(path).unwrap()
317 }
318
319 pub fn get_dir_prefix(&self, path: impl AsRef<camino::Utf8Path>) -> &camino::Utf8Path {
321 let path = path.as_ref();
322 if self.0.contains_key_str(path.as_str()) {
323 return self
324 .0
325 .longest_common_prefix::<BStr>(path.as_str().into())
326 .as_str()
327 .into();
328 }
329 let prefix = self
330 .0
331 .longest_common_prefix::<BStr>(format!("{path}/").as_str().into())
332 .as_str();
333 let prefix = if !self.0.contains_key_str(prefix) || !path.starts_with(prefix) {
334 prefix.rsplit_once('/').map(|(s, _)| s).unwrap_or_default()
335 } else {
336 prefix
337 };
338 prefix.into()
339 }
340
341 pub fn remove_file(&mut self, path: impl AsRef<camino::Utf8Path>) -> Option<T> {
343 let path = path.as_ref();
344 let filename = path.iter().next_back()?;
345 let dir = path.parent().unwrap_or(camino::Utf8Path::new(""));
346 self.0
347 .get_mut_str(dir.as_str())
348 .and_then(|dir_trie| dir_trie.remove_str(filename))
349 .flatten()
350 }
351
352 pub fn remove_dir(&mut self, path: impl AsRef<camino::Utf8Path>) -> bool {
355 let path = path.as_ref();
356 let path_str = path.as_str();
357 if path_str.is_empty() {
358 self.0.clear();
359 true
360 } else if self.0.contains_key_str(path_str) {
361 self.0.remove_prefix_str(&format!("{path_str}/"));
362 self.0.remove_str(path_str);
363 if let Some(parent) = path.parent() {
364 self.create_dir(parent);
365 if let (Some(parent_trie), Some(dirname)) =
366 (self.0.get_mut_str(parent.as_str()), path.iter().next_back())
367 {
368 parent_trie.remove_str(dirname);
369 }
370 }
371 true
372 } else if self
373 .0
374 .longest_common_prefix::<BStr>(format!("{path_str}/").as_str().into())
375 .as_str()
376 .len()
377 == path_str.len() + 1
378 {
379 self.0.remove_prefix_str(&format!("{path_str}/"));
380 if let Some(parent) = path.parent() {
381 self.create_dir(parent);
382 if let (Some(parent_trie), Some(dirname)) =
383 (self.0.get_mut_str(parent.as_str()), path.iter().next_back())
384 {
385 parent_trie.remove_str(dirname);
386 }
387 }
388 true
389 } else {
390 false
391 }
392 }
393
394 pub fn iter_dir(
398 &self,
399 path: impl AsRef<camino::Utf8Path>,
400 ) -> Option<FileSystemTrieDirIter<'_, T>> {
401 let path = path.as_ref();
402 let prefix_with_trailing_slash = format!("{}/", path.as_str());
403 if let Some(dir_trie) = self.0.get_str(path.as_str()) {
404 Some(FileSystemTrieDirIter(FileSystemTrieDirIterInner::Direct(
405 dir_trie.iter(),
406 dir_trie.count(),
407 )))
408 } else if let Some((key, _)) = self
409 .0
410 .iter_prefix_str(if path.as_str().is_empty() {
411 ""
412 } else {
413 &prefix_with_trailing_slash
414 })
415 .next()
416 {
417 Some(FileSystemTrieDirIter(FileSystemTrieDirIterInner::Prefix(
418 std::iter::once((
419 camino::Utf8Path::new(key.as_str())
420 .strip_prefix(path)
421 .unwrap()
422 .iter()
423 .next()
424 .unwrap(),
425 None,
426 )),
427 )))
428 } else {
429 None
430 }
431 }
432
433 pub fn iter_prefix(
436 &self,
437 path: impl AsRef<camino::Utf8Path>,
438 ) -> Option<FileSystemTrieIter<'_, T>> {
439 let path = path.as_ref();
440 if self.contains_dir(path) {
441 Some(FileSystemTrieIter {
442 path: path.to_owned(),
443 trie: &self.0,
444 root_done: false,
445 trie_iter: None,
446 dir_iter: None,
447 })
448 } else {
449 None
450 }
451 }
452}