ferrous-actions 0.1.0-beta.1

GitHub actions for Rust written in Rust and compiled to WebAssembly
Documentation
use crate::node::fs;
use crate::node::path::Path;
use crate::Error;
use async_recursion::async_recursion;
use async_trait::async_trait;
use simple_path_match::PathMatch;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet, VecDeque};

pub const ROOT_NAME: &str = ".";

#[derive(Debug, Default, Clone)]
pub struct Ignores {
    map: HashMap<usize, HashSet<String>>,
}

impl Ignores {
    pub fn add(&mut self, depth: usize, name: &str) {
        self.map.entry(depth).or_default().insert(name.to_string());
    }

    pub fn should_ignore(&self, name: &str, depth: usize) -> bool {
        self.map.get(&depth).map_or(false, |names| names.contains(name))
    }
}

#[async_trait(?Send)]
pub trait Visitor {
    async fn should_enter(&self, _path: &Path) -> Result<bool, Error> {
        Ok(true)
    }
    async fn enter_folder(&mut self, path: &Path) -> Result<(), Error>;
    async fn visit_entry(&mut self, name: &Path, is_file: bool) -> Result<(), Error>;
    async fn exit_folder(&mut self, path: &Path) -> Result<(), Error>;
}

pub async fn apply_visitor<V>(folder_path: &Path, ignores: &Ignores, visitor: &mut V) -> Result<(), Error>
where
    V: Visitor,
{
    apply_visitor_impl(0, folder_path, ignores, visitor).await
}

#[async_recursion(?Send)]
async fn apply_visitor_impl(
    depth: usize,
    path: &Path,
    ignores: &Ignores,
    visitor: &mut dyn Visitor,
) -> Result<(), Error> {
    let file_name: Cow<str> = if depth == 0 {
        ROOT_NAME.into()
    } else {
        path.file_name().into()
    };
    if ignores.should_ignore(&file_name, depth) {
        return Ok(());
    }
    let metadata = fs::symlink_metadata(path).await?;
    if metadata.is_directory() {
        if visitor.should_enter(path).await? {
            visitor.enter_folder(path).await?;
            let depth = depth + 1;
            let dir = fs::read_dir(path).await?;
            for entry in dir {
                let path = entry.path();
                apply_visitor_impl(depth, &path, ignores, visitor).await?;
            }
            visitor.exit_folder(path).await?;
        } else {
            visitor.visit_entry(path, false).await?;
        }
    } else {
        visitor.visit_entry(path, true).await?;
    }
    Ok(())
}

#[derive(Debug)]
struct PathMatchVisitor<'a> {
    matching_paths: Vec<Path>,
    matcher: &'a PathMatch,
    path_stack: VecDeque<Path>,
    output_relative: bool,
}

impl<'a> PathMatchVisitor<'a> {
    fn full_path_to_relative(&self, full_path: &Path) -> Path {
        self.path_stack
            .back()
            .map_or_else(|| Path::from("."), |p| p.join(&full_path.file_name()))
    }

    fn visit_path(&mut self, absolute: &Path, relative: &Path) {
        if self.matcher.matches(relative.to_string()) {
            let path = if self.output_relative { relative } else { absolute }.clone();
            self.matching_paths.push(path);
        }
    }
}

#[async_trait(?Send)]
impl<'a> Visitor for PathMatchVisitor<'a> {
    async fn should_enter(&self, full: &Path) -> Result<bool, Error> {
        let result = if self.path_stack.len() >= self.matcher.max_depth() {
            false
        } else {
            let relative = self.full_path_to_relative(full);
            self.matcher.matches_prefix(relative.to_string())
        };
        Ok(result)
    }

    async fn enter_folder(&mut self, full: &Path) -> Result<(), Error> {
        let relative = self.full_path_to_relative(full);
        self.visit_path(full, &relative);
        self.path_stack.push_back(relative);
        Ok(())
    }

    async fn visit_entry(&mut self, full: &Path, _is_file: bool) -> Result<(), Error> {
        let relative = self.full_path_to_relative(full);
        self.visit_path(full, &relative);
        Ok(())
    }

    async fn exit_folder(&mut self, _: &Path) -> Result<(), Error> {
        self.path_stack.pop_back();
        Ok(())
    }
}

pub async fn match_relative_paths(path: &Path, matcher: &PathMatch, output_relative: bool) -> Result<Vec<Path>, Error> {
    let mut visitor = PathMatchVisitor {
        matching_paths: Vec::new(),
        matcher,
        path_stack: VecDeque::new(),
        output_relative,
    };
    let ignores = Ignores::default();
    apply_visitor(path, &ignores, &mut visitor).await?;
    Ok(visitor.matching_paths)
}