pub mod brace;
pub mod cache;
pub mod micromatch;
use crate::error::GlobError;
use crate::options::GlobOptions;
use globset::GlobSet;
#[derive(Clone)]
pub struct Patterns {
pub set: GlobSet,
pub regexes: Vec<regex::Regex>,
}
impl Patterns {
pub fn compile_many<I, S>(patterns: I, opts: &GlobOptions) -> Result<Self, GlobError>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut builder = globset::GlobSetBuilder::new();
let mut regexes = Vec::new();
for pattern in patterns {
let pattern_str = pattern.as_ref().trim();
if pattern_str.is_empty() {
continue;
}
if pattern_str.contains("**/..") || pattern_str.contains("/../") {
return Err(GlobError::PathTraversal);
}
Self::process_pattern(pattern_str, &mut builder, &mut regexes, opts)?;
}
let set = builder
.build()
.map_err(|e| GlobError::InvalidPattern(e.to_string()))?;
Ok(Self { set, regexes })
}
fn process_pattern(
pattern: &str,
builder: &mut globset::GlobSetBuilder,
regexes: &mut Vec<regex::Regex>,
_opts: &GlobOptions,
) -> Result<(), GlobError> {
let expanded_patterns = if pattern.contains('{') && pattern.contains('}') {
brace::expand(pattern)?
} else {
vec![pattern.to_string()]
};
for expanded in expanded_patterns {
if let Some(regex_pattern) = expanded.strip_prefix("re:") {
let re = cache::get_or_compile_regex(regex_pattern)?;
regexes.push(re);
continue;
}
if Self::is_complex_pattern(&expanded) {
let regex_pattern = micromatch::micromatch_to_regex(&expanded)?;
let re = cache::get_or_compile_regex(®ex_pattern)?;
regexes.push(re);
} else {
Self::add_glob_pattern(&expanded, builder)?;
}
}
Ok(())
}
fn is_complex_pattern(pattern: &str) -> bool {
pattern.contains('@')
|| pattern.contains('!')
|| pattern.contains('+')
|| pattern.contains('?')
|| pattern.contains('(')
|| pattern.contains(')')
|| pattern.contains('[')
|| pattern.contains(']')
|| pattern.contains('{')
|| pattern.contains('}')
|| pattern.contains('|')
}
fn add_glob_pattern(
pattern: &str,
builder: &mut globset::GlobSetBuilder,
) -> Result<(), GlobError> {
let glob =
globset::Glob::new(pattern).map_err(|e| GlobError::InvalidPattern(e.to_string()))?;
builder.add(glob);
Ok(())
}
pub fn is_match(&self, path: &camino::Utf8PathBuf) -> bool {
let path_str = path.as_str();
if !self.set.is_empty() && self.set.is_match(path_str) {
return true;
}
for re in &self.regexes {
if re.is_match(path_str) {
return true;
}
}
false
}
pub fn could_match(&self, path: &camino::Utf8PathBuf) -> bool {
self.set.is_match(path.as_str()) || !self.regexes.is_empty()
}
}
pub fn cache_metrics() -> (cache::CacheMetrics, cache::CacheMetrics) {
(cache::glob_cache_metrics(), cache::regex_cache_metrics())
}