use crate::utils::paths::{absolutize, expand_home};
use std::path::Path;
pub(super) fn wildcard_match(pattern: &str, text: &str) -> bool {
let p: Vec<char> = pattern.chars().collect();
let t: Vec<char> = text.chars().collect();
let (mut pi, mut ti) = (0usize, 0usize);
let mut star: Option<usize> = None;
let mut star_ti = 0usize;
while ti < t.len() {
match p.get(pi) {
Some('*') => {
star = Some(pi);
star_ti = ti;
pi += 1;
}
Some('?') => {
pi += 1;
ti += 1;
}
Some(c) if *c == t[ti] => {
pi += 1;
ti += 1;
}
_ => {
let Some(sp) = star else {
return false;
};
pi = sp + 1;
star_ti += 1;
ti = star_ti;
}
}
}
while p.get(pi) == Some(&'*') {
pi += 1;
}
pi == p.len()
}
pub(super) fn canonical_forms(path: &str, cwd: &Path) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let mut push = |s: String| {
if !out.contains(&s) {
out.push(s);
}
};
push(path.to_string());
let expanded = expand_home(path);
if expanded != path {
push(expanded);
}
let base = if Path::new(path).is_absolute() {
Path::new(path).to_path_buf()
} else {
cwd.join(path)
};
push(
absolutize(&base.to_string_lossy(), cwd)
.to_string_lossy()
.to_string(),
);
for candidate in [Path::new(path).to_path_buf(), base] {
if let Ok(real) = candidate.canonicalize() {
push(real.to_string_lossy().to_string());
}
}
if let Ok(real) = cwd.canonicalize() {
push(absolutize(path, &real).to_string_lossy().to_string());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wildcard_crosses_separators() {
assert!(wildcard_match("src/*", "src/a/b/c.rs"));
assert!(wildcard_match("*", "anything at all"));
assert!(wildcard_match("*.env", "src/.env"));
assert!(!wildcard_match("*.env", "src/.env.local"));
assert!(wildcard_match("*.env.*", "src/.env.local"));
assert!(wildcard_match("git status", "git status"));
assert!(!wildcard_match("git status", "git status -s"));
}
#[test]
fn question_mark_is_single_char() {
assert!(wildcard_match("a?c", "abc"));
assert!(!wildcard_match("a?c", "ac"));
assert!(!wildcard_match("a?c", "abbc"));
}
#[test]
fn wildcard_backtracks() {
assert!(wildcard_match("a*b*c", "axxbyyc"));
assert!(!wildcard_match("a*b*c", "axxbyy"));
assert!(wildcard_match("*a*b", "za/b"));
}
#[test]
fn forms_include_relative_and_absolute() {
let cwd = Path::new("/work/proj");
let forms = canonical_forms("src/a.rs", cwd);
assert!(forms.contains(&"src/a.rs".to_string()));
assert!(forms.contains(&"/work/proj/src/a.rs".to_string()));
}
}