1use anyhow::{Context, Result};
2use ignore::gitignore::{Gitignore, GitignoreBuilder};
3use std::path::{Path, PathBuf};
4
5pub struct IgnoreRules {
8 gitignore: Gitignore,
9 base_dir: PathBuf,
10}
11
12impl IgnoreRules {
13 pub fn new(rules: &[String], base_dir: &Path) -> Result<Self> {
22 let mut builder = GitignoreBuilder::new(base_dir);
23
24 for rule in rules {
25 builder
26 .add_line(None, rule)
27 .with_context(|| format!("无效的忽略规则: {}", rule))?;
28 }
29
30 let gitignore = builder.build().with_context(|| "构建忽略规则失败")?;
31
32 Ok(Self {
33 gitignore,
34 base_dir: base_dir.to_path_buf(),
35 })
36 }
37
38 pub fn should_ignore(&self, path: &Path, is_dir: bool) -> bool {
47 let relative_path = match path.strip_prefix(&self.base_dir) {
48 Ok(p) => p,
49 Err(_) => return false, };
51
52 self.gitignore
53 .matched_path_or_any_parents(relative_path, is_dir)
54 .is_ignore()
55 }
56
57 pub fn filter_files<I>(&self, files_to_filter: I) -> Result<Vec<PathBuf>>
65 where
66 I: Iterator<Item = PathBuf>,
67 {
68 let mut files = Vec::new();
69 for path in files_to_filter {
70 if !self.should_ignore(&path, path.is_dir()) {
71 files.push(path);
72 }
73 }
74 Ok(files)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use std::fs;
82 use tempfile::tempdir;
83
84 #[test]
85 fn test_ignore_rules_creation() -> Result<()> {
86 let dir = tempdir()?;
87 let base_dir = dir.path();
88 let rules = vec!["*.txt".to_string(), "temp_dir/".to_string()];
89 let ignore_rules = IgnoreRules::new(&rules, base_dir)?;
90
91 assert!(!ignore_rules.should_ignore(&base_dir.join("file.rs"), false));
92 assert!(ignore_rules.should_ignore(&base_dir.join("file.txt"), false));
93 assert!(ignore_rules.should_ignore(&base_dir.join("temp_dir"), true));
94 assert!(!ignore_rules.should_ignore(&base_dir.join("another_dir"), true));
95
96 Ok(())
97 }
98
99 #[test]
100 fn test_filter_files() -> Result<()> {
101 let dir = tempdir()?;
102 let base_dir = dir.path();
103
104 fs::write(base_dir.join("file1.txt"), "content")?;
106 fs::write(base_dir.join("file2.rs"), "content")?;
107 fs::create_dir(base_dir.join("ignore_me"))?;
108 fs::write(base_dir.join("ignore_me/file3.txt"), "content")?;
109 fs::write(base_dir.join("ignore_me/file4.rs"), "content")?;
110
111 let rules = vec!["*.txt".to_string(), "ignore_me/".to_string()];
112 let ignore_rules = IgnoreRules::new(&rules, base_dir)?;
113
114 let all_files = vec![
115 base_dir.join("file1.txt"),
116 base_dir.join("file2.rs"),
117 base_dir.join("ignore_me"),
118 base_dir.join("ignore_me/file3.txt"),
119 base_dir.join("ignore_me/file4.rs"),
120 ];
121
122 let files = ignore_rules.filter_files(all_files.into_iter())?;
123
124 assert_eq!(files.len(), 1);
125 assert!(files.contains(&base_dir.join("file2.rs")));
126 assert!(!files.contains(&base_dir.join("file1.txt")));
127 assert!(!files.contains(&base_dir.join("ignore_me/file3.txt")));
128 assert!(!files.contains(&base_dir.join("ignore_me/file4.rs")));
129
130 Ok(())
131 }
132
133 #[test]
134 fn test_should_ignore_nested_dir() -> Result<()> {
135 let dir = tempdir()?;
136 let base_dir = dir.path();
137
138 fs::create_dir_all(base_dir.join("src/components"))?;
139 fs::write(base_dir.join("src/components/foo.txt"), "content")?;
140 fs::write(base_dir.join("src/components/bar.js"), "content")?;
141
142 let rules = vec!["src/components/".to_string()];
143 let ignore_rules = IgnoreRules::new(&rules, base_dir)?;
144
145 assert!(ignore_rules.should_ignore(&base_dir.join("src/components"), true));
146 assert!(ignore_rules.should_ignore(&base_dir.join("src/components/foo.txt"), false));
147 assert!(ignore_rules.should_ignore(&base_dir.join("src/components/bar.js"), false));
148
149 Ok(())
150 }
151}