diff --git a/doc/rg.1 b/doc/rg.1 index b61376d..edcc25a 100644 --- a/doc/rg.1 +++ b/doc/rg.1 @@ -70,6 +70,7 @@ Show this usage message. .TP .B \-i, \-\-ignore\-case Case insensitive search. +Overridden by \-\-case\-sensitive. .RS .RE .TP @@ -209,6 +210,12 @@ Follow symlinks. .RS .RE .TP +.B \-\-maxdepth \f[I]NUM\f[] +Descend at most NUM directories below the command line arguments. +A value of zero searches only the starting\-points themselves. +.RS +.RE +.TP .B \-\-mmap Search using memory maps when possible. This is enabled by default when ripgrep thinks it will be faster. @@ -252,9 +259,16 @@ Alias for \-\-color=always \-\-heading \-n. .RS .RE .TP +.B \-s, \-\-case\-sensitive +Search case sensitively. +This overrides \-\-ignore\-case and \-\-smart\-case. +.RS +.RE +.TP .B \-S, \-\-smart\-case Search case insensitively if the pattern is all lowercase. Search case sensitively otherwise. +This is overridden by either \-\-case\-sensitive or \-\-ignore\-case. .RS .RE .TP diff --git a/doc/rg.1.md b/doc/rg.1.md index f7379fc..6de16e5 100644 --- a/doc/rg.1.md +++ b/doc/rg.1.md @@ -49,7 +49,7 @@ the raw speed of grep. : Show this usage message. -i, --ignore-case -: Case insensitive search. +: Case insensitive search. Overridden by --case-sensitive. -n, --line-number : Show line numbers (1-based). This is enabled by default at a tty. @@ -168,9 +168,13 @@ the raw speed of grep. -p, --pretty : Alias for --color=always --heading -n. +-s, --case-sensitive +: Search case sensitively. This overrides --ignore-case and --smart-case. + -S, --smart-case : Search case insensitively if the pattern is all lowercase. - Search case sensitively otherwise. + Search case sensitively otherwise. This is overridden by either + --case-sensitive or --ignore-case. -j, --threads *ARG* : The number of threads to use. Defaults to the number of logical CPUs diff --git a/src/args.rs b/src/args.rs index 0f49e53..5cfb14a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -62,6 +62,7 @@ Common options: Precede a glob with a '!' to exclude it. -h, --help Show this usage message. -i, --ignore-case Case insensitive search. + Overridden by --case-sensitive. -n, --line-number Show line numbers (1-based). This is enabled by default at a tty. -N, --no-line-number Suppress line numbers. @@ -168,9 +169,13 @@ Less common options: -p, --pretty Alias for --color=always --heading -n. + -s, --case-sensitive + Search case sensitively. This overrides --ignore-case and --smart-case. + -S, --smart-case Search case insensitively if the pattern is all lowercase. - Search case sensitively otherwise. + Search case sensitively otherwise. This is overridden by + either --case-sensitive or --ignore-case. -j, --threads ARG The number of threads to use. Defaults to the number of logical CPUs @@ -210,6 +215,7 @@ pub struct RawArgs { arg_path: Vec, flag_after_context: usize, flag_before_context: usize, + flag_case_sensitive: bool, flag_color: String, flag_column: bool, flag_context: usize, @@ -257,7 +263,6 @@ pub struct RawArgs { /// Args are transformed/normalized from RawArgs. #[derive(Debug)] pub struct Args { - pattern: String, paths: Vec, after_context: usize, before_context: usize, @@ -287,7 +292,6 @@ pub struct Args { replace: Option>, text: bool, threads: usize, - type_defs: Vec, type_list: bool, types: Types, with_filename: bool, @@ -296,7 +300,6 @@ pub struct Args { impl RawArgs { /// Convert arguments parsed into a configuration used by ripgrep. fn to_args(&self) -> Result { - let pattern = self.pattern(); let paths = if self.arg_path.is_empty() { if atty::on_stdin() @@ -362,7 +365,6 @@ impl RawArgs { } else { self.flag_color == "always" }; - let eol = b'\n'; let mut with_filename = self.flag_with_filename; if !with_filename { @@ -370,22 +372,10 @@ impl RawArgs { } with_filename = with_filename && !self.flag_no_filename; - let mut btypes = TypesBuilder::new(); - btypes.add_defaults(); - try!(self.add_types(&mut btypes)); - let types = try!(btypes.build()); - let grep = try!( - GrepBuilder::new(&pattern) - .case_smart(self.flag_smart_case) - .case_insensitive(self.flag_ignore_case) - .line_terminator(eol) - .build() - ); let no_ignore = self.flag_no_ignore || self.flag_unrestricted >= 1; let hidden = self.flag_hidden || self.flag_unrestricted >= 2; let text = self.flag_text || self.flag_unrestricted >= 3; let mut args = Args { - pattern: pattern, paths: paths, after_context: after_context, before_context: before_context, @@ -394,11 +384,11 @@ impl RawArgs { context_separator: unescape(&self.flag_context_separator), count: self.flag_count, files_with_matches: self.flag_files_with_matches, - eol: eol, + eol: self.eol(), files: self.flag_files, follow: self.flag_follow, glob_overrides: glob_overrides, - grep: grep, + grep: try!(self.grep()), heading: !self.flag_no_heading && self.flag_heading, hidden: hidden, ignore_case: self.flag_ignore_case, @@ -419,9 +409,8 @@ impl RawArgs { replace: self.flag_replace.clone().map(|s| s.into_bytes()), text: text, threads: threads, - type_defs: btypes.definitions(), type_list: self.flag_type_list, - types: types, + types: try!(self.types()), with_filename: with_filename, }; // If stdout is a tty, then apply some special default options. @@ -440,20 +429,22 @@ impl RawArgs { Ok(args) } - fn add_types(&self, types: &mut TypesBuilder) -> Result<()> { + fn types(&self) -> Result { + let mut btypes = TypesBuilder::new(); + btypes.add_defaults(); for ty in &self.flag_type_clear { - types.clear(ty); + btypes.clear(ty); } for def in &self.flag_type_add { - try!(types.add_def(def)); + try!(btypes.add_def(def)); } for ty in &self.flag_type { - types.select(ty); + btypes.select(ty); } for ty in &self.flag_type_not { - types.negate(ty); + btypes.negate(ty); } - Ok(()) + btypes.build().map_err(From::from) } fn pattern(&self) -> String { @@ -483,6 +474,27 @@ impl RawArgs { s } } + + fn eol(&self) -> u8 { + // We might want to make this configurable. + b'\n' + } + + fn grep(&self) -> Result { + let smart = + self.flag_smart_case + && !self.flag_ignore_case + && !self.flag_case_sensitive; + let casei = + self.flag_ignore_case + && !self.flag_case_sensitive; + GrepBuilder::new(&self.pattern()) + .case_smart(smart) + .case_insensitive(casei) + .line_terminator(self.eol()) + .build() + .map_err(From::from) + } } impl Args { @@ -677,7 +689,7 @@ impl Args { /// Returns a list of type definitions currently loaded. pub fn type_defs(&self) -> &[FileTypeDef] { - &self.type_defs + self.types.definitions() } /// Returns true if ripgrep should print the type definitions currently diff --git a/src/types.rs b/src/types.rs index 2b60bcb..320a724 100644 --- a/src/types.rs +++ b/src/types.rs @@ -159,6 +159,7 @@ impl FileTypeDef { /// Types is a file type matcher. #[derive(Clone, Debug)] pub struct Types { + defs: Vec, selected: Option, negated: Option, has_selected: bool, @@ -176,8 +177,10 @@ impl Types { selected: Option, negated: Option, has_selected: bool, + defs: Vec, ) -> Types { Types { + defs: defs, selected: selected, negated: negated, has_selected: has_selected, @@ -193,7 +196,7 @@ impl Types { /// Creates a new file type matcher that never matches. pub fn empty() -> Types { - Types::new(None, None, false) + Types::new(None, None, false, vec![]) } /// Returns a match for the given path against this file type matcher. @@ -233,6 +236,11 @@ impl Types { Match::None } } + + /// Return the set of current file type definitions. + pub fn definitions(&self) -> &[FileTypeDef] { + &self.defs + } } /// TypesBuilder builds a type matcher from a set of file type definitions and @@ -298,7 +306,11 @@ impl TypesBuilder { Some(try!(bset.build_yesno())) }; Ok(Types::new( - selected_globs, negated_globs, !self.selected.is_empty())) + selected_globs, + negated_globs, + !self.selected.is_empty(), + self.definitions(), + )) } /// Return the set of current file type definitions. diff --git a/tests/tests.rs b/tests/tests.rs index 660b352..62fc55a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -754,7 +754,8 @@ clean!(regression_105_part2, "test", ".", |wd: WorkDir, mut cmd: Command| { }); // See: https://github.com/BurntSushi/ripgrep/issues/20 -sherlock!(feature_20, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| { +sherlock!(feature_20_no_filename, "Sherlock", ".", +|wd: WorkDir, mut cmd: Command| { cmd.arg("--no-filename"); let lines: String = wd.stdout(&mut cmd); @@ -766,7 +767,7 @@ be, to a very large extent, the result of luck. Sherlock Holmes }); // See: https://github.com/BurntSushi/ripgrep/issues/68 -clean!(feature_68, "test", ".", |wd: WorkDir, mut cmd: Command| { +clean!(feature_68_no_ignore_vcs, "test", ".", |wd: WorkDir, mut cmd: Command| { wd.create(".gitignore", "foo"); wd.create(".ignore", "bar"); wd.create("foo", "test"); @@ -778,7 +779,8 @@ clean!(feature_68, "test", ".", |wd: WorkDir, mut cmd: Command| { }); // See: https://github.com/BurntSushi/ripgrep/issues/70 -sherlock!(feature_70, "sherlock", ".", |wd: WorkDir, mut cmd: Command| { +sherlock!(feature_70_smart_case, "sherlock", ".", +|wd: WorkDir, mut cmd: Command| { cmd.arg("--smart-case"); let lines: String = wd.stdout(&mut cmd); @@ -832,7 +834,7 @@ sherlock\x00can extract a clew from a wisp of straw or a flake of cigar ash; }); // See: https://github.com/BurntSushi/ripgrep/issues/109 -clean!(max_depth, "far", ".", |wd: WorkDir, mut cmd: Command| { +clean!(feature_109_max_depth, "far", ".", |wd: WorkDir, mut cmd: Command| { wd.create_dir("one"); wd.create("one/pass", "far"); wd.create_dir("one/too"); @@ -842,10 +844,25 @@ clean!(max_depth, "far", ".", |wd: WorkDir, mut cmd: Command| { let lines: String = wd.stdout(&mut cmd); let expected = path("one/pass:far\n"); - assert_eq!(lines, expected); }); +// See: https://github.com/BurntSushi/ripgrep/issues/124 +clean!(feature_109_case_sensitive_part1, "test", ".", +|wd: WorkDir, mut cmd: Command| { + wd.create("foo", "tEsT"); + cmd.arg("--smart-case").arg("--case-sensitive"); + wd.assert_err(&mut cmd); +}); + +// See: https://github.com/BurntSushi/ripgrep/issues/124 +clean!(feature_109_case_sensitive_part2, "test", ".", +|wd: WorkDir, mut cmd: Command| { + wd.create("foo", "tEsT"); + cmd.arg("--ignore-case").arg("--case-sensitive"); + wd.assert_err(&mut cmd); +}); + #[test] fn binary_nosearch() { let wd = WorkDir::new("binary_nosearch");