1use std::path::Path;
9
10use crate::{
11 Error, Match,
12 gitignore::{self, Gitignore, GitignoreBuilder},
13};
14
15#[derive(Clone, Debug)]
27#[allow(dead_code)]
28pub struct Glob<'a>(GlobInner<'a>);
29
30#[derive(Clone, Debug)]
31#[allow(dead_code)]
32enum GlobInner<'a> {
33 UnmatchedIgnore,
35 Matched(&'a gitignore::Glob),
37}
38
39impl<'a> Glob<'a> {
40 fn unmatched() -> Glob<'a> {
41 Glob(GlobInner::UnmatchedIgnore)
42 }
43}
44
45#[derive(Clone, Debug)]
47pub struct Override(Gitignore);
48
49impl Override {
50 pub fn empty() -> Override {
52 Override(Gitignore::empty())
53 }
54
55 pub fn path(&self) -> &Path {
59 self.0.path()
60 }
61
62 pub fn is_empty(&self) -> bool {
66 self.0.is_empty()
67 }
68
69 pub fn num_ignores(&self) -> u64 {
71 self.0.num_whitelists()
72 }
73
74 pub fn num_whitelists(&self) -> u64 {
76 self.0.num_ignores()
77 }
78
79 pub fn matched<'a, P: AsRef<Path>>(
98 &'a self,
99 path: P,
100 is_dir: bool,
101 ) -> Match<Glob<'a>> {
102 if self.is_empty() {
103 return Match::None;
104 }
105 let mat = self.0.matched(path, is_dir).invert();
106 if mat.is_none() && self.num_whitelists() > 0 && !is_dir {
107 return Match::Ignore(Glob::unmatched());
108 }
109 mat.map(move |giglob| Glob(GlobInner::Matched(giglob)))
110 }
111}
112
113#[derive(Clone, Debug)]
115pub struct OverrideBuilder {
116 builder: GitignoreBuilder,
117}
118
119impl OverrideBuilder {
120 pub fn new<P: AsRef<Path>>(path: P) -> OverrideBuilder {
124 let mut builder = GitignoreBuilder::new(path);
125 builder.allow_unclosed_class(false);
126 OverrideBuilder { builder }
127 }
128
129 pub fn build(&self) -> Result<Override, Error> {
133 Ok(Override(self.builder.build()?))
134 }
135
136 pub fn add(&mut self, glob: &str) -> Result<&mut OverrideBuilder, Error> {
143 self.builder.add_line(None, glob)?;
144 Ok(self)
145 }
146
147 pub fn case_insensitive(
154 &mut self,
155 yes: bool,
156 ) -> Result<&mut OverrideBuilder, Error> {
157 self.builder.case_insensitive(yes)?;
160 Ok(self)
161 }
162
163 pub fn allow_unclosed_class(&mut self, yes: bool) -> &mut OverrideBuilder {
181 self.builder.allow_unclosed_class(yes);
182 self
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::{Override, OverrideBuilder};
189
190 const ROOT: &'static str = "/home/andrew/foo";
191
192 fn ov(globs: &[&str]) -> Override {
193 let mut builder = OverrideBuilder::new(ROOT);
194 for glob in globs {
195 builder.add(glob).unwrap();
196 }
197 builder.build().unwrap()
198 }
199
200 #[test]
201 fn empty() {
202 let ov = ov(&[]);
203 assert!(ov.matched("a.foo", false).is_none());
204 assert!(ov.matched("a", false).is_none());
205 assert!(ov.matched("", false).is_none());
206 }
207
208 #[test]
209 fn simple() {
210 let ov = ov(&["*.foo", "!*.bar"]);
211 assert!(ov.matched("a.foo", false).is_whitelist());
212 assert!(ov.matched("a.foo", true).is_whitelist());
213 assert!(ov.matched("a.rs", false).is_ignore());
214 assert!(ov.matched("a.rs", true).is_none());
215 assert!(ov.matched("a.bar", false).is_ignore());
216 assert!(ov.matched("a.bar", true).is_ignore());
217 }
218
219 #[test]
220 fn only_ignores() {
221 let ov = ov(&["!*.bar"]);
222 assert!(ov.matched("a.rs", false).is_none());
223 assert!(ov.matched("a.rs", true).is_none());
224 assert!(ov.matched("a.bar", false).is_ignore());
225 assert!(ov.matched("a.bar", true).is_ignore());
226 }
227
228 #[test]
229 fn precedence() {
230 let ov = ov(&["*.foo", "!*.bar.foo"]);
231 assert!(ov.matched("a.foo", false).is_whitelist());
232 assert!(ov.matched("a.baz", false).is_ignore());
233 assert!(ov.matched("a.bar.foo", false).is_ignore());
234 }
235
236 #[test]
237 fn gitignore() {
238 let ov = ov(&["/foo", "bar/*.rs", "baz/**"]);
239 assert!(ov.matched("bar/lib.rs", false).is_whitelist());
240 assert!(ov.matched("bar/wat/lib.rs", false).is_ignore());
241 assert!(ov.matched("wat/bar/lib.rs", false).is_ignore());
242 assert!(ov.matched("foo", false).is_whitelist());
243 assert!(ov.matched("wat/foo", false).is_ignore());
244 assert!(ov.matched("baz", false).is_ignore());
245 assert!(ov.matched("baz/a", false).is_whitelist());
246 assert!(ov.matched("baz/a/b", false).is_whitelist());
247 }
248
249 #[test]
250 fn allow_directories() {
251 let ov = ov(&["*.rs"]);
253 assert!(ov.matched("foo.rs", false).is_whitelist());
254 assert!(ov.matched("foo.c", false).is_ignore());
255 assert!(ov.matched("foo", false).is_ignore());
256 assert!(ov.matched("foo", true).is_none());
257 assert!(ov.matched("src/foo.rs", false).is_whitelist());
258 assert!(ov.matched("src/foo.c", false).is_ignore());
259 assert!(ov.matched("src/foo", false).is_ignore());
260 assert!(ov.matched("src/foo", true).is_none());
261 }
262
263 #[test]
264 fn absolute_path() {
265 let ov = ov(&["!/bar"]);
266 assert!(ov.matched("./foo/bar", false).is_none());
267 }
268
269 #[test]
270 fn case_insensitive() {
271 let ov = OverrideBuilder::new(ROOT)
272 .case_insensitive(true)
273 .unwrap()
274 .add("*.html")
275 .unwrap()
276 .build()
277 .unwrap();
278 assert!(ov.matched("foo.html", false).is_whitelist());
279 assert!(ov.matched("foo.HTML", false).is_whitelist());
280 assert!(ov.matched("foo.htm", false).is_ignore());
281 assert!(ov.matched("foo.HTM", false).is_ignore());
282 }
283
284 #[test]
285 fn default_case_sensitive() {
286 let ov =
287 OverrideBuilder::new(ROOT).add("*.html").unwrap().build().unwrap();
288 assert!(ov.matched("foo.html", false).is_whitelist());
289 assert!(ov.matched("foo.HTML", false).is_ignore());
290 assert!(ov.matched("foo.htm", false).is_ignore());
291 assert!(ov.matched("foo.HTM", false).is_ignore());
292 }
293}