use std::path::Path;
pub fn path_is_ignored(path: &Path, patterns: &[String]) -> bool {
use glob::{MatchOptions, Pattern};
if patterns.is_empty() {
return false;
}
let normalized = path
.to_string_lossy()
.replace('\\', "/")
.trim_start_matches("./")
.to_string();
let options = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: false,
};
for pattern in patterns {
let mut pat = pattern.replace('\\', "/");
pat = pat.trim_start_matches("./").to_string();
if pat.ends_with('/') {
pat.push_str("**");
}
let mut candidates = vec![pat.clone()];
if !pat.starts_with("**/") {
candidates.push(format!("**/{pat}"));
}
for candidate in candidates {
if let Ok(compiled) = Pattern::new(&candidate)
&& compiled.matches_with(&normalized, options)
{
return true;
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_path_is_ignored() {
let p = |s: &str| PathBuf::from(s);
let pats = vec!["vendor/".to_string()];
assert!(path_is_ignored(&p("vendor/skip.md"), &pats));
assert!(path_is_ignored(&p("./vendor/nested/skip.md"), &pats));
assert!(!path_is_ignored(&p("docs/keep.md"), &pats));
let pats = vec!["*.backup.md".to_string()];
assert!(path_is_ignored(&p("notes.backup.md"), &pats));
assert!(path_is_ignored(&p("a/b/notes.backup.md"), &pats));
assert!(!path_is_ignored(&p("notes.md"), &pats));
let pats = vec!["not-found.md".to_string()];
assert!(path_is_ignored(&p("not-found.md"), &pats));
assert!(path_is_ignored(&p("src/deep/not-found.md"), &pats));
let pats = vec!["**/generated.md".to_string()];
assert!(path_is_ignored(&p("x/y/generated.md"), &pats));
assert!(!path_is_ignored(&p("anything.md"), &[]));
}
#[test]
fn test_path_is_ignored_normalizes_separators() {
let pats = vec!["generated/".to_string()];
assert!(path_is_ignored(&PathBuf::from(r"generated\api.md"), &pats));
assert!(path_is_ignored(
&PathBuf::from(r"generated\deep\nested.md"),
&pats
));
assert!(!path_is_ignored(&PathBuf::from(r"guide\intro.md"), &pats));
}
#[test]
fn test_path_is_ignored_star_does_not_cross_separator() {
let pats = vec!["sub/*.md".to_string()];
assert!(path_is_ignored(&PathBuf::from("sub/chapter1.md"), &pats));
assert!(!path_is_ignored(
&PathBuf::from("sub/deep/chapter1.md"),
&pats
));
}
}