use globset::GlobBuilder;
use super::LazyGlob;
const BACKSLASH: &str = "\u{1}";
const GLOB_CHARS: [char; 9] = ['*', '?', '[', ']', '{', '}', '\\', ',', '!'];
#[derive(Debug, Clone)]
pub(super) enum WordGlob {
Literal(String),
Glob(Box<LazyGlob>),
}
impl WordGlob {
pub(super) fn new(word: &str) -> Result<Self, globset::Error> {
if !word.contains(GLOB_CHARS) {
return Ok(Self::Literal(word.to_owned()));
}
let mut glob = String::with_capacity(word.len());
let mut chars = word.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' && chars.peek() == Some(&'\\') {
chars.next();
glob.push_str(BACKSLASH);
} else {
glob.push(c);
}
}
GlobBuilder::new(&glob)
.literal_separator(false)
.backslash_escape(true)
.build()
.map(|g| Self::Glob(Box::new(LazyGlob::new(g))))
}
pub(super) fn is_match(&self, word: &str) -> bool {
match self {
Self::Literal(literal) => literal == word,
Self::Glob(glob) if word.contains('\\') => {
glob.is_match(&word.replace('\\', BACKSLASH))
}
Self::Glob(glob) => glob.is_match(word),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn literal_words_match_exactly_what_their_glob_matches() {
let words = [
"git", "--force", "-rf", "", "ünï", "a/b", "C:", "x.y", "$", "|", "=", "@",
];
let candidates = [
"git",
"Git",
"git ",
"gitx",
"xgit",
"--force",
"--force-with-lease",
"-rf",
"-fr",
"",
"ünï",
"ÜNÏ",
"a/b",
"a\\b",
"a//b",
"C:",
"c:",
"x.y",
"xzy",
"$",
"|",
"=",
"@",
];
for word in words {
let literal = WordGlob::new(word).unwrap();
assert!(matches!(literal, WordGlob::Literal(_)), "{word}");
let glob = WordGlob::Glob(Box::new(LazyGlob::new(
GlobBuilder::new(word)
.literal_separator(false)
.backslash_escape(true)
.build()
.unwrap(),
)));
for candidate in candidates {
assert_eq!(
literal.is_match(candidate),
glob.is_match(candidate),
"{word:?} vs {candidate:?}"
);
}
}
}
#[test]
fn glob_syntax_still_compiles_to_a_glob() {
for word in [
"*sh*", "--force*", "x?", "[ab]", "{a,b}", r"\*", "a]", "!x", "a,b",
] {
assert!(
matches!(WordGlob::new(word), Ok(WordGlob::Glob(_))),
"{word}"
);
}
for malformed in ["{a", "[a", "b}"] {
assert!(
WordGlob::new(malformed).is_err(),
"{malformed} is still rejected"
);
}
}
}