pub use anyhow::{Error, Result};
pub use globset::{Candidate, Glob, GlobMatcher};
#[derive(PartialEq, Eq)]
enum Conclusion {
Matches,
Exclusion,
NonMatching,
}
#[derive(Debug, Clone)]
struct Pattern {
glob: GlobMatcher,
negative: bool,
}
impl Pattern {
fn new(glob: &str) -> Result<Self> {
let (negative, glob) = glob
.strip_prefix('!')
.map(|st| (true, st))
.unwrap_or_else(|| (false, glob));
let glob = Glob::new(glob)?.compile_matcher();
Ok(Pattern { glob, negative })
}
fn matches(&self, path: &Candidate) -> Conclusion {
match (self.glob.is_match_candidate(path), self.negative) {
(true, false) => Conclusion::Matches,
(true, true) => Conclusion::Exclusion,
(false, _) => Conclusion::NonMatching,
}
}
}
#[derive(Debug, Clone)]
pub struct Globreeks {
patterns: Vec<Pattern>,
}
impl Globreeks {
pub fn new<L>(glob_list: L) -> Result<Globreeks>
where
L: IntoIterator,
L::Item: AsRef<str>,
{
let mut patterns = Vec::new();
for glob in glob_list.into_iter() {
patterns.push(Pattern::new(glob.as_ref())?);
}
Ok(Globreeks { patterns })
}
pub fn evaluate_candidate<'a>(&self, path: &Candidate<'a>) -> bool {
for pattern in self.patterns.iter().rev() {
match pattern.matches(path) {
Conclusion::Matches => return true,
Conclusion::Exclusion => return false,
Conclusion::NonMatching => continue,
}
}
false
}
pub fn evaluate<S>(&self, path: S) -> bool
where
S: AsRef<str>,
{
self.evaluate_candidate(&Candidate::new(path.as_ref()))
}
}
#[cfg(test)]
mod tests {
use crate::Globreeks;
use anyhow::Result;
#[test]
fn test_basic() -> Result<()> {
let reeks = Globreeks::new(["**/*.{js,ts}", "!bundle*.js", "bundle1.js"])?;
assert!(reeks.evaluate("somewhere/some_file.js"));
assert!(!reeks.evaluate("bundle2137.js"));
assert!(reeks.evaluate("bundle1.js"));
assert!(!reeks.evaluate("readme.txt"));
Ok(())
}
}