#![doc = include_str!("../README.md")]
mod predicate;
use resiter::TryFilter;
use vfs::{VfsPath, VfsResult, WalkDirIterator};
use resiter::try_filter::TryFilterOk;
type Select<P> = TryFilterOk<WalkDirIterator, P>;
pub trait VfsPathExt {
fn try_select<P>(&self, predicate: P) -> VfsResult<Select<P>>
where
P: Fn(&VfsPath) -> VfsResult<bool>;
}
impl VfsPathExt for VfsPath {
fn try_select<P>(&self, predicate: P) -> VfsResult<Select<P>>
where
P: Fn(&VfsPath) -> VfsResult<bool>,
{
Ok(self.walk_dir()?.try_filter_ok(predicate))
}
}
pub struct FilesOnly<I: ?Sized>(I);
pub struct DirsOnly<I: ?Sized>(I);
pub struct WithExtension<I: Sized>(I, &'static str);
pub trait VfsIteratorExt
where
Self: Iterator,
{
fn files_only(self) -> FilesOnly<Self>
where
Self: Sized,
{
FilesOnly(self)
}
fn dirs_only(self) -> DirsOnly<Self>
where
Self: Sized,
{
DirsOnly(self)
}
fn with_extension(self, extension: &'static str) -> WithExtension<Self>
where
Self: Sized,
{
WithExtension(self, extension)
}
}
impl<I> VfsIteratorExt for I
where
I: Iterator<Item = VfsResult<VfsPath>>,
{
}
fn next_match<I, P>(iter: &mut I, predicate: P) -> Option<VfsResult<VfsPath>>
where
I: Iterator<Item = VfsResult<VfsPath>>,
P: Fn(VfsPath) -> VfsResult<Option<VfsPath>>,
{
while let Some(res) = iter.next() {
if let Some(res) = res.and_then(|p| predicate(p)).transpose() {
return Some(res);
}
}
None
}
impl<I> Iterator for FilesOnly<I>
where
I: Iterator<Item = VfsResult<VfsPath>>,
{
type Item = VfsResult<VfsPath>;
fn next(&mut self) -> Option<Self::Item> {
let iterator = &mut self.0;
next_match(iterator, predicate::file_only)
}
}
impl<I> Iterator for DirsOnly<I>
where
I: Iterator<Item = VfsResult<VfsPath>>,
{
type Item = VfsResult<VfsPath>;
fn next(&mut self) -> Option<Self::Item> {
let iterator = &mut self.0;
next_match(iterator, predicate::dir_only)
}
}
impl<I> Iterator for WithExtension<I>
where
I: Iterator<Item=VfsResult<VfsPath>>,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
let iterator = &mut self.0;
let extension = Some(self.1.to_owned());
next_match(iterator,|p|predicate::apply_predicate(p,|pp|Ok(pp.extension() == extension)))
}
}
#[macro_export]
macro_rules! setup_files {
($($($dir:ident/)* $file:literal : $contents:literal)*)=> {{
let files: ::vfs::VfsPath = ::vfs::MemoryFS::new().into();
$(
let pwd = &files;
$(
let pwd = pwd.join(stringify!($dir))?;
if !pwd.exists()? {
pwd.create_dir()?;
}
)*
pwd
.join($file)?
.create_file()?
.write_all($contents)?;
)*
::vfs::VfsResult::Ok(files)
}};
}
#[cfg(test)]
fn setup_files() -> VfsResult<VfsPath> {
setup_files! {
dir/"file.txt" : b"hello world"
dir/subdir/"file.txt" : b"hello world again"
dir/"other_file.md" : b"hello other world"
}
}
#[test]
fn try_select_matches_nothing() -> VfsResult<()> {
let root = setup_files()?;
let selected: Vec<VfsResult<VfsPath>> = root.try_select(|_| Ok(false))?.collect();
assert_eq!(selected.len(), 0);
Ok(())
}
#[test]
fn try_select_matches_everything() -> VfsResult<()> {
let root = setup_files()?;
let selected: Vec<VfsResult<VfsPath>> = root.try_select(|_| Ok(true))?.collect();
assert_eq!(selected.len(), 5);
Ok(())
}
#[test]
fn files_only_selects_only_files() -> VfsResult<()> {
let files_only = setup_files()?
.walk_dir()?
.files_only()
.collect::<VfsResult<Vec<VfsPath>>>()?;
assert_eq!(files_only.len(), 3);
Ok(())
}
#[test]
fn dirs_only_selects_only_dirs() -> VfsResult<()> {
let dirs_only = setup_files()?
.walk_dir()?
.dirs_only()
.collect::<VfsResult<Vec<VfsPath>>>()?;
assert_eq!(dirs_only.len(), 2);
Ok(())
}
#[test]
fn with_extension_selects_correct_extension() -> VfsResult<()> {
let dirs_only = setup_files()?
.walk_dir()?
.with_extension("md")
.collect::<VfsResult<Vec<VfsPath>>>()?;
assert_eq!(dirs_only.len(), 1);
Ok(())
}