use crate::cache::TemplateSourceMatcher;
use crate::template::utility::glob_to_regex;
use regex::Regex;
pub struct PathGlobMatcher {
glob: String,
pattern: Regex,
case_insensitive: bool,
}
impl PathGlobMatcher {
pub fn new(glob: &str) -> Self {
if glob.starts_with('/') {
panic!("Absolute template paths need no inital \"/\"; remove it from: {glob}");
}
let mut m = PathGlobMatcher {
glob: glob.to_string(),
pattern: Regex::new(".*").unwrap(),
case_insensitive: false,
};
m.build_pattern();
m
}
fn build_pattern(&mut self) {
self.pattern =
glob_to_regex(&self.glob, self.case_insensitive).unwrap_or_else(|e| panic!("{e}"));
}
pub fn is_case_insensitive(&self) -> bool {
self.case_insensitive
}
pub fn set_case_insensitive(&mut self, case_insensitive: bool) {
let last = self.case_insensitive;
self.case_insensitive = case_insensitive;
if last != case_insensitive {
self.build_pattern();
}
}
pub fn case_insensitive(mut self, case_insensitive: bool) -> Self {
self.set_case_insensitive(case_insensitive);
self
}
}
impl TemplateSourceMatcher for PathGlobMatcher {
fn matches(&self, source_name: &str) -> bool {
self.pattern.is_match(source_name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn path_glob_matches() {
let m = PathGlobMatcher::new("a/**");
assert!(m.matches("a/b/c.ftl"), "尾部 ** 匹配任意深度");
assert!(m.matches("a/b.ftl"));
assert!(!m.matches("b/c.ftl"));
let m = PathGlobMatcher::new("**/head.ftl");
assert!(m.matches("head.ftl"));
assert!(m.matches("foo/head.ftl"));
assert!(m.matches("foo/bar/head.ftl"));
let m = PathGlobMatcher::new("foo/*.ftl");
assert!(m.matches("foo/a.ftl"));
assert!(!m.matches("foo/a/b.ftl"), "* 不跨目录");
assert!(std::panic::catch_unwind(|| PathGlobMatcher::new("a**/b.ftl")).is_err());
}
}