use anyhow::{Context, Result};
use ignore::gitignore::{Gitignore, GitignoreBuilder};
use std::path::{Path, PathBuf};
pub struct IgnoreRules {
gitignore: Gitignore,
base_dir: PathBuf,
}
impl IgnoreRules {
pub fn new(rules: &[String], base_dir: &Path) -> Result<Self> {
let mut builder = GitignoreBuilder::new(base_dir);
for rule in rules {
builder
.add_line(None, rule)
.with_context(|| format!("无效的忽略规则: {}", rule))?;
}
let gitignore = builder.build().with_context(|| "构建忽略规则失败")?;
Ok(Self {
gitignore,
base_dir: base_dir.to_path_buf(),
})
}
pub fn should_ignore(&self, path: &Path, is_dir: bool) -> bool {
let relative_path = match path.strip_prefix(&self.base_dir) {
Ok(p) => p,
Err(_) => return false, };
self.gitignore
.matched_path_or_any_parents(relative_path, is_dir)
.is_ignore()
}
pub fn filter_files<I>(&self, files_to_filter: I) -> Result<Vec<PathBuf>>
where
I: Iterator<Item = PathBuf>,
{
let mut files = Vec::new();
for path in files_to_filter {
if !self.should_ignore(&path, path.is_dir()) {
files.push(path);
}
}
Ok(files)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_ignore_rules_creation() -> Result<()> {
let dir = tempdir()?;
let base_dir = dir.path();
let rules = vec!["*.txt".to_string(), "temp_dir/".to_string()];
let ignore_rules = IgnoreRules::new(&rules, base_dir)?;
assert!(!ignore_rules.should_ignore(&base_dir.join("file.rs"), false));
assert!(ignore_rules.should_ignore(&base_dir.join("file.txt"), false));
assert!(ignore_rules.should_ignore(&base_dir.join("temp_dir"), true));
assert!(!ignore_rules.should_ignore(&base_dir.join("another_dir"), true));
Ok(())
}
#[test]
fn test_filter_files() -> Result<()> {
let dir = tempdir()?;
let base_dir = dir.path();
fs::write(base_dir.join("file1.txt"), "content")?;
fs::write(base_dir.join("file2.rs"), "content")?;
fs::create_dir(base_dir.join("ignore_me"))?;
fs::write(base_dir.join("ignore_me/file3.txt"), "content")?;
fs::write(base_dir.join("ignore_me/file4.rs"), "content")?;
let rules = vec!["*.txt".to_string(), "ignore_me/".to_string()];
let ignore_rules = IgnoreRules::new(&rules, base_dir)?;
let all_files = vec![
base_dir.join("file1.txt"),
base_dir.join("file2.rs"),
base_dir.join("ignore_me"),
base_dir.join("ignore_me/file3.txt"),
base_dir.join("ignore_me/file4.rs"),
];
let files = ignore_rules.filter_files(all_files.into_iter())?;
assert_eq!(files.len(), 1);
assert!(files.contains(&base_dir.join("file2.rs")));
assert!(!files.contains(&base_dir.join("file1.txt")));
assert!(!files.contains(&base_dir.join("ignore_me/file3.txt")));
assert!(!files.contains(&base_dir.join("ignore_me/file4.rs")));
Ok(())
}
#[test]
fn test_should_ignore_nested_dir() -> Result<()> {
let dir = tempdir()?;
let base_dir = dir.path();
fs::create_dir_all(base_dir.join("src/components"))?;
fs::write(base_dir.join("src/components/foo.txt"), "content")?;
fs::write(base_dir.join("src/components/bar.js"), "content")?;
let rules = vec!["src/components/".to_string()];
let ignore_rules = IgnoreRules::new(&rules, base_dir)?;
assert!(ignore_rules.should_ignore(&base_dir.join("src/components"), true));
assert!(ignore_rules.should_ignore(&base_dir.join("src/components/foo.txt"), false));
assert!(ignore_rules.should_ignore(&base_dir.join("src/components/bar.js"), false));
Ok(())
}
}