1use std::path::{Component, Path, PathBuf};
4
5use crate::core::utils;
6use super::node::DirNode;
7use crate::{DSError, Result};
8
9pub struct FileTree {
42 root: DirNode,
43}
44
45impl FileTree {
46 pub fn new() -> Self {
48 Self {
49 root: DirNode::new(),
50 }
51 }
52
53 pub fn is_empty(&self) -> bool {
57 self.root.is_empty()
58 }
59
60 pub fn contains_file<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
64 let path = path.as_ref();
65 if path.as_os_str() == "" {
66 return Err(DSError::EmptyPath);
67 }
68 if !path.is_absolute() {
69 return Err(DSError::NotAbsolutePath {
70 path: path.to_owned(),
71 });
72 }
73 if path.as_os_str() == "/" {
74 return Err(DSError::NotFile {
75 path: path.to_owned(),
76 });
77 }
78
79 let (dir, file) = utils::split_path(path);
80 let dir = dir.ok_or(DSError::WrongPath { path: path.to_owned() })?;
81
82 Ok(self.check_path(dir, file))
83 }
84
85 pub fn contains_dir<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
89 let path = path.as_ref();
90 if path.as_os_str() == "" {
91 return Err(DSError::EmptyPath);
92 }
93 if !path.is_absolute() {
94 return Err(DSError::NotAbsolutePath {
95 path: path.to_owned(),
96 });
97 }
98 if path.as_os_str() == "/" {
99 return Ok(true);
100 }
101
102 Ok(self.check_path(path, None))
103 }
104
105 pub fn add_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
112 let path = path.as_ref();
113 if path.as_os_str() == "" {
114 return Err(DSError::EmptyPath);
115 }
116 if !path.is_absolute() {
117 return Err(DSError::NotAbsolutePath {
118 path: path.to_owned(),
119 });
120 }
121 if path.as_os_str() == "/" {
122 return Ok(());
123 }
124
125 let _ = self.ensure_dirs(path);
127
128 Ok(())
129 }
130
131 pub fn add_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
138 let path = path.as_ref();
139 if path.as_os_str() == "" {
140 return Err(DSError::EmptyPath);
141 }
142 if !path.is_absolute() {
143 return Err(DSError::NotAbsolutePath {
144 path: path.to_owned(),
145 });
146 }
147 if path.as_os_str() == "/" {
148 return Err(DSError::NotFile {
149 path: path.to_owned(),
150 });
151 }
152
153 let (dir, file) = utils::split_path(path);
154
155 if let Some(dir) = dir {
157 let parent_dir = self.ensure_dirs(dir);
158
159 if let Some(file) = file {
161 parent_dir.insert_file(file);
162 }
163 }
164
165 Ok(())
166 }
167
168 pub fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
170 let path = path.as_ref();
171 if path.as_os_str() == "" {
172 return Err(DSError::EmptyPath);
173 }
174 if !path.is_absolute() {
175 return Err(DSError::NotAbsolutePath {
176 path: path.to_owned(),
177 });
178 }
179 if path.as_os_str() == "/" {
180 return Err(DSError::NotFile {
181 path: path.to_owned(),
182 });
183 }
184
185 let (dir, file) = utils::split_path(path);
186 let dir = dir.ok_or(DSError::WrongPath { path: path.to_owned() })?;
187 let file_name = file.ok_or(DSError::NotFile { path: path.to_owned() })?;
188
189 let parent = self.find_dir(dir)?;
191
192 if !parent.files_contains(&file_name) {
194 return Err(DSError::PathNotFound {
196 path: path.to_owned(),
197 });
198 }
199 parent.remove_file(&file_name);
200
201 Ok(())
202 }
203
204 pub fn remove_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
206 let path = path.as_ref();
207 if path.as_os_str() == "" {
208 return Err(DSError::EmptyPath);
209 }
210 if !path.is_absolute() {
211 return Err(DSError::NotAbsolutePath {
212 path: path.to_owned(),
213 });
214 }
215 if path.as_os_str() == "/" {
216 return Err(DSError::NotFile {
217 path: path.to_owned(),
218 });
219 }
220
221 let (parent, child) = utils::split_path(path);
222 let parent = parent.ok_or(DSError::WrongPath { path: path.to_owned() })?;
223 let child = child.ok_or(DSError::NotDirectory { path: path.to_owned() })?;
224
225 let parent = self.find_dir(parent)?;
227
228 if !parent.dirs_contains(child) {
230 return Err(DSError::PathNotFound {
232 path: path.to_owned(),
233 });
234 }
235 parent.remove_dir(child);
236
237 Ok(())
238 }
239
240 pub fn clear(&mut self) {
244 self.root.clear();
245 }
246
247 pub fn visit(&self, mut visitor: impl FnMut(&Path)) {
249 fn visit_recursive(parent: &Path, current: &DirNode, visitor: &mut impl FnMut(&Path)) {
250 for name in current.files_iter() {
251 visitor(parent.join(Path::new(name)).as_path())
252 }
253 for (name, sub_childs) in current.dirs_iter() {
254 let parent = parent.join(Path::new(name));
255 if sub_childs.is_empty() {
256 visitor(&parent);
257 } else {
258 visit_recursive(&parent, sub_childs, visitor);
259 }
260 }
261 }
262
263 let parent = Path::new("/");
264
265 visit_recursive(parent, &self.root, &mut visitor);
266 }
267
268 fn check_path(
269 &self,
270 dir_path: &Path,
271 file_name: Option<&str>,
272 ) -> bool {
273 if self.is_empty() {
274 return false;
275 }
276
277 let mut current = &self.root;
278 let mut is_found = true;
279
280 let components = dir_path.components().skip(1);
283 for component in components {
284 let name = utils::path_comp_to_str(&component);
285 if let Some(next) = current.get_dir(name) {
286 current = next;
287 } else {
288 is_found = false;
289 break;
290 }
291 }
292
293 if let Some(file_name) = file_name && is_found {
295 if !current.files_contains(file_name) {
296 is_found = false;
297 }
298 }
299
300 is_found
301 }
302
303 fn ensure_dirs(&mut self, path: &Path) -> &mut DirNode {
304 let mut current = &mut self.root;
305
306 let components = path.components().skip(1);
308 for component in components {
309 let name = utils::path_comp_to_str(&component);
310 current.insert_dir(name);
311 current = current.get_dir_mut(name).unwrap(); }
313
314 current
315 }
316
317 fn find_dir(&mut self, path: &Path) -> Result<&mut DirNode> {
320 let mut current = &mut self.root;
321
322 let components = path.components().skip(1);
324 for component in components {
325 let name = utils::path_comp_to_str(&component);
326
327 if let Some(dir) = current.get_dir_mut(name) {
328 current = dir;
329 } else {
330 return Err(DSError::PathNotFound { path: path.to_owned() });
331 }
332 }
333
334 Ok(current)
335 }
336
337 fn build_path(&self, components: &[Component<'_>]) -> PathBuf {
339 let mut pb = PathBuf::from("/");
340 for component in components {
341 pb.push(component);
342 }
343 pb
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 mod add {
352 use super::*;
353
354 #[test]
356 fn test_add_simple_dir() {
357 let mut tree = FileTree::new();
358
359 assert!(tree.add_dir("/home").is_ok());
361
362 assert!(tree.root.dirs_contains("home"));
364 }
365
366 #[test]
368 fn test_add_nested_dirs() {
369 let mut tree = FileTree::new();
370
371 assert!(tree.add_dir(Path::new("/home/user/documents")).is_ok());
373
374 let home = tree.root.get_dir("home").unwrap();
376 let user = home.get_dir("user").unwrap();
377 assert!(user.dirs_contains("documents"));
378 }
379
380 #[test]
382 fn test_add_root_dir() {
383 let mut tree = FileTree::new();
384
385 assert!(tree.add_dir(Path::new("/")).is_ok());
387
388 assert!(tree.root.is_empty());
390 }
391
392 #[test]
394 fn test_add_file_to_existing_dirs() {
395 let mut tree = FileTree::new();
396
397 assert!(tree.add_dir(Path::new("/home/user")).is_ok());
399 assert!(tree.add_file(Path::new("/home/user/document.txt")).is_ok());
401
402 let home = tree.root.get_dir("home").unwrap();
404 let user = home.get_dir("user").unwrap();
405 assert!(user.files_contains("document.txt"));
406 }
407
408 #[test]
410 fn test_add_file_creates_intermediate_dirs() {
411 let mut tree = FileTree::new();
412
413 assert!(tree.add_file(Path::new("/projects/rust/main.rs")).is_ok());
415
416 let projects = tree
418 .root
419 .get_dir("projects")
420 .unwrap();
421 let rust = projects.get_dir("rust").unwrap();
422 assert!(rust.files_contains("main.rs"));
423 }
424
425 #[test]
427 fn test_add_non_absolute_dir_path_error() {
428 let mut tree = FileTree::new();
429
430 let result = tree.add_dir(Path::new("relative/path"));
432 assert!(result.is_err());
433 if let Err(DSError::NotAbsolutePath { path }) = result {
434 assert_eq!(path, PathBuf::from("relative/path"));
435 } else {
436 panic!("Expected NotAbsolutePath error");
437 }
438 }
439
440 #[test]
442 fn test_add_non_absolute_file_path_error() {
443 let mut tree = FileTree::new();
444
445 let result = tree.add_file(Path::new("document.txt"));
447 assert!(result.is_err());
448 if let Err(DSError::NotAbsolutePath { path }) = result {
449 assert_eq!(path, PathBuf::from("document.txt"));
450 } else {
451 panic!("Expected NotAbsolutePath error");
452 }
453 }
454
455 #[test]
457 fn test_add_multiple_files_same_dir() {
458 let mut tree = FileTree::new();
459
460 assert!(tree.add_file(Path::new("/tmp/file1.txt")).is_ok());
462 assert!(tree.add_file(Path::new("/tmp/file2.txt")).is_ok());
463 assert!(tree.add_file(Path::new("/tmp/script.sh")).is_ok());
464
465 let tmp = tree.root.get_dir("tmp").unwrap();
467 assert!(tmp.files_contains("file1.txt"));
468 assert!(tmp.files_contains("file2.txt"));
469 assert!(tmp.files_contains("script.sh"));
470 }
471
472 #[test]
474 fn test_idempotent_add_operations() {
475 let mut tree = FileTree::new();
476
477 assert!(tree.add_dir(Path::new("/var/log")).is_ok());
479 assert!(tree.add_dir(Path::new("/var/log")).is_ok()); assert!(tree.add_file(Path::new("/var/log/system.log")).is_ok());
483 assert!(tree.add_file(Path::new("/var/log/system.log")).is_ok());
485
486 let var = tree.root.get_dir("var").unwrap();
488 let log = var.get_dir("log").unwrap();
489 assert!(log.files_contains("system.log"));
490 }
491
492 #[test]
494 fn test_add_path_with_special_chars() {
495 let mut tree = FileTree::new();
496
497 assert!(tree.add_dir(Path::new("/special-@#$%/test")).is_ok());
499
500 let special = tree
502 .root
503 .get_dir("special-@#$%")
504 .unwrap();
505 assert!(special.dirs_contains("test"));
506 }
507
508 #[test]
510 fn test_empty_path_handling() {
511 let mut tree = FileTree::new();
512
513 let empty_path = Path::new("");
515 let result = tree.add_file(empty_path);
516 assert!(result.is_err());
517 if let Err(DSError::EmptyPath) = result {
518 } else {
520 panic!("Expected EmptyPath error for empty path");
521 }
522 }
523 }
524
525 mod contains {
526 use super::*;
527
528 #[test]
530 fn test_contains_root_path() {
531 let tree = FileTree::new();
532
533 assert_eq!(tree.contains_dir("/"), Ok(true));
534 assert_eq!(
535 tree.contains_file("/"),
536 Err(DSError::NotFile {
537 path: PathBuf::from("/")
538 })
539 ); }
541
542 #[test]
544 fn test_contains_simple_dir() {
545 let mut tree = FileTree::new();
546 tree.add_dir(Path::new("/home")).unwrap();
547
548 assert_eq!(tree.contains_dir("/home"), Ok(true));
549 assert_eq!(tree.contains_file("/home"), Ok(false)); }
551
552 #[test]
554 fn test_contains_nested_dir() {
555 let mut tree = FileTree::new();
556 tree.add_dir(Path::new("/home/user/documents")).unwrap();
557
558 assert_eq!(tree.contains_dir("/home"), Ok(true));
559 assert_eq!(tree.contains_dir("/home/user"), Ok(true));
560 assert_eq!(tree.contains_dir("/home/user/documents"), Ok(true));
561 }
562
563 #[test]
565 fn test_contains_file() {
566 let mut tree = FileTree::new();
567 tree.add_file(Path::new("/home/user/document.txt")).unwrap();
568
569 assert_eq!(tree.contains_file("/home/user/document.txt"), Ok(true));
570 assert_eq!(tree.contains_dir("/home/user/document.txt"), Ok(false)); }
572
573 #[test]
575 fn test_contains_nonexistent_path() {
576 let tree = FileTree::new();
577
578 assert_eq!(tree.contains_dir("/nonexistent"), Ok(false));
579 assert_eq!(tree.contains_file("/home/user/file.txt"), Ok(false));
580 }
581
582 #[test]
584 fn test_contains_file_in_nonexistent_dir() {
585 let mut tree = FileTree::new();
586 tree.add_dir(Path::new("/home")).unwrap();
588
589 assert_eq!(tree.contains_file("/home/user/document.txt"), Ok(false));
591
592 assert_eq!(tree.contains_dir("/home/user"), Ok(false));
594 }
595
596 #[test]
598 fn test_contains_empty_path_error() {
599 let tree = FileTree::new();
600
601 let result = tree.contains_dir("");
602 assert!(result.is_err());
603 if let Err(DSError::EmptyPath) = result {
604 } else {
606 panic!("Expected EmptyPath error for empty path");
607 }
608 }
609
610 #[test]
612 fn test_contains_non_absolute_path_error() {
613 let tree = FileTree::new();
614
615 let result = tree.contains_dir("relative/path");
616 assert!(result.is_err());
617 if let Err(DSError::NotAbsolutePath { path }) = result {
618 assert_eq!(path, PathBuf::from("relative/path"));
619 } else {
620 panic!("Expected NotAbsolutePath error");
621 }
622 }
623
624 #[test]
626 fn test_contains_multiple_paths_complex_tree() {
627 let mut tree = FileTree::new();
628
629 tree.add_dir(Path::new("/etc")).unwrap();
631 tree.add_dir(Path::new("/var/log")).unwrap();
632 tree.add_file(Path::new("/etc/config")).unwrap();
633 tree.add_file(Path::new("/var/log/system.log")).unwrap();
634
635 assert_eq!(tree.contains_dir("/etc"), Ok(true));
637 assert_eq!(tree.contains_dir("/var"), Ok(true));
638 assert_eq!(tree.contains_dir("/var/log"), Ok(true));
639 assert_eq!(tree.contains_file("/etc/config"), Ok(true));
640 assert_eq!(tree.contains_file("/var/log/system.log"), Ok(true));
641 assert_eq!(tree.contains_file("/etc/passwd"), Ok(false));
642 assert_eq!(tree.contains_dir("/tmp"), Ok(false));
643 }
644
645 #[test]
647 fn test_contains_path_with_special_chars() {
648 let mut tree = FileTree::new();
649
650 tree.add_dir(Path::new("/special-@#$%")).unwrap();
651 tree.add_file(Path::new("/special-@#$%/test.file")).unwrap();
652
653 assert_eq!(tree.contains_dir("/special-@#$%"), Ok(true));
654 assert_eq!(tree.contains_file("/special-@#$%/test.file"), Ok(true));
655 assert_eq!(tree.contains_file("/special-@#$%/nonexistent"), Ok(false));
656 }
657
658 #[test]
660 fn test_contains_dir_but_is_file() {
661 let mut tree = FileTree::new();
662
663 tree.add_file(Path::new("/conflicted")).unwrap();
665
666 assert_eq!(tree.contains_dir("/conflicted"), Ok(false));
668 assert_eq!(tree.contains_file("/conflicted"), Ok(true));
670 }
671
672 #[test]
674 fn test_contains_file_but_is_dir() {
675 let mut tree = FileTree::new();
676
677 tree.add_dir(Path::new("/conflicted")).unwrap();
679
680 assert_eq!(tree.contains_file("/conflicted"), Ok(false));
682 assert_eq!(tree.contains_dir("/conflicted"), Ok(true));
684 }
685 }
686
687 mod remove_file {
688 use super::*;
689
690 #[test]
692 fn test_remove_simple_file() {
693 let mut tree = FileTree::new();
694 tree.add_file(Path::new("/document.txt")).unwrap();
695
696 assert_eq!(tree.contains_file("/document.txt"), Ok(true));
698
699 assert!(tree.remove_file("/document.txt").is_ok());
701
702 assert_eq!(tree.contains_file("/document.txt"), Ok(false));
704 }
705
706 #[test]
708 fn test_remove_nested_file() {
709 let mut tree = FileTree::new();
710 tree.add_file(Path::new("/home/user/document.txt")).unwrap();
711
712 assert_eq!(tree.contains_file("/home/user/document.txt"), Ok(true));
714
715 assert!(tree.remove_file("/home/user/document.txt").is_ok());
717
718 assert_eq!(tree.contains_file("/home/user/document.txt"), Ok(false));
720
721 assert_eq!(tree.contains_dir("/home"), Ok(true));
723 assert_eq!(tree.contains_dir("/home/user"), Ok(true));
724 }
725
726 #[test]
728 fn test_idempotent_remove_file() {
729 let mut tree = FileTree::new();
730 tree.add_file(Path::new("/tmp/file.txt")).unwrap();
731
732 assert!(tree.remove_file("/tmp/file.txt").is_ok());
734 assert!(tree.remove_file("/tmp/file.txt").is_err()); assert_eq!(tree.contains_file("/tmp/file.txt"), Ok(false));
739 }
740
741 #[test]
743 fn test_remove_nonexistent_file() {
744 let mut tree = FileTree::new();
745
746 assert!(tree.remove_file("/nonexistent.txt").is_err());
748
749 assert!(tree.root.is_empty());
751 }
752
753 #[test]
755 fn test_remove_empty_path_error() {
756 let mut tree = FileTree::new();
757
758 let result = tree.remove_file("");
759 assert!(result.is_err());
760 if let Err(DSError::EmptyPath) = result {
761 } else {
763 panic!("Expected EmptyPath error for empty path");
764 }
765 }
766
767 #[test]
769 fn test_remove_non_absolute_path_error() {
770 let mut tree = FileTree::new();
771
772 let result = tree.remove_file("relative/path/file.txt");
773 assert!(result.is_err());
774 if let Err(DSError::NotAbsolutePath { path }) = result {
775 assert_eq!(path, PathBuf::from("relative/path/file.txt"));
776 } else {
777 panic!("Expected NotAbsolutePath error");
778 }
779 }
780
781 #[test]
783 fn test_remove_root_as_file_error() {
784 let mut tree = FileTree::new();
785
786 let result = tree.remove_file("/");
787 assert!(result.is_err());
788 if let Err(DSError::NotFile { path }) = result {
789 assert_eq!(path, PathBuf::from("/"));
790 } else {
791 panic!("Expected NotFile error for root path");
792 }
793 }
794
795 #[test]
797 fn test_remove_multiple_files() {
798 let mut tree = FileTree::new();
799 tree.add_file(Path::new("/tmp/file1.txt")).unwrap();
800 tree.add_file(Path::new("/tmp/file2.txt")).unwrap();
801 tree.add_file(Path::new("/tmp/script.sh")).unwrap();
802
803 assert!(tree.remove_file("/tmp/file1.txt").is_ok());
805 assert_eq!(tree.contains_file("/tmp/file1.txt"), Ok(false));
806
807 assert!(tree.remove_file("/tmp/script.sh").is_ok());
809 assert_eq!(tree.contains_file("/tmp/script.sh"), Ok(false));
810
811 assert_eq!(tree.contains_file("/tmp/file2.txt"), Ok(true));
813 }
814
815 #[test]
817 fn test_remove_file_with_special_chars() {
818 let mut tree = FileTree::new();
819 tree.add_file(Path::new("/special-@#$%/test.file")).unwrap();
820
821 assert_eq!(tree.contains_file("/special-@#$%/test.file"), Ok(true));
822
823 assert!(tree.remove_file("/special-@#$%/test.file").is_ok());
825
826 assert_eq!(tree.contains_file("/special-@#$%/test.file"), Ok(false));
827 }
828 }
829
830 mod remove_dir {
831 use super::*;
832
833 #[test]
835 fn test_remove_simple_dir() {
836 let mut tree = FileTree::new();
837 tree.add_dir(Path::new("/home")).unwrap();
838
839 assert_eq!(tree.contains_dir("/home"), Ok(true));
840
841 assert!(tree.remove_dir("/home").is_ok());
842
843 assert_eq!(tree.contains_dir("/home"), Ok(false));
844 }
845
846 #[test]
848 fn test_remove_nested_dir_with_files() {
849 let mut tree = FileTree::new();
850 tree.add_file(Path::new("/projects/rust/main.rs")).unwrap();
851 tree.add_file(Path::new("/projects/python/script.py"))
852 .unwrap();
853
854 assert!(tree.remove_dir("/projects").is_ok());
856
857 assert_eq!(tree.contains_dir("/projects"), Ok(false));
859 assert_eq!(tree.contains_dir("/projects/rust"), Ok(false));
860 assert_eq!(tree.contains_file("/projects/rust/main.rs"), Ok(false));
861 assert_eq!(tree.contains_file("/projects/python/script.py"), Ok(false));
862 }
863
864 #[test]
866 fn test_idempotent_remove_dir() {
867 let mut tree = FileTree::new();
868 tree.add_dir(Path::new("/var/log")).unwrap();
869
870 assert!(tree.remove_dir("/var/log").is_ok());
871 assert!(tree.remove_dir("/var/log").is_err()); assert_eq!(tree.contains_dir("/var/log"), Ok(false));
874 }
875
876 #[test]
878 fn test_remove_nonexistent_dir() {
879 let mut tree = FileTree::new();
880
881 assert!(tree.remove_dir("/nonexistent").is_err());
883 }
884
885 #[test]
887 fn test_remove_root_dir_error() {
888 let mut tree = FileTree::new();
889
890 let result = tree.remove_dir("/");
891 assert!(result.is_err());
892 if let Err(DSError::NotFile { path }) = result {
893 assert_eq!(path, PathBuf::from("/"));
894 } else {
895 panic!("Expected NotFile error for root path");
896 }
897 }
898
899 #[test]
901 fn test_remove_dir_with_special_chars() {
902 let mut tree = FileTree::new();
903 tree.add_dir(Path::new("/special-@#$%/test")).unwrap();
904
905 assert_eq!(tree.contains_dir("/special-@#$%/test"), Ok(true));
906
907 assert!(tree.remove_dir("/special-@#$%/test").is_ok());
909
910 assert_eq!(tree.contains_dir("/special-@#$%/test"), Ok(false));
911 }
912
913 #[test]
915 fn test_remove_file_path_as_dir() {
916 let mut tree = FileTree::new();
917 tree.add_file(Path::new("/tmp/document.txt")).unwrap();
918
919 assert!(tree.remove_dir("/tmp/document.txt").is_err());
921 assert_eq!(tree.contains_file("/tmp/document.txt"), Ok(true));
925 }
926
927 #[test]
929 fn test_remove_intermediate_dir() {
930 let mut tree = FileTree::new();
931 tree.add_file(Path::new("/projects/rust/src/main.rs"))
932 .unwrap();
933 tree.add_file(Path::new("/projects/rust/tests/unit.rs"))
934 .unwrap();
935 tree.add_file(Path::new("/projects/python/app.py")).unwrap();
936
937 assert!(tree.remove_dir("/projects/rust").is_ok());
939
940 assert_eq!(tree.contains_dir("/projects/rust"), Ok(false));
942 assert_eq!(tree.contains_file("/projects/rust/src/main.rs"), Ok(false));
943 assert_eq!(
944 tree.contains_file("/projects/rust/tests/unit.rs"),
945 Ok(false)
946 );
947
948 assert_eq!(tree.contains_dir("/projects/python"), Ok(true));
950 assert_eq!(tree.contains_file("/projects/python/app.py"), Ok(true));
951 }
952
953 #[test]
955 fn test_remove_nonexistent_middle_dir() {
956 let mut tree = FileTree::new();
957 tree.add_dir("/existing/path").unwrap();
958
959 let result = tree.remove_dir("/nonexistent/parent/dir");
961 assert!(result.is_err()); assert_eq!(tree.contains_dir("/existing/path"), Ok(true));
965 }
966 }
967
968 mod build_path {
969 use super::*;
970
971 #[test]
973 fn test_build_path_simple() {
974 let tree = FileTree::new();
975 let components: Vec<_> = Path::new("/home/user").components().skip(1).collect();
976
977 let path = tree.build_path(&components);
978 assert_eq!(path, PathBuf::from("/home/user"));
979 }
980
981 #[test]
983 fn test_build_root_path() {
984 let tree = FileTree::new();
985 let empty_components: Vec<Component<'_>> = vec![];
986
987 let path = tree.build_path(&empty_components);
988 assert_eq!(path, PathBuf::from("/"));
989 }
990
991 #[test]
993 fn test_build_path_special_chars() {
994 let tree = FileTree::new();
995 let components: Vec<_> = Path::new("/special-@#$%/test/path")
996 .components()
997 .skip(1)
998 .collect();
999
1000 let path = tree.build_path(&components);
1001 assert_eq!(path, PathBuf::from("/special-@#$%/test/path"));
1002 }
1003 }
1004
1005 mod clear {
1006 use super::*;
1007
1008 #[test]
1010 fn test_clear_removes_all_children() {
1011 let mut tree = FileTree::new();
1012
1013 tree.add_dir(Path::new("/home/user")).unwrap();
1015 tree.add_file(Path::new("/etc/config")).unwrap();
1016
1017 assert!(!tree.root.is_empty());
1019
1020 tree.clear();
1022
1023 assert!(tree.root.is_empty());
1025 }
1026
1027 #[test]
1029 fn test_clear_on_empty_tree() {
1030 let mut tree = FileTree::new();
1031
1032 assert!(tree.root.is_empty());
1034
1035 tree.clear();
1037
1038 assert!(tree.root.is_empty());
1039 }
1040
1041 #[test]
1043 fn test_clear_complex_structure() {
1044 let mut tree = FileTree::new();
1045
1046 tree.add_dir(Path::new("/var/log")).unwrap();
1048 tree.add_dir(Path::new("/home/user/projects")).unwrap();
1049 tree.add_file(Path::new("/etc/passwd")).unwrap();
1050 tree.add_file(Path::new("/home/user/notes.txt")).unwrap();
1051
1052 assert!(tree.contains_dir("/var/log").unwrap());
1054 assert!(tree.contains_dir("/home/user/projects").unwrap());
1055 assert!(tree.contains_file("/etc/passwd").unwrap());
1056
1057 tree.clear();
1059
1060 assert!(!tree.contains_dir("/var/log").unwrap_or(false));
1062 assert!(!tree.contains_dir("/home/user/projects").unwrap_or(false));
1063 assert!(!tree.contains_file("/etc/passwd").unwrap_or(false));
1064 assert!(tree.root.is_empty());
1065 }
1066
1067 #[test]
1069 fn test_multiple_clear_calls() {
1070 let mut tree = FileTree::new();
1071
1072 tree.add_dir(Path::new("/test")).unwrap();
1073 tree.clear(); tree.clear(); assert!(tree.root.is_empty());
1077 }
1078 }
1079
1080 mod visit {
1081 use std::path::PathBuf;
1082 use super::*;
1083
1084 #[test]
1086 fn test_visit_empty_tree() {
1087 let tree = FileTree::new();
1088 let mut visited = Vec::new();
1089 tree.visit(|path| visited.push(path.to_path_buf()));
1090 assert!(visited.is_empty(), "Empty tree should not visit any paths");
1091 }
1092
1093 #[test]
1096 fn test_visit_single_root_directory() {
1097 let mut tree = FileTree::new();
1098 tree.add_dir("/home").unwrap();
1099 let mut visited = Vec::new();
1100 tree.visit(|path| visited.push(path.to_path_buf()));
1101 assert_eq!(visited, vec![PathBuf::from("/home")], "Should visit /home");
1102 }
1103
1104 #[test]
1107 fn test_visit_single_root_file() {
1108 let mut tree = FileTree::new();
1109 tree.add_file("/file.txt").unwrap();
1110 let mut visited = Vec::new();
1111 tree.visit(|path| visited.push(path.to_path_buf()));
1112 assert_eq!(visited, vec![PathBuf::from("/file.txt")], "Should visit file.txt");
1113 }
1114
1115 #[test]
1119 fn test_visit_nested_directories() {
1120 let mut tree = FileTree::new();
1121 tree.add_dir("/home/user/documents").unwrap();
1122 let mut visited = Vec::new();
1123 tree.visit(|path| visited.push(path.to_path_buf()));
1124 assert_eq!(
1125 visited,
1126 vec![PathBuf::from("/home/user/documents")],
1127 "Should visit the full nested directory path"
1128 );
1129 }
1130
1131 #[test]
1139 fn test_visit_files_in_nested_directories() {
1140 let mut tree = FileTree::new();
1141 tree.add_file("/etc/passwd").unwrap();
1142 tree.add_file("/var/log/messages").unwrap();
1143 let mut visited = Vec::new();
1144 tree.visit(|path| visited.push(path.to_path_buf()));
1145 assert_eq!(
1147 visited,
1148 vec![
1149 PathBuf::from("/etc/passwd"),
1150 PathBuf::from("/var/log/messages")
1151 ],
1152 "Should visit all files with correct full paths"
1153 );
1154 }
1155
1156 #[test]
1166 fn test_visit_mixed_structure() {
1167 let mut tree = FileTree::new();
1168 tree.add_file("/home/user/document.txt").unwrap();
1169 tree.add_file("/tmp/script.sh").unwrap();
1170 tree.add_dir("/var").unwrap();
1171 let mut visited = Vec::new();
1172 tree.visit(|path| visited.push(path.to_path_buf()));
1173 assert_eq!(
1175 visited,
1176 vec![
1177 PathBuf::from("/home/user/document.txt"),
1178 PathBuf::from("/tmp/script.sh"),
1179 PathBuf::from("/var")
1180 ],
1181 "Should visit all items with correct full paths in lexical order"
1182 );
1183 }
1184
1185 #[test]
1190 fn test_visit_only_leaf_directories() {
1191 let mut tree = FileTree::new();
1192 tree.add_dir("/a/b/c").unwrap();
1193 let mut visited = Vec::new();
1194 tree.visit(|path| visited.push(path.to_path_buf()));
1195 assert_eq!(
1196 visited,
1197 vec![PathBuf::from("/a/b/c")],
1198 "Only leaf directories should be visited"
1199 );
1200 }
1201
1202 #[test]
1210 fn test_visit_complex_tree() {
1211 let mut tree = FileTree::new();
1212 tree.add_file("/projects/rust/main.rs").unwrap();
1213 tree.add_file("/projects/python/app.py").unwrap();
1214 tree.add_file("/docs/README.md").unwrap();
1215 tree.add_dir("/temp").unwrap();
1216 let mut visited = Vec::new();
1217 tree.visit(|path| visited.push(path.to_path_buf()));
1218 assert_eq!(
1219 visited,
1220 vec![
1221 PathBuf::from("/docs/README.md"),
1222 PathBuf::from("/projects/python/app.py"),
1223 PathBuf::from("/projects/rust/main.rs"),
1224 PathBuf::from("/temp")
1225 ],
1226 "Should visit all leaf items with full paths in correct order"
1227 );
1228 }
1229
1230 #[test]
1238 fn test_visit_directory_with_files_and_subdirs() {
1239 let mut tree = FileTree::new();
1240 tree.add_file("/mixed/file1.txt").unwrap();
1241 tree.add_file("/mixed/subdir/file2.txt").unwrap();
1242 let mut visited = Vec::new();
1243 tree.visit(|path| visited.push(path.to_path_buf()));
1244 assert_eq!(
1245 visited,
1246 vec![
1247 PathBuf::from("/mixed/file1.txt"),
1248 PathBuf::from("/mixed/subdir/file2.txt")
1249 ],
1250 "Should visit both files and files in subdirectories with full paths"
1251 );
1252 }
1253 }
1254}