1use std::path::Path;
8
9use gitignore::{self, Gitignore, GitignoreBuilder};
10use {Error, Match};
11
12#[derive(Clone, Debug)]
24pub struct Glob<'a>(GlobInner<'a>);
25
26#[derive(Clone, Debug)]
27enum GlobInner<'a> {
28 UnmatchedIgnore,
30 Matched(&'a gitignore::Glob),
32}
33
34impl<'a> Glob<'a> {
35 fn unmatched() -> Glob<'a> {
36 Glob(GlobInner::UnmatchedIgnore)
37 }
38}
39
40#[derive(Clone, Debug)]
42pub struct Override(Gitignore);
43
44impl Override {
45 pub fn empty() -> Override {
47 Override(Gitignore::empty())
48 }
49
50 pub fn path(&self) -> &Path {
54 self.0.path()
55 }
56
57 pub fn is_empty(&self) -> bool {
61 self.0.is_empty()
62 }
63
64 pub fn num_ignores(&self) -> u64 {
66 self.0.num_whitelists()
67 }
68
69 pub fn num_whitelists(&self) -> u64 {
71 self.0.num_ignores()
72 }
73
74 pub fn matched<'a, P: AsRef<Path>>(
93 &'a self,
94 path: P,
95 is_dir: bool,
96 ) -> Match<Glob<'a>> {
97 if self.is_empty() {
98 return Match::None;
99 }
100 let mat = self.0.matched(path, is_dir).invert();
101 if mat.is_none() && self.num_whitelists() > 0 && !is_dir {
102 return Match::Ignore(Glob::unmatched());
103 }
104 mat.map(move |giglob| Glob(GlobInner::Matched(giglob)))
105 }
106}
107
108pub struct OverrideBuilder {
110 builder: GitignoreBuilder,
111}
112
113impl OverrideBuilder {
114 pub fn new<P: AsRef<Path>>(path: P) -> OverrideBuilder {
118 OverrideBuilder {
119 builder: GitignoreBuilder::new(path),
120 }
121 }
122
123 pub fn build(&self) -> Result<Override, Error> {
127 Ok(Override(self.builder.build()?))
128 }
129
130 pub fn add(&mut self, glob: &str) -> Result<&mut OverrideBuilder, Error> {
137 self.builder.add_line(None, glob)?;
138 Ok(self)
139 }
140
141 pub fn case_insensitive(
147 &mut self,
148 yes: bool,
149 ) -> Result<&mut OverrideBuilder, Error> {
150 self.builder.case_insensitive(yes)?;
153 Ok(self)
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::{Override, OverrideBuilder};
160
161 const ROOT: &'static str = "/home/andrew/foo";
162
163 fn ov(globs: &[&str]) -> Override {
164 let mut builder = OverrideBuilder::new(ROOT);
165 for glob in globs {
166 builder.add(glob).unwrap();
167 }
168 builder.build().unwrap()
169 }
170
171 #[test]
172 fn empty() {
173 let ov = ov(&[]);
174 assert!(ov.matched("a.foo", false).is_none());
175 assert!(ov.matched("a", false).is_none());
176 assert!(ov.matched("", false).is_none());
177 }
178
179 #[test]
180 fn simple() {
181 let ov = ov(&["*.foo", "!*.bar"]);
182 assert!(ov.matched("a.foo", false).is_whitelist());
183 assert!(ov.matched("a.foo", true).is_whitelist());
184 assert!(ov.matched("a.rs", false).is_ignore());
185 assert!(ov.matched("a.rs", true).is_none());
186 assert!(ov.matched("a.bar", false).is_ignore());
187 assert!(ov.matched("a.bar", true).is_ignore());
188 }
189
190 #[test]
191 fn only_ignores() {
192 let ov = ov(&["!*.bar"]);
193 assert!(ov.matched("a.rs", false).is_none());
194 assert!(ov.matched("a.rs", true).is_none());
195 assert!(ov.matched("a.bar", false).is_ignore());
196 assert!(ov.matched("a.bar", true).is_ignore());
197 }
198
199 #[test]
200 fn precedence() {
201 let ov = ov(&["*.foo", "!*.bar.foo"]);
202 assert!(ov.matched("a.foo", false).is_whitelist());
203 assert!(ov.matched("a.baz", false).is_ignore());
204 assert!(ov.matched("a.bar.foo", false).is_ignore());
205 }
206
207 #[test]
208 fn gitignore() {
209 let ov = ov(&["/foo", "bar/*.rs", "baz/**"]);
210 assert!(ov.matched("bar/lib.rs", false).is_whitelist());
211 assert!(ov.matched("bar/wat/lib.rs", false).is_ignore());
212 assert!(ov.matched("wat/bar/lib.rs", false).is_ignore());
213 assert!(ov.matched("foo", false).is_whitelist());
214 assert!(ov.matched("wat/foo", false).is_ignore());
215 assert!(ov.matched("baz", false).is_ignore());
216 assert!(ov.matched("baz/a", false).is_whitelist());
217 assert!(ov.matched("baz/a/b", false).is_whitelist());
218 }
219
220 #[test]
221 fn allow_directories() {
222 let ov = ov(&["*.rs"]);
224 assert!(ov.matched("foo.rs", false).is_whitelist());
225 assert!(ov.matched("foo.c", false).is_ignore());
226 assert!(ov.matched("foo", false).is_ignore());
227 assert!(ov.matched("foo", true).is_none());
228 assert!(ov.matched("src/foo.rs", false).is_whitelist());
229 assert!(ov.matched("src/foo.c", false).is_ignore());
230 assert!(ov.matched("src/foo", false).is_ignore());
231 assert!(ov.matched("src/foo", true).is_none());
232 }
233
234 #[test]
235 fn absolute_path() {
236 let ov = ov(&["!/bar"]);
237 assert!(ov.matched("./foo/bar", false).is_none());
238 }
239
240 #[test]
241 fn case_insensitive() {
242 let ov = OverrideBuilder::new(ROOT)
243 .case_insensitive(true).unwrap()
244 .add("*.html").unwrap()
245 .build().unwrap();
246 assert!(ov.matched("foo.html", false).is_whitelist());
247 assert!(ov.matched("foo.HTML", false).is_whitelist());
248 assert!(ov.matched("foo.htm", false).is_ignore());
249 assert!(ov.matched("foo.HTM", false).is_ignore());
250 }
251
252 #[test]
253 fn default_case_sensitive() {
254 let ov = OverrideBuilder::new(ROOT)
255 .add("*.html").unwrap()
256 .build().unwrap();
257 assert!(ov.matched("foo.html", false).is_whitelist());
258 assert!(ov.matched("foo.HTML", false).is_ignore());
259 assert!(ov.matched("foo.htm", false).is_ignore());
260 assert!(ov.matched("foo.HTM", false).is_ignore());
261 }
262}