1
0
mirror of https://github.com/BurntSushi/ripgrep.git synced 2025-04-19 09:02:15 +02:00

Add --smart-case.

It does what it says on the tin.

Closes #70.
This commit is contained in:
Andrew Gallant 2016-09-24 21:51:04 -04:00
parent 8eeb0c0b60
commit 1595f0faf5
5 changed files with 63 additions and 1 deletions

View File

@ -238,6 +238,12 @@ Alias for \-\-color=always \-\-heading \-n.
.RS .RS
.RE .RE
.TP .TP
.B \-S, \-\-smart\-case
Search case insensitively if the pattern is all lowercase.
Search case sensitively otherwise.
.RS
.RE
.TP
.B \-j, \-\-threads \f[I]ARG\f[] .B \-j, \-\-threads \f[I]ARG\f[]
The number of threads to use. The number of threads to use.
Defaults to the number of logical CPUs (capped at 6). Defaults to the number of logical CPUs (capped at 6).

View File

@ -154,6 +154,10 @@ the raw speed of grep.
-p, --pretty -p, --pretty
: Alias for --color=always --heading -n. : Alias for --color=always --heading -n.
-S, --smart-case
: Search case insensitively if the pattern is all lowercase.
Search case sensitively otherwise.
-j, --threads *ARG* -j, --threads *ARG*
: The number of threads to use. Defaults to the number of logical CPUs : The number of threads to use. Defaults to the number of logical CPUs
(capped at 6). [default: 0] (capped at 6). [default: 0]

View File

@ -52,6 +52,7 @@ pub struct GrepBuilder {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Options { struct Options {
case_insensitive: bool, case_insensitive: bool,
case_smart: bool,
line_terminator: u8, line_terminator: u8,
size_limit: usize, size_limit: usize,
dfa_size_limit: usize, dfa_size_limit: usize,
@ -61,6 +62,7 @@ impl Default for Options {
fn default() -> Options { fn default() -> Options {
Options { Options {
case_insensitive: false, case_insensitive: false,
case_smart: false,
line_terminator: b'\n', line_terminator: b'\n',
size_limit: 10 * (1 << 20), size_limit: 10 * (1 << 20),
dfa_size_limit: 10 * (1 << 20), dfa_size_limit: 10 * (1 << 20),
@ -98,6 +100,18 @@ impl GrepBuilder {
self self
} }
/// Whether to enable smart case search or not (disabled by default).
///
/// Smart case uses case insensitive search if the regex is contains all
/// lowercase literal characters. Otherwise, a case sensitive search is
/// used instead.
///
/// Enabling the case_insensitive flag overrides this.
pub fn case_smart(mut self, yes: bool) -> GrepBuilder {
self.opts.case_smart = yes;
self
}
/// Set the approximate size limit of the compiled regular expression. /// Set the approximate size limit of the compiled regular expression.
/// ///
/// This roughly corresponds to the number of bytes occupied by a /// This roughly corresponds to the number of bytes occupied by a
@ -148,8 +162,11 @@ impl GrepBuilder {
/// Creates a new regex from the given expression with the current /// Creates a new regex from the given expression with the current
/// configuration. /// configuration.
fn regex(&self, expr: &Expr) -> Result<Regex> { fn regex(&self, expr: &Expr) -> Result<Regex> {
let casei =
self.opts.case_insensitive
|| (self.opts.case_smart && !has_uppercase_literal(expr));
RegexBuilder::new(&expr.to_string()) RegexBuilder::new(&expr.to_string())
.case_insensitive(self.opts.case_insensitive) .case_insensitive(casei)
.multi_line(true) .multi_line(true)
.unicode(true) .unicode(true)
.size_limit(self.opts.size_limit) .size_limit(self.opts.size_limit)
@ -274,6 +291,23 @@ impl<'b, 's> Iterator for Iter<'b, 's> {
} }
} }
fn has_uppercase_literal(expr: &Expr) -> bool {
use syntax::Expr::*;
match *expr {
Literal { ref chars, casei } => {
casei || chars.iter().any(|c| c.is_uppercase())
}
LiteralBytes { ref bytes, casei } => {
casei || bytes.iter().any(|&b| b'A' <= b && b <= b'Z')
}
Group { ref e, .. } => has_uppercase_literal(e),
Repeat { ref e, .. } => has_uppercase_literal(e),
Concat(ref es) => es.iter().any(has_uppercase_literal),
Alternate(ref es) => es.iter().any(has_uppercase_literal),
_ => false,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(unused_imports)] #![allow(unused_imports)]

View File

@ -154,6 +154,10 @@ Less common options:
-p, --pretty -p, --pretty
Alias for --color=always --heading -n. Alias for --color=always --heading -n.
-S, --smart-case
Search case insensitively if the pattern is all lowercase.
Search case sensitively otherwise.
-j, --threads ARG -j, --threads ARG
The number of threads to use. Defaults to the number of logical CPUs The number of threads to use. Defaults to the number of logical CPUs
(capped at 6). [default: 0] (capped at 6). [default: 0]
@ -217,6 +221,7 @@ pub struct RawArgs {
flag_quiet: bool, flag_quiet: bool,
flag_regexp: Vec<String>, flag_regexp: Vec<String>,
flag_replace: Option<String>, flag_replace: Option<String>,
flag_smart_case: bool,
flag_text: bool, flag_text: bool,
flag_threads: usize, flag_threads: usize,
flag_type: Vec<String>, flag_type: Vec<String>,
@ -348,6 +353,7 @@ impl RawArgs {
let types = try!(btypes.build()); let types = try!(btypes.build());
let grep = try!( let grep = try!(
GrepBuilder::new(&pattern) GrepBuilder::new(&pattern)
.case_smart(self.flag_smart_case)
.case_insensitive(self.flag_ignore_case) .case_insensitive(self.flag_ignore_case)
.line_terminator(eol) .line_terminator(eol)
.build() .build()

View File

@ -699,6 +699,18 @@ clean!(feature_68, "test", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, "foo:test\n"); assert_eq!(lines, "foo:test\n");
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/70
sherlock!(feature_70, "sherlock", ".", |wd: WorkDir, mut cmd: Command| {
cmd.arg("--smart-case");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
";
assert_eq!(lines, expected);
});
#[test] #[test]
fn binary_nosearch() { fn binary_nosearch() {
let wd = WorkDir::new("binary_nosearch"); let wd = WorkDir::new("binary_nosearch");