diff --git a/ignore/src/gitignore.rs b/ignore/src/gitignore.rs index 68655261..7ae91bf8 100644 --- a/ignore/src/gitignore.rs +++ b/ignore/src/gitignore.rs @@ -254,6 +254,7 @@ pub struct GitignoreBuilder { builder: GlobSetBuilder, root: PathBuf, globs: Vec, + case_insensitive: bool, } impl GitignoreBuilder { @@ -269,6 +270,7 @@ impl GitignoreBuilder { builder: GlobSetBuilder::new(), root: strip_prefix("./", root).unwrap_or(root).to_path_buf(), globs: vec![], + case_insensitive: false, } } @@ -424,6 +426,7 @@ impl GitignoreBuilder { let parsed = try!( GlobBuilder::new(&glob.actual) .literal_separator(literal_separator) + .case_insensitive(self.case_insensitive) .build() .map_err(|err| { Error::Glob { @@ -435,6 +438,16 @@ impl GitignoreBuilder { self.globs.push(glob); Ok(self) } + + /// Toggle whether the globs should be matched case insensitively or not. + /// + /// This is disabled by default. + pub fn case_insensitive( + &mut self, yes: bool + ) -> Result<&mut GitignoreBuilder, Error> { + self.case_insensitive = yes; + Ok(self) + } } /// Return the file path of the current environment's global gitignore file. @@ -617,4 +630,21 @@ mod tests { fn regression_106() { gi_from_str("/", " "); } + + #[test] + fn case_insensitive() { + let gi = GitignoreBuilder::new(ROOT) + .case_insensitive(true).unwrap() + .add_str(None, "*.html").unwrap() + .build().unwrap(); + assert!(gi.matched("foo.html", false).is_ignore()); + assert!(gi.matched("foo.HTML", false).is_ignore()); + assert!(!gi.matched("foo.htm", false).is_ignore()); + assert!(!gi.matched("foo.HTM", false).is_ignore()); + } + + ignored!(cs1, ROOT, "*.html", "foo.html"); + not_ignored!(cs2, ROOT, "*.html", "foo.HTML"); + not_ignored!(cs3, ROOT, "*.html", "foo.htm"); + not_ignored!(cs4, ROOT, "*.html", "foo.HTM"); } diff --git a/ignore/src/overrides.rs b/ignore/src/overrides.rs index faf05f31..453066f9 100644 --- a/ignore/src/overrides.rs +++ b/ignore/src/overrides.rs @@ -137,6 +137,16 @@ impl OverrideBuilder { try!(self.builder.add_line(None, glob)); Ok(self) } + + /// Toggle whether the globs should be matched case insensitively or not. + /// + /// This is disabled by default. + pub fn case_insensitive( + &mut self, yes: bool + ) -> Result<&mut OverrideBuilder, Error> { + try!(self.builder.case_insensitive(yes)); + Ok(self) + } } #[cfg(test)] @@ -220,4 +230,27 @@ mod tests { let ov = ov(&["!/bar"]); assert!(ov.matched("./foo/bar", false).is_none()); } + + #[test] + fn case_insensitive() { + let ov = OverrideBuilder::new(ROOT) + .case_insensitive(true).unwrap() + .add("*.html").unwrap() + .build().unwrap(); + assert!(ov.matched("foo.html", false).is_whitelist()); + assert!(ov.matched("foo.HTML", false).is_whitelist()); + assert!(ov.matched("foo.htm", false).is_ignore()); + assert!(ov.matched("foo.HTM", false).is_ignore()); + } + + #[test] + fn default_case_sensitive() { + let ov = OverrideBuilder::new(ROOT) + .add("*.html").unwrap() + .build().unwrap(); + assert!(ov.matched("foo.html", false).is_whitelist()); + assert!(ov.matched("foo.HTML", false).is_ignore()); + assert!(ov.matched("foo.htm", false).is_ignore()); + assert!(ov.matched("foo.HTM", false).is_ignore()); + } } diff --git a/src/app.rs b/src/app.rs index e43dc5ae..832ac6ea 100644 --- a/src/app.rs +++ b/src/app.rs @@ -90,6 +90,9 @@ pub fn app() -> App<'static, 'static> { .arg(flag("glob").short("g") .takes_value(true).multiple(true).number_of_values(1) .value_name("GLOB")) + .arg(flag("iglob") + .takes_value(true).multiple(true).number_of_values(1) + .value_name("GLOB")) .arg(flag("ignore-case").short("i")) .arg(flag("line-number").short("n")) .arg(flag("no-line-number").short("N").overrides_with("line-number")) @@ -270,6 +273,13 @@ lazy_static! { ignore logic. Multiple glob flags may be used. Globbing \ rules match .gitignore globs. Precede a glob with a ! \ to exclude it."); + doc!(h, "iglob", + "Include or exclude files/directories case insensitively.", + "Include or exclude files/directories for searching that \ + match the given glob. This always overrides any other \ + ignore logic. Multiple glob flags may be used. Globbing \ + rules match .gitignore globs. Precede a glob with a ! \ + to exclude it. Globs are matched case insensitively."); doc!(h, "ignore-case", "Case insensitive search.", "Case insensitive search. This is overridden by \ diff --git a/src/args.rs b/src/args.rs index b0e5d648..cd905b45 100644 --- a/src/args.rs +++ b/src/args.rs @@ -771,6 +771,14 @@ impl<'a> ArgMatches<'a> { for glob in self.values_of_lossy_vec("glob") { try!(ovr.add(&glob)); } + // this is smelly. In the long run it might make sense + // to change overridebuilder to be like globsetbuilder + // but this would be a breaking change to the ignore crate + // so it is being shelved for now... + try!(ovr.case_insensitive(true)); + for glob in self.values_of_lossy_vec("iglob") { + try!(ovr.add(&glob)); + } ovr.build().map_err(From::from) } diff --git a/tests/tests.rs b/tests/tests.rs index 95fecfd4..8e0c9e0b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -336,6 +336,21 @@ sherlock!(glob_negate, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { assert_eq!(lines, "file.py:Sherlock\n"); }); +sherlock!(iglob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { + wd.create("file.HTML", "Sherlock"); + cmd.arg("--iglob").arg("*.html"); + let lines: String = wd.stdout(&mut cmd); + assert_eq!(lines, "file.HTML:Sherlock\n"); +}); + +sherlock!(csglob, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { + wd.create("file1.HTML", "Sherlock"); + wd.create("file2.html", "Sherlock"); + cmd.arg("--glob").arg("*.html"); + let lines: String = wd.stdout(&mut cmd); + assert_eq!(lines, "file2.html:Sherlock\n"); +}); + sherlock!(count, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { cmd.arg("--count"); let lines: String = wd.stdout(&mut cmd);