1use std::path::Path;
8
9use crate::gitignore::{self, Gitignore, GitignoreBuilder};
10use crate::{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
108#[derive(Clone, Debug)]
110pub struct OverrideBuilder {
111 builder: GitignoreBuilder,
112}
113
114impl OverrideBuilder {
115 pub fn new<P: AsRef<Path>>(path: P) -> OverrideBuilder {
119 OverrideBuilder { builder: GitignoreBuilder::new(path) }
120 }
121
122 pub fn build(&self) -> Result<Override, Error> {
126 Ok(Override(self.builder.build()?))
127 }
128
129 pub fn add(&mut self, glob: &str) -> Result<&mut OverrideBuilder, Error> {
136 self.builder.add_line(None, glob)?;
137 Ok(self)
138 }
139
140 pub fn case_insensitive(
146 &mut self,
147 yes: bool,
148 ) -> Result<&mut OverrideBuilder, Error> {
149 self.builder.case_insensitive(yes)?;
152 Ok(self)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::{Override, OverrideBuilder};
159
160 const ROOT: &'static str = "/home/andrew/foo";
161
162 fn ov(globs: &[&str]) -> Override {
163 let mut builder = OverrideBuilder::new(ROOT);
164 for glob in globs {
165 builder.add(glob).unwrap();
166 }
167 builder.build().unwrap()
168 }
169
170 #[test]
171 fn empty() {
172 let ov = ov(&[]);
173 assert!(ov.matched("a.foo", false).is_none());
174 assert!(ov.matched("a", false).is_none());
175 assert!(ov.matched("", false).is_none());
176 }
177
178 #[test]
179 fn simple() {
180 let ov = ov(&["*.foo", "!*.bar"]);
181 assert!(ov.matched("a.foo", false).is_whitelist());
182 assert!(ov.matched("a.foo", true).is_whitelist());
183 assert!(ov.matched("a.rs", false).is_ignore());
184 assert!(ov.matched("a.rs", true).is_none());
185 assert!(ov.matched("a.bar", false).is_ignore());
186 assert!(ov.matched("a.bar", true).is_ignore());
187 }
188
189 #[test]
190 fn only_ignores() {
191 let ov = ov(&["!*.bar"]);
192 assert!(ov.matched("a.rs", false).is_none());
193 assert!(ov.matched("a.rs", true).is_none());
194 assert!(ov.matched("a.bar", false).is_ignore());
195 assert!(ov.matched("a.bar", true).is_ignore());
196 }
197
198 #[test]
199 fn precedence() {
200 let ov = ov(&["*.foo", "!*.bar.foo"]);
201 assert!(ov.matched("a.foo", false).is_whitelist());
202 assert!(ov.matched("a.baz", false).is_ignore());
203 assert!(ov.matched("a.bar.foo", false).is_ignore());
204 }
205
206 #[test]
207 fn gitignore() {
208 let ov = ov(&["/foo", "bar/*.rs", "baz/**"]);
209 assert!(ov.matched("bar/lib.rs", false).is_whitelist());
210 assert!(ov.matched("bar/wat/lib.rs", false).is_ignore());
211 assert!(ov.matched("wat/bar/lib.rs", false).is_ignore());
212 assert!(ov.matched("foo", false).is_whitelist());
213 assert!(ov.matched("wat/foo", false).is_ignore());
214 assert!(ov.matched("baz", false).is_ignore());
215 assert!(ov.matched("baz/a", false).is_whitelist());
216 assert!(ov.matched("baz/a/b", false).is_whitelist());
217 }
218
219 #[test]
220 fn allow_directories() {
221 let ov = ov(&["*.rs"]);
223 assert!(ov.matched("foo.rs", false).is_whitelist());
224 assert!(ov.matched("foo.c", false).is_ignore());
225 assert!(ov.matched("foo", false).is_ignore());
226 assert!(ov.matched("foo", true).is_none());
227 assert!(ov.matched("src/foo.rs", false).is_whitelist());
228 assert!(ov.matched("src/foo.c", false).is_ignore());
229 assert!(ov.matched("src/foo", false).is_ignore());
230 assert!(ov.matched("src/foo", true).is_none());
231 }
232
233 #[test]
234 fn absolute_path() {
235 let ov = ov(&["!/bar"]);
236 assert!(ov.matched("./foo/bar", false).is_none());
237 }
238
239 #[test]
240 fn case_insensitive() {
241 let ov = OverrideBuilder::new(ROOT)
242 .case_insensitive(true)
243 .unwrap()
244 .add("*.html")
245 .unwrap()
246 .build()
247 .unwrap();
248 assert!(ov.matched("foo.html", false).is_whitelist());
249 assert!(ov.matched("foo.HTML", false).is_whitelist());
250 assert!(ov.matched("foo.htm", false).is_ignore());
251 assert!(ov.matched("foo.HTM", false).is_ignore());
252 }
253
254 #[test]
255 fn default_case_sensitive() {
256 let ov =
257 OverrideBuilder::new(ROOT).add("*.html").unwrap().build().unwrap();
258 assert!(ov.matched("foo.html", false).is_whitelist());
259 assert!(ov.matched("foo.HTML", false).is_ignore());
260 assert!(ov.matched("foo.htm", false).is_ignore());
261 assert!(ov.matched("foo.HTM", false).is_ignore());
262 }
263}