use std::path::Path;
pub fn path_str(path: &Path) -> String {
path.to_string_lossy().replace('\\', "/")
}
pub fn any_glob_matches(globs: &[String], path: &Path) -> bool {
let p = path_str(path);
globs.iter().any(|g| glob_match(g, &p))
}
pub fn glob_match(pattern: &str, path: &str) -> bool {
#[derive(Debug, Clone)]
enum Tok {
Char(char),
Star, DoubleStar, }
let mut tokens: Vec<Tok> = Vec::with_capacity(pattern.len());
let bytes: Vec<char> = pattern.chars().collect();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == '*' {
if i + 1 < bytes.len() && bytes[i + 1] == '*' {
tokens.push(Tok::DoubleStar);
i += 2;
if i < bytes.len() && bytes[i] == '/' {
i += 1;
}
} else {
tokens.push(Tok::Star);
i += 1;
}
} else {
tokens.push(Tok::Char(bytes[i]));
i += 1;
}
}
let chars: Vec<char> = path.chars().collect();
let m = tokens.len();
let n = chars.len();
let mut dp = vec![vec![false; n + 1]; m + 1];
dp[0][0] = true;
for i in 1..=m {
match tokens[i - 1] {
Tok::Star | Tok::DoubleStar => dp[i][0] = dp[i - 1][0],
Tok::Char(_) => {}
}
}
for i in 1..=m {
for j in 1..=n {
match &tokens[i - 1] {
Tok::Char(c) => {
if chars[j - 1] == *c {
dp[i][j] = dp[i - 1][j - 1];
}
}
Tok::Star => {
let skip = dp[i - 1][j];
let stay = dp[i][j - 1] && chars[j - 1] != '/';
dp[i][j] = skip || stay;
}
Tok::DoubleStar => {
let skip = dp[i - 1][j];
let stay = dp[i][j - 1];
dp[i][j] = skip || stay;
}
}
}
}
dp[m][n]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn double_star_matches_deep_paths() {
assert!(glob_match("**/*.rs", "src/foo.rs"));
assert!(glob_match("**/*.rs", "deep/nested/bar.rs"));
assert!(glob_match("docs/**", "docs/a.md"));
assert!(glob_match("docs/**", "docs/sub/a.md"));
}
#[test]
fn single_star_does_not_cross_slash() {
assert!(!glob_match("*.rs", "src/foo.rs"));
assert!(glob_match("*.rs", "foo.rs"));
}
#[test]
fn star_suffix_works_on_dotfiles() {
assert!(glob_match("*.env*", ".env.local"));
}
#[test]
fn any_glob_matches_short_circuits_empty_list() {
assert!(!any_glob_matches(&[], &std::path::PathBuf::from("foo.rs")));
}
#[test]
fn path_str_normalises_backslashes() {
assert_eq!(path_str(&std::path::PathBuf::from("a\\b\\c")), "a/b/c");
}
}