pub fn matches(pattern: &str, path: &str) -> bool {
let mut pattern = pattern.replace('\\', "/");
if pattern.ends_with('/') {
pattern.push('*');
}
let path: Vec<char> = path.replace('\\', "/").chars().collect();
glob(&tokens(&pattern), &path)
}
pub fn select<'a>(patterns: &[&str], paths: &'a [String]) -> Vec<&'a str> {
paths
.iter()
.map(String::as_str)
.filter(|path| patterns.iter().any(|pattern| matches(pattern, path)))
.collect()
}
enum Token {
Star,
Any,
Class {
negated: bool,
ranges: Vec<(char, char)>,
},
Char(char),
}
fn tokens(pattern: &str) -> Vec<Token> {
let chars: Vec<char> = pattern.chars().collect();
let mut tokens = Vec::new();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
i += 1;
match c {
'*' if matches!(tokens.last(), Some(Token::Star)) => {}
'*' => tokens.push(Token::Star),
'?' => tokens.push(Token::Any),
'[' => match class(&chars, i) {
Some((token, next)) => {
tokens.push(token);
i = next;
}
None => tokens.push(Token::Char('[')),
},
c => tokens.push(Token::Char(c)),
}
}
tokens
}
fn class(chars: &[char], start: usize) -> Option<(Token, usize)> {
let negated = chars.get(start) == Some(&'!');
let body = start + usize::from(negated);
let mut end = body + usize::from(chars.get(body) == Some(&']'));
while end < chars.len() && chars[end] != ']' {
end += 1;
}
if end >= chars.len() {
return None;
}
let body = &chars[body..end];
let mut ranges = Vec::new();
let mut k = 0;
while k < body.len() {
if k + 2 < body.len() && body[k + 1] == '-' {
ranges.push((body[k], body[k + 2]));
k += 3;
} else {
ranges.push((body[k], body[k]));
k += 1;
}
}
Some((Token::Class { negated, ranges }, end + 1))
}
fn glob(tokens: &[Token], text: &[char]) -> bool {
let (mut t, mut p) = (0, 0);
let mut star: Option<(usize, usize)> = None;
while p < text.len() {
match tokens.get(t) {
Some(Token::Star) => {
star = Some((t + 1, p));
t += 1;
continue;
}
Some(token) if one(token, text[p]) => {
t += 1;
p += 1;
continue;
}
_ => {}
}
let Some((after, from)) = star else {
return false;
};
star = Some((after, from + 1));
t = after;
p = from + 1;
}
tokens[t..].iter().all(|token| matches!(token, Token::Star))
}
fn one(token: &Token, c: char) -> bool {
match token {
Token::Star => false,
Token::Any => true,
Token::Char(expected) => *expected == c,
Token::Class { negated, ranges } => {
*negated != ranges.iter().any(|&(lo, hi)| lo <= c && c <= hi)
}
}
}