use regex::{
Regex,
RegexBuilder,
};
use crate::{
MimeError,
MimeResult,
};
#[derive(Debug, Clone)]
pub struct MimeGlob {
weight: u16,
case_sensitive: bool,
pattern: String,
matcher: Regex,
}
impl MimeGlob {
pub const MIN_WEIGHT: u16 = 0;
pub const MAX_WEIGHT: u16 = 100;
pub const DEFAULT_WEIGHT: u16 = 50;
pub fn new(pattern: &str, weight: u16, case_sensitive: bool) -> MimeResult<Self> {
if weight > Self::MAX_WEIGHT {
return Err(MimeError::InvalidGlobWeight { weight });
}
let regex = glob_to_regex(pattern);
let matcher = RegexBuilder::new(®ex)
.case_insensitive(!case_sensitive)
.build()
.expect("generated MIME glob regex should be valid");
Ok(Self {
weight,
case_sensitive,
pattern: pattern.to_owned(),
matcher,
})
}
pub fn weight(&self) -> u16 {
self.weight
}
pub fn case_sensitive(&self) -> bool {
self.case_sensitive
}
pub fn pattern(&self) -> &str {
&self.pattern
}
pub fn matches(&self, filename: &str) -> bool {
!filename.is_empty() && !self.pattern.is_empty() && self.matcher.is_match(filename)
}
}
fn glob_to_regex(pattern: &str) -> String {
let chars: Vec<char> = pattern.chars().collect();
let mut regex = String::from("^");
let mut index = 0;
while index < chars.len() {
match chars[index] {
'*' => regex.push_str(".*"),
'?' => regex.push('.'),
'[' => index = append_character_class(&chars, index, &mut regex),
ch => append_escaped_regex_char(ch, &mut regex),
}
index += 1;
}
regex.push('$');
regex
}
fn append_character_class(chars: &[char], start: usize, regex: &mut String) -> usize {
let mut end = start + 1;
while end < chars.len() && chars[end] != ']' {
end += 1;
}
if end >= chars.len() {
regex.push_str("\\[");
return start;
}
regex.push('[');
let mut content_start = start + 1;
if content_start < end && chars[content_start] == '!' {
regex.push('^');
content_start += 1;
}
for ch in chars.iter().take(end).skip(content_start) {
if *ch == '\\' {
regex.push('\\');
}
regex.push(*ch);
}
regex.push(']');
end
}
fn append_escaped_regex_char(ch: char, regex: &mut String) {
if matches!(
ch,
'.' | '+'
| '('
| ')'
| '|'
| '^'
| '$'
| '{'
| '}'
| '='
| '!'
| '<'
| '>'
| ':'
| '-'
| '\\'
) {
regex.push('\\');
}
regex.push(ch);
}