1#![doc = include_str!("../README.md")]
2
3mod predicate;
4
5use resiter::TryFilter;
6use vfs::{VfsPath, VfsResult, WalkDirIterator};
7use resiter::try_filter::TryFilterOk;
8
9type Select<P> = TryFilterOk<WalkDirIterator, P>;
10
11pub trait VfsPathExt {
12 fn try_select<P>(&self, predicate: P) -> VfsResult<Select<P>>
13 where
14 P: Fn(&VfsPath) -> VfsResult<bool>;
15}
16
17impl VfsPathExt for VfsPath {
18 fn try_select<P>(&self, predicate: P) -> VfsResult<Select<P>>
19 where
20 P: Fn(&VfsPath) -> VfsResult<bool>,
21 {
22 Ok(self.walk_dir()?.try_filter_ok(predicate))
23 }
24}
25
26pub struct FilesOnly<I: ?Sized>(I);
27pub struct DirsOnly<I: ?Sized>(I);
28pub struct WithExtension<I: Sized>(I, &'static str);
29
30pub trait VfsIteratorExt
31where
32 Self: Iterator,
33{
34 fn files_only(self) -> FilesOnly<Self>
35 where
36 Self: Sized,
37 {
38 FilesOnly(self)
39 }
40
41 fn dirs_only(self) -> DirsOnly<Self>
42 where
43 Self: Sized,
44 {
45 DirsOnly(self)
46 }
47
48 fn with_extension(self, extension: &'static str) -> WithExtension<Self>
49 where
50 Self: Sized,
51 {
52 WithExtension(self, extension)
53 }
54}
55
56impl<I> VfsIteratorExt for I
57where
58 I: Iterator<Item = VfsResult<VfsPath>>,
59{
60}
61
62fn next_match<I, P>(iter: &mut I, predicate: P) -> Option<VfsResult<VfsPath>>
63where
64 I: Iterator<Item = VfsResult<VfsPath>>,
65 P: Fn(VfsPath) -> VfsResult<Option<VfsPath>>,
66{
67 while let Some(res) = iter.next() {
68 if let Some(res) = res.and_then(|p| predicate(p)).transpose() {
69 return Some(res);
70 }
71 }
72 None
73}
74
75impl<I> Iterator for FilesOnly<I>
76where
77 I: Iterator<Item = VfsResult<VfsPath>>,
78{
79 type Item = VfsResult<VfsPath>;
80 fn next(&mut self) -> Option<Self::Item> {
81 let iterator = &mut self.0;
82 next_match(iterator, predicate::file_only)
83 }
84}
85
86impl<I> Iterator for DirsOnly<I>
87where
88 I: Iterator<Item = VfsResult<VfsPath>>,
89{
90 type Item = VfsResult<VfsPath>;
91 fn next(&mut self) -> Option<Self::Item> {
92 let iterator = &mut self.0;
93 next_match(iterator, predicate::dir_only)
94 }
95}
96
97impl<I> Iterator for WithExtension<I>
98where
99 I: Iterator<Item=VfsResult<VfsPath>>,
100{
101 type Item = I::Item;
102
103 fn next(&mut self) -> Option<Self::Item> {
104 let iterator = &mut self.0;
105 let extension = Some(self.1.to_owned());
106 next_match(iterator,|p|predicate::apply_predicate(p,|pp|Ok(pp.extension() == extension)))
107 }
108}
109
110#[macro_export]
111macro_rules! setup_files {
112 ($($($dir:ident/)* $file:literal : $contents:literal)*)=> {{
113 let files: ::vfs::VfsPath = ::vfs::MemoryFS::new().into();
114 $(
115 let pwd = &files;
116 $(
117 let pwd = pwd.join(stringify!($dir))?;
118 if !pwd.exists()? {
119 pwd.create_dir()?;
120 }
121 )*
122 pwd
123 .join($file)?
124 .create_file()?
125 .write_all($contents)?;
126 )*
127 ::vfs::VfsResult::Ok(files)
128
129 }};
130}
131
132#[cfg(test)]
133fn setup_files() -> VfsResult<VfsPath> {
134 setup_files! {
135 dir/"file.txt" : b"hello world"
136 dir/subdir/"file.txt" : b"hello world again"
137 dir/"other_file.md" : b"hello other world"
138 }
139}
140
141#[test]
142fn try_select_matches_nothing() -> VfsResult<()> {
143 let root = setup_files()?;
144 let selected: Vec<VfsResult<VfsPath>> = root.try_select(|_| Ok(false))?.collect();
145 assert_eq!(selected.len(), 0);
146 Ok(())
147}
148
149#[test]
150fn try_select_matches_everything() -> VfsResult<()> {
151 let root = setup_files()?;
152 let selected: Vec<VfsResult<VfsPath>> = root.try_select(|_| Ok(true))?.collect();
153
154 assert_eq!(selected.len(), 5);
155 Ok(())
156}
157
158#[test]
159fn files_only_selects_only_files() -> VfsResult<()> {
160 let files_only = setup_files()?
161 .walk_dir()?
162 .files_only()
163 .collect::<VfsResult<Vec<VfsPath>>>()?;
164 assert_eq!(files_only.len(), 3);
165 Ok(())
166}
167
168#[test]
169fn dirs_only_selects_only_dirs() -> VfsResult<()> {
170 let dirs_only = setup_files()?
171 .walk_dir()?
172 .dirs_only()
173 .collect::<VfsResult<Vec<VfsPath>>>()?;
174 assert_eq!(dirs_only.len(), 2);
175 Ok(())
176}
177
178#[test]
179fn with_extension_selects_correct_extension() -> VfsResult<()> {
180 let dirs_only = setup_files()?
181 .walk_dir()?
182 .with_extension("md")
183 .collect::<VfsResult<Vec<VfsPath>>>()?;
184 assert_eq!(dirs_only.len(), 1);
185 Ok(())
186}