tgrep 1.6.15

Toy grep that honors .gitignore
Documentation
use anyhow::Error;
use log::debug;

use crate::utils::patterns::{Pattern, PatternSet};

#[derive(Clone, Default)]
pub struct Filters {
    patterns: PatternSet,
}

impl Filters {
    pub fn new(strings: &[String]) -> Result<Self, Error> {
        let mut patterns = PatternSet::new("/");
        for pattern in strings {
            let pattern = if pattern.starts_with("**/") {
                pattern.to_owned()
            } else {
                "**/".to_owned() + pattern
            };
            let transformed = Pattern::new(&pattern)?;
            debug!("Transformed filter {:?} -> {:?}", pattern, transformed);
            patterns.push(transformed, false);
        }
        Ok(Filters { patterns })
    }

    pub fn matches(&self, path: &str) -> bool {
        self.patterns.matches(path, false)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn matches_prefixed_glob_filters() {
        let filters = Filters::new(&["*.rs".to_string(), "src/*.toml".to_string()]).unwrap();

        assert!(filters.matches("/tmp/project/src/main.rs"));
        assert!(filters.matches("/tmp/project/src/Cargo.toml"));
        assert!(!filters.matches("/tmp/project/src/main.py"));
        assert!(!filters.matches("/tmp/project/Cargo.toml"));
    }

    #[test]
    fn preserves_double_star_prefixes() {
        let filters = Filters::new(&["**/README.md".to_string()]).unwrap();

        assert!(filters.matches("/tmp/project/README.md"));
        assert!(filters.matches("/tmp/project/docs/README.md"));
        assert!(!filters.matches("/tmp/project/docs/README.txt"));
    }
}