vfs-tools 0.1.0

A collection ofttools to work with VFS
Documentation
#![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(())
}