use std::path::Path;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
pub fn should_ignore(path: &Path, patterns: &[String]) -> bool {
matched(&build_matcher(patterns), path)
}
fn build_matcher(patterns: &[String]) -> Gitignore {
let mut builder = GitignoreBuilder::new("");
for pattern in patterns {
let line = canonical_line(pattern);
let _ = builder.add_line(None, &line);
}
builder.build().unwrap_or_else(|_| Gitignore::empty())
}
fn canonical_line(pattern: &str) -> String {
match pattern {
".heddle" => "/.heddle".to_string(),
".heddleignore" => "/.heddleignore".to_string(),
".git" => "/.git".to_string(),
other => other.to_string(),
}
}
fn matched(gi: &Gitignore, path: &Path) -> bool {
matches!(
gi.matched_path_or_any_parents(path, true),
ignore::Match::Ignore(_)
)
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
#[test]
fn test_glob_extension() {
let patterns = vec!["*.log".to_string()];
assert!(should_ignore(&PathBuf::from("test.log"), &patterns));
assert!(should_ignore(&PathBuf::from("debug.log"), &patterns));
assert!(!should_ignore(&PathBuf::from("test.txt"), &patterns));
}
#[test]
fn test_directory_pattern() {
let patterns = vec!["build/".to_string()];
assert!(should_ignore(&PathBuf::from("build/output.txt"), &patterns));
assert!(should_ignore(&PathBuf::from("build"), &patterns));
assert!(should_ignore(&PathBuf::from("build/anything"), &patterns));
assert!(!should_ignore(&PathBuf::from("builder.txt"), &patterns));
}
#[test]
fn test_simple_pattern() {
let patterns = vec!["node_modules".to_string()];
assert!(should_ignore(
&PathBuf::from("node_modules/package.json"),
&patterns
));
assert!(!should_ignore(&PathBuf::from("src/main.rs"), &patterns));
}
#[test]
fn test_simple_pattern_does_not_match_prefixes() {
let patterns = vec!["target".to_string()];
assert!(should_ignore(
&PathBuf::from("target/output.txt"),
&patterns
));
assert!(should_ignore(&PathBuf::from("build/target/app"), &patterns));
assert!(!should_ignore(&PathBuf::from("target.txt"), &patterns));
assert!(!should_ignore(
&PathBuf::from("targeted/output.txt"),
&patterns
));
}
#[test]
fn test_root_admin_patterns_do_not_ignore_nested_paths() {
let patterns = vec![".heddle".to_string(), ".heddleignore".to_string()];
assert!(should_ignore(&PathBuf::from(".heddle/objects"), &patterns));
assert!(should_ignore(
&PathBuf::from(".heddle/state/index.bin"),
&patterns
));
assert!(should_ignore(&PathBuf::from(".heddleignore"), &patterns));
assert!(!should_ignore(
&PathBuf::from("examples/calculator/.heddle/objects"),
&patterns
));
assert!(!should_ignore(
&PathBuf::from("examples/calculator/.heddle/state/index.bin"),
&patterns
));
assert!(!should_ignore(
&PathBuf::from("examples/calculator/.heddleignore"),
&patterns
));
}
#[test]
fn test_path_relative_glob_matches_specific_directory_only() {
let patterns = vec!["config/*.toml".to_string()];
assert!(should_ignore(
&PathBuf::from("config/secrets.toml"),
&patterns
));
assert!(should_ignore(
&PathBuf::from("config/database.toml"),
&patterns
));
assert!(!should_ignore(&PathBuf::from("secrets.toml"), &patterns));
assert!(!should_ignore(
&PathBuf::from("other/secrets.toml"),
&patterns
));
}
#[test]
fn test_double_star_recursive_glob_descends_directories() {
let patterns = vec!["**/*.pem".to_string()];
assert!(should_ignore(&PathBuf::from("dev.pem"), &patterns));
assert!(should_ignore(&PathBuf::from("keys/dev.pem"), &patterns));
assert!(should_ignore(
&PathBuf::from("nested/deeper/key.pem"),
&patterns
));
assert!(!should_ignore(&PathBuf::from("dev.txt"), &patterns));
}
#[test]
fn test_negation_rule_whitelists_a_path() {
let patterns = vec!["*.log".to_string(), "!keep.log".to_string()];
assert!(should_ignore(&PathBuf::from("debug.log"), &patterns));
assert!(!should_ignore(&PathBuf::from("keep.log"), &patterns));
}
#[test]
fn test_leading_slash_anchors_to_root_only() {
let patterns = vec!["/build".to_string()];
assert!(should_ignore(&PathBuf::from("build/output"), &patterns));
assert!(!should_ignore(
&PathBuf::from("nested/build/file"),
&patterns
));
}
#[test]
fn test_character_class_matches_set() {
let patterns = vec!["[Mm]akefile".to_string()];
assert!(should_ignore(&PathBuf::from("Makefile"), &patterns));
assert!(should_ignore(&PathBuf::from("makefile"), &patterns));
assert!(!should_ignore(&PathBuf::from("Rakefile"), &patterns));
}
#[test]
fn test_comments_and_blank_lines_are_handled_upstream() {
let patterns = vec!["# comment".to_string(), "".to_string(), "*.log".to_string()];
assert!(should_ignore(&PathBuf::from("foo.log"), &patterns));
assert!(!should_ignore(&PathBuf::from("foo.txt"), &patterns));
}
#[test]
fn test_malformed_pattern_does_not_break_matcher() {
let patterns = vec!["[unbalanced".to_string(), "*.log".to_string()];
assert!(should_ignore(&PathBuf::from("foo.log"), &patterns));
}
}