use crate::error::Result;
use crate::interner::StringInterner;
use crate::types::ChangedFile;
use globset::{Glob, GlobSet, GlobSetBuilder};
use rayon::prelude::*;
pub struct PatternMatcher {
include_set: GlobSet,
exclude_set: GlobSet,
negation_first: bool,
}
impl PatternMatcher {
pub fn new(includes: &[&str], excludes: &[&str], negation_first: bool) -> Result<Self> {
let mut include_builder = GlobSetBuilder::new();
for pattern in includes {
include_builder.add(Glob::new(pattern)?);
}
let mut exclude_builder = GlobSetBuilder::new();
for pattern in excludes {
exclude_builder.add(Glob::new(pattern)?);
}
Ok(Self {
include_set: include_builder.build()?,
exclude_set: exclude_builder.build()?,
negation_first,
})
}
#[inline]
pub fn matches_sync(&self, path: &str) -> bool {
if self.negation_first {
if self.exclude_set.is_match(path) {
return false;
}
self.include_set.is_empty() || self.include_set.is_match(path)
} else {
if !self.include_set.is_empty() && !self.include_set.is_match(path) {
return false;
}
!self.exclude_set.is_match(path)
}
}
#[deprecated(note = "Use partition_files_parallel for index-based partitioning")]
pub fn filter_files_parallel(
&self,
files: &[ChangedFile],
interner: &StringInterner,
) -> Vec<ChangedFile> {
files
.par_iter()
.filter(|file| {
if let Some(path) = interner.resolve(file.path) {
self.matches_sync(path)
} else {
false
}
})
.cloned()
.collect()
}
pub fn partition_files_parallel(
&self,
files: &[ChangedFile],
interner: &StringInterner,
) -> (Vec<u32>, Vec<u32>) {
use rayon::iter::Either;
(0..files.len() as u32).into_par_iter().partition_map(|i| {
let file = &files[i as usize];
match interner.resolve(file.path) {
Some(path) if self.matches_sync(path) => Either::Left(i),
_ => Either::Right(i),
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_matching() {
let matcher = PatternMatcher::new(&["**/*.rs"], &[], false).unwrap();
assert!(matcher.matches_sync("src/main.rs"));
assert!(matcher.matches_sync("lib/mod.rs"));
assert!(!matcher.matches_sync("README.md"));
}
#[test]
fn test_exclusion() {
let matcher = PatternMatcher::new(&["**/*.rs"], &["**/test_*.rs"], false).unwrap();
assert!(matcher.matches_sync("src/main.rs"));
assert!(!matcher.matches_sync("src/test_utils.rs"));
}
}