vfs_tools/
lib.rs

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}