pub fn glob_match(pattern: &str, name: &str) -> bool {
let pattern = pattern.to_lowercase();
let name = name.to_lowercase();
glob_match_impl(&pattern, &name)
}
fn glob_match_impl(pattern: &str, name: &str) -> bool {
let mut p = pattern.chars().peekable();
let mut n = name.chars().peekable();
while let Some(pc) = p.next() {
match pc {
'*' => {
if p.peek().is_none() {
return true;
}
let rest_pattern: String = p.clone().collect();
let mut rest_name: String = n.clone().collect();
while !rest_name.is_empty() {
if glob_match_impl(&rest_pattern, &rest_name) {
return true;
}
rest_name = rest_name.chars().skip(1).collect();
}
return glob_match_impl(&rest_pattern, "");
}
'?' => {
if n.next().is_none() {
return false;
}
}
c => {
if n.next() != Some(c) {
return false;
}
}
}
}
n.peek().is_none()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exact_match() {
assert!(glob_match("foo.txt", "foo.txt"));
assert!(glob_match("FOO.TXT", "foo.txt")); assert!(!glob_match("foo.txt", "bar.txt"));
}
#[test]
fn test_star_wildcard() {
assert!(glob_match("*.rs", "main.rs"));
assert!(glob_match("*.rs", "test.rs"));
assert!(!glob_match("*.rs", "main.txt"));
assert!(glob_match("test_*", "test_foo"));
assert!(glob_match("test_*", "test_"));
assert!(glob_match("*", "anything"));
assert!(glob_match("*.*", "file.txt"));
}
#[test]
fn test_question_wildcard() {
assert!(glob_match("?.txt", "a.txt"));
assert!(!glob_match("?.txt", "ab.txt"));
assert!(glob_match("??.rs", "ab.rs"));
assert!(!glob_match("??.rs", "abc.rs"));
}
#[test]
fn test_combined_wildcards() {
assert!(glob_match("*.?", "file.c"));
assert!(glob_match("test_*.rs", "test_foo.rs"));
assert!(glob_match("*_test.rs", "foo_test.rs"));
}
#[test]
fn test_empty_cases() {
assert!(glob_match("*", ""));
assert!(!glob_match("?", ""));
assert!(glob_match("", ""));
assert!(!glob_match("a", ""));
}
}