use super::super::patterns::literal_tree;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Case {
Exact,
Folded,
}
pub(super) fn filter(pattern: &str, case: Case) -> String {
match literal_tree(pattern) {
_ if case == Case::Folded => format!("(regex {})", quote(®ex(pattern, case))),
Some(dir) if pattern.ends_with("/**") => format!("(subpath {})", quote(dir)),
Some(path) => format!("(literal {})", quote(path)),
None => format!("(regex {})", quote(®ex(pattern, case))),
}
}
fn quote(text: &str) -> String {
format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
}
pub(super) fn regex(glob: &str, case: Case) -> String {
let mut re = String::from("^");
let mut depth = 0usize;
let mut i = 0;
while let Some(c) = glob[i..].chars().next() {
let rest = &glob[i..];
let (piece, used) = if rest == "/**" {
("(/.*)?".to_owned(), 3)
} else if rest.starts_with("/**/") {
("/(.*/)?".to_owned(), 4)
} else if i == 0 && rest == "**" {
(".*".to_owned(), 2)
} else if i == 0 && rest.starts_with("**/") {
("(.*/)?".to_owned(), 3)
} else {
match c {
'*' => ("[^/]*".to_owned(), 1),
'?' => ("[^/]".to_owned(), 1),
'{' => {
depth += 1;
("(".to_owned(), 1)
}
'}' if depth > 0 => {
depth -= 1;
(")".to_owned(), 1)
}
',' if depth > 0 => ("|".to_owned(), 1),
'[' => class(rest, case).unwrap_or_else(|| (escape('[', case), 1)),
'\\' => match rest[1..].chars().next() {
Some(next) => (escape(next, case), 1 + next.len_utf8()),
None => (escape('\\', case), 1),
},
other => (escape(other, case), other.len_utf8()),
}
};
re.push_str(&piece);
i += used;
}
re.push('$');
re
}
fn class(rest: &str, case: Case) -> Option<(String, usize)> {
let body = &rest[1..];
let (negated, body) = match body.strip_prefix(['!', '^']) {
Some(b) => (true, b),
None => (false, body),
};
let end = body.char_indices().skip(1).find(|(_, c)| *c == ']')?.0;
let written = &body[..end];
let mut members = written.replace('\\', "\\\\");
if case == Case::Folded {
let swapped: String = written.chars().map(swap_case).collect();
if swapped != written {
members.push_str(&swapped.replace('\\', "\\\\"));
}
}
let negation = if negated { "^/" } else { "" };
Some((
format!("[{negation}{members}]"),
rest.len() - body.len() + end + 1,
))
}
fn swap_case(c: char) -> char {
if c.is_ascii_lowercase() {
c.to_ascii_uppercase()
} else {
c.to_ascii_lowercase()
}
}
fn escape(c: char, case: Case) -> String {
if case == Case::Folded && c.is_ascii_alphabetic() {
format!("[{}{}]", c.to_ascii_lowercase(), c.to_ascii_uppercase())
} else if ".^$|()+[]{}\\*?".contains(c) {
format!("\\{c}")
} else {
c.to_string()
}
}