pub mod date;
pub mod path;
pub use date::{date_to_path, parse_date, path_to_date};
pub use path::{relative_path_from_dir_to_target, relative_path_from_file_to_target};
pub fn matches_glob_pattern(pattern: &str, path: &str) -> bool {
let pattern = pattern.replace('\\', "/");
let path = path.replace('\\', "/");
matches_glob_recursive(&pattern, &path)
}
fn matches_glob_recursive(pattern: &str, text: &str) -> bool {
if pattern.contains("**") {
let parts: Vec<&str> = pattern.splitn(2, "**").collect();
if parts.len() == 2 {
let prefix = parts[0];
let suffix = parts[1].trim_start_matches('/');
if !prefix.is_empty() && !text.starts_with(prefix.trim_end_matches('/')) {
return false;
}
let remaining = if prefix.is_empty() {
text
} else {
let prefix_trimmed = prefix.trim_end_matches('/');
if text.len() > prefix_trimmed.len() {
text[prefix_trimmed.len()..].trim_start_matches('/')
} else {
return suffix.is_empty();
}
};
if suffix.is_empty() {
return true;
}
for i in 0..=remaining.len() {
if matches_glob_recursive(suffix, &remaining[i..]) {
return true;
}
}
return false;
}
}
let pattern_bytes = pattern.as_bytes();
let text_bytes = text.as_bytes();
let mut pi = 0; let mut ti = 0; let mut star_pi = None; let mut star_ti = None;
while ti < text_bytes.len() {
if pi < pattern_bytes.len() && pattern_bytes[pi] == b'*' {
star_pi = Some(pi + 1);
star_ti = Some(ti);
pi += 1;
} else if pi < pattern_bytes.len()
&& (pattern_bytes[pi] == text_bytes[ti] || pattern_bytes[pi] == b'?')
{
pi += 1;
ti += 1;
} else if let (Some(sp), Some(st)) = (star_pi, star_ti) {
if text_bytes[st] == b'/' {
return false;
}
pi = sp;
star_ti = Some(st + 1);
ti = st + 1;
} else {
return false;
}
}
while pi < pattern_bytes.len() && pattern_bytes[pi] == b'*' {
pi += 1;
}
pi == pattern_bytes.len()
}
#[cfg(test)]
mod glob_tests {
use super::*;
#[test]
fn test_simple_extension_patterns() {
assert!(matches_glob_pattern("*.lock", "Cargo.lock"));
assert!(matches_glob_pattern("*.lock", "package-lock.json") == false);
assert!(matches_glob_pattern("*.toml", "Cargo.toml"));
assert!(matches_glob_pattern("*.toml", "release.toml"));
assert!(matches_glob_pattern("*.md", "README.md"));
assert!(!matches_glob_pattern("*.md", "file.txt"));
}
#[test]
fn test_exact_match() {
assert!(matches_glob_pattern("Cargo.lock", "Cargo.lock"));
assert!(!matches_glob_pattern("Cargo.lock", "cargo.lock"));
assert!(!matches_glob_pattern("Cargo.lock", "Cargo.toml"));
}
#[test]
fn test_directory_patterns() {
assert!(matches_glob_pattern("build/*", "build/output.js"));
assert!(!matches_glob_pattern("build/*", "build/sub/file.js"));
assert!(matches_glob_pattern("build/**", "build/output.js"));
assert!(matches_glob_pattern("build/**", "build/sub/file.js"));
}
#[test]
fn test_star_at_start() {
assert!(matches_glob_pattern("*lock*", "Cargo.lock"));
assert!(matches_glob_pattern("*lock*", "package-lock.json"));
}
}