filenamegen 0.2.7

Shell-style filename generation aka globbing
Documentation
use crate::node::Node;
use crate::{new_binary_pattern_string, normalize_slashes, Walker};
use bstr::ByteSlice;
use regex::bytes::Regex;
use std::path::{Path, PathBuf};

#[derive(Debug)]
pub struct RecursiveWalker {
    walk_root: PathBuf,
    walk: Option<walkdir::IntoIter>,
    regex: Regex,
}

impl RecursiveWalker {
    pub fn new<'a>(
        nodes: std::iter::Peekable<std::slice::Iter<'a, Node>>,
        walk_root: PathBuf,
    ) -> Self {
        let mut pattern = new_binary_pattern_string();
        Node::RecursiveMatch.append_regex(&mut pattern);
        for node in nodes {
            #[cfg(not(windows))]
            pattern.push_str("/?");
            #[cfg(windows)]
            pattern.push_str("[/\\\\]?");
            node.append_regex(&mut pattern);
        }
        pattern.push('$');
        let regex = Regex::new(&pattern).expect("regex to compile");

        Self {
            regex,
            walk_root,
            walk: None,
        }
    }

    pub(crate) fn next<'a>(&mut self, walker: &mut Walker<'a>) -> Option<PathBuf> {
        if self.walk.is_none() {
            self.walk = Some(
                walkdir::WalkDir::new(&self.walk_root)
                    .follow_links(true)
                    .into_iter(),
            );
        }

        while let Some(entry) = self.walk.as_mut().unwrap().next() {
            if let Ok(entry) = entry {
                let path = entry
                    .path()
                    .strip_prefix(&self.walk_root)
                    .expect("walk is always relative to self.walk_root");
                if self.is_match(path) {
                    return Some(normalize_slashes(
                        entry
                            .path()
                            .strip_prefix(&walker.root)
                            .expect("walk is always relative to walker.root")
                            .to_path_buf(),
                    ));
                }
            }
        }

        None
    }

    fn is_match(&self, path: &Path) -> bool {
        let matched = if let Some(bytes) = <[u8]>::from_path(path) {
            self.regex.is_match(bytes)
        } else {
            false
        };
        matched
    }
}