use std::sync::OnceLock;
use crate::evaluator::Evaluator;
#[doc(hidden)]
#[allow(dead_code)]
pub static EVALUATOR: OnceLock<Evaluator> = OnceLock::new();
#[macro_export]
macro_rules! is_ignored {
($path:expr) => {{
$crate::macros::is_ignored::EVALUATOR
.get_or_init($crate::evaluator::Evaluator::default)
.is_ignored($path)
}};
}
#[cfg(test)]
mod tests {
use crate::utils;
use std::{path::PathBuf, process::Command};
use rstest::rstest;
#[rstest]
#[case(vec!["root.txt"])]
#[case(vec!["src","root.txt"])]
#[case(vec!["build","artifact.bin"])]
#[case(vec!["src","build","nested.bin"])]
#[case(vec!["tmp","keep.txt"])]
#[case(vec!["src","lib.rs"])]
#[case(vec!["src","main.rs"])]
#[case(vec!["src","module","deep.log"])]
#[case(vec!["src","module","deep_keep.log"])]
#[case(vec!["src","module","a.tmp"])]
#[case(vec!["src","module","b.tmp"])]
#[case(vec!["src","module","file1.txt"])]
#[case(vec!["src","module","fileA.txt"])]
#[case(vec!["fileA.txt"])]
#[case(vec!["file1.txt"])]
#[case(vec!["file9.txt"])]
#[case(vec!["fileZ.txt"])]
#[case(vec!["filex.txt"])]
#[case(vec!["literal","#hash.txt"])]
#[case(vec!["literal","!bang.txt"])]
#[case(vec!["literal","space file.txt"])]
#[case(vec!["vendor","ignored.txt"])]
#[case(vec!["vendor","keep.me"])]
#[case(vec!["foo"])]
#[case(vec!["foo "])]
#[case(vec!["bar"])]
#[case(vec!["bar "])]
#[case(vec!["baz", "foo", "bar"])]
#[case(vec!["build_dir"])]
#[case(vec!["build_dir","file.txt"])]
#[case(vec!["src","build","file.txt"])]
#[case(vec!["other","src","build","file.txt"])]
#[case(vec!["a","b"])]
#[case(vec!["a","x","b"])]
#[case(vec!["a","x","y","b"])]
#[case(vec!["nested","build"])]
#[case(vec!["nested","sub","build"])]
#[case(vec!["file_vs_dir","same"])]
#[case(vec!["file_vs_dir","same_dir","file.txt"])]
#[case(vec!["double_negation","important.tmp"])]
#[case(vec![".gitignore_should_be_ignored"])]
#[case(vec!["emptydir"])]
#[case(vec!["file1.dat"])]
#[case(vec!["file12.dat"])]
#[case(vec!["x","y","z","globfoo.txt"])]
#[case(vec!["globdir","subdir","file.txt"])]
#[case(vec!["a","b","c","globbar.txt"])]
#[case(vec!["anchored.txt"])]
#[case(vec!["src","anchored.txt"])]
#[case(vec!["dironly","sub","file.txt"])]
#[case(vec!["dironly"])]
#[case(vec!["literal","file*.txt"])]
#[case(vec!["literal","file?.txt"])]
#[case(vec!["literal","file[abc].txt"])]
#[case(vec!["literal","filea.txt"])]
#[case(vec!["precedence.log"])]
#[case(vec!["important.log"])]
#[case(vec!["pruned","deep","keep.txt"])]
#[case(vec!["deep","a","b","c","match.txt"])]
#[case(vec!["deep","match.txt"])]
#[case(vec!["prefix","a","b","suffix.log"])]
#[case(vec!["prefix","suffix.log"])]
#[case(vec!["anydepth","x","y","z","file.tmp"])]
#[case(vec!["file.tmp"])]
#[case(vec!["dirslash"])]
#[case(vec!["dirslash","file.txt"])]
#[case(vec!["dirslash_file"])]
#[case(vec!["escaped",r"space\ "])]
#[case(vec!["escaped","space "])]
#[case(vec!["escaped","space"]) ]
#[case(vec!["escaped","!literal.txt"])]
#[case(vec!["escaped","#literal.txt"])]
#[case(vec!["multi","slash","file.txt"])]
#[case(vec!["reinclude","keep.txt"])]
#[case(vec!["reinclude","other.txt"])]
#[case(vec!["prune_dir","deep","file.txt"])]
#[case(vec!["prune_dir","deep","important.txt"])]
#[case(vec!["escaped","back\\slash.txt"])]
#[case(vec!["escaped","back-slash.txt"])]
#[case(vec!["escaped","back\\-slash.txt"])]
#[case(vec!["charclass","filea.log"])]
#[case(vec!["charclass","filed.log"])]
#[case(vec!["qmark","file1.txt"])]
#[case(vec!["qmark","file12.txt"])]
#[case(vec!["range","file5.txt"])]
#[case(vec!["range","filex.txt"])]
#[case(vec!["anchored_dir","file.txt"])]
#[case(vec!["sub","anchored_dir","file.txt"])]
#[case(vec!["trailing_space","foo"])]
#[case(vec!["trailing_space","foo "])]
#[case(vec!["double_star_root","file.txt"])]
#[case(vec!["dotfile_root",".hidden.txt"])]
#[case(vec!["dotfile_dir",".hidden.txt"])]
#[case(vec!["builder"])]
#[case(vec!["build"])]
#[case(vec!["unicode","fileƩ.txt"])]
fn test_matches_git_check_ignore(#[case] path: Vec<&str>) {
let temp = tempfile::tempdir().expect("Should be able to create a temporary directory");
let repo_path = temp.path();
let relative_path = path.iter().collect::<PathBuf>();
utils::copy_recursively("tests/fixtures/mock-project", repo_path)
.expect("Should be able to copy the mock project");
Command::new("git")
.arg("init")
.arg(repo_path)
.output()
.expect("Should be able to initialize a git repository");
let file_path = repo_path
.iter()
.filter_map(|part| part.to_str())
.chain(path.into_iter())
.collect::<PathBuf>();
let result = is_ignored!(file_path.as_path());
let expected = crate::utils::git_check_ignore(repo_path, file_path.as_path());
assert_eq!(
result,
expected,
"Expected is_ignored! to match git check-ignore for {}. is_ignored! returned {result}, but git check-ignore returned {expected}",
relative_path.display()
);
}
#[test]
fn test_handles_negation() {
let temp = tempfile::tempdir().expect("Should be able to create a temporary directory");
let repo_path = temp.path();
utils::copy_recursively("tests/fixtures/mock-project", repo_path)
.expect("Should be able to copy the mock project");
let not_negated = PathBuf::from_iter(vec!["double_negation/important.tmp"]);
let negated = "important.log";
assert!(is_ignored!(repo_path.join(not_negated).as_path()));
assert!(!is_ignored!(repo_path.join(negated).as_path()));
}
}