use regex::Regex;
pub fn matches_glob_pattern(file_path: &str, pattern: &str) -> bool {
if pattern.is_empty() {
return false;
}
if pattern.starts_with("**/") && pattern.ends_with("/**") {
let middle = &pattern[3..pattern.len() - 3];
return file_path.contains(&format!("/{}/", middle))
|| file_path.starts_with(&format!("{}/", middle));
}
if pattern.contains("**/") && pattern.contains('*') && !pattern.ends_with("**/") {
if let Some((base_path, file_pattern)) = pattern.split_once("**/") {
let regex_pattern = file_pattern.replace('.', "\\.").replace('*', ".*");
if let Ok(re) = Regex::new(&format!(".*{}$", regex_pattern)) {
return file_path.starts_with(base_path) && re.is_match(file_path);
}
}
}
if let Some(suffix) = pattern.strip_prefix("**/") {
return file_path.ends_with(suffix) || file_path.contains(&format!("/{}", suffix));
}
if pattern.contains("**/") {
if let Some((prefix, suffix)) = pattern.split_once("**/") {
return (prefix.is_empty() || file_path.starts_with(prefix))
&& (suffix.is_empty() || file_path.ends_with(suffix));
}
}
if pattern.ends_with("/**") {
let prefix = &pattern[0..pattern.len() - 3];
return file_path == prefix || file_path.starts_with(&format!("{}/", prefix));
}
if pattern.contains('*') {
if pattern.starts_with("*.") {
let parts: Vec<&str> = file_path.split('/').collect();
let filename = parts.last().unwrap_or(&"");
let extension = &pattern[1..];
if parts.len() > 1 && pattern == "*.json" {
return false;
}
return filename.ends_with(extension);
}
let regex_pattern = pattern.replace('.', "\\.").replace('*', ".*");
if let Ok(re) = Regex::new(&format!("^{}$", regex_pattern)) {
return re.is_match(file_path);
}
}
file_path == pattern
}
pub fn filter_excluded_files(files: Vec<String>, exclude_patterns: Vec<String>) -> Vec<String> {
if exclude_patterns.is_empty() {
return files;
}
files
.into_iter()
.filter(|file| {
!exclude_patterns
.iter()
.any(|pattern| matches_glob_pattern(file, pattern))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_matches_glob_pattern() {
assert!(matches_glob_pattern(
"src/node_modules/package.json",
"**/node_modules/**"
));
assert!(matches_glob_pattern(
"node_modules/package.json",
"**/node_modules/**"
));
assert!(!matches_glob_pattern(
"src/modules/package.json",
"**/node_modules/**"
));
assert!(matches_glob_pattern(
"src/components/Button.tsx",
"src/**/*.tsx"
));
assert!(!matches_glob_pattern(
"lib/components/Button.tsx",
"src/**/*.tsx"
));
assert!(matches_glob_pattern(
"src/components/Button.tsx",
"**/Button.tsx"
));
assert!(matches_glob_pattern("Button.tsx", "**/Button.tsx"));
assert!(!matches_glob_pattern("Button.js", "**/Button.tsx"));
assert!(matches_glob_pattern(
"src/components/Button.tsx",
"src/**/Button.tsx"
));
assert!(!matches_glob_pattern(
"lib/components/Button.tsx",
"src/**/Button.tsx"
));
assert!(matches_glob_pattern("dist/index.js", "dist/**"));
assert!(matches_glob_pattern("dist", "dist/**"));
assert!(!matches_glob_pattern("src/dist/index.js", "dist/**"));
assert!(matches_glob_pattern("package.json", "*.json"));
assert!(!matches_glob_pattern("src/package.json", "*.json"));
assert!(matches_glob_pattern("config.js", "config.*"));
assert!(matches_glob_pattern("package.json", "package.json"));
assert!(!matches_glob_pattern("package.js", "package.json"));
}
#[test]
fn test_filter_excluded_files() {
let files = vec![
"src/index.js".to_string(),
"src/components/Button.tsx".to_string(),
"node_modules/package.json".to_string(),
"dist/bundle.js".to_string(),
];
let exclude_patterns = vec!["**/node_modules/**".to_string(), "dist/**".to_string()];
let filtered = filter_excluded_files(files, exclude_patterns);
assert_eq!(
filtered,
vec![
"src/index.js".to_string(),
"src/components/Button.tsx".to_string(),
]
);
}
}