From 90a11dec5e5765af560d343e879869a60363ef54 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Tue, 28 Mar 2017 21:14:32 +0300 Subject: [PATCH] Add `-o/--only-matching` flag. Currently, the `--only-matching` flag conflicts with the `--replace` flag. In the future, this restriction may be relaxed. Fixes #34 --- doc/rg.1.md | 4 ++++ src/app.rs | 5 +++++ src/args.rs | 3 +++ src/printer.rs | 19 +++++++++++++++++-- tests/tests.rs | 26 ++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/doc/rg.1.md b/doc/rg.1.md index 658eef6a1..421910248 100644 --- a/doc/rg.1.md +++ b/doc/rg.1.md @@ -249,6 +249,10 @@ Project home page: https://github.com/BurntSushi/ripgrep a list of matching files such as with --count, --files-with-matches and --files. +-o, --only-matching +: Print only the matched (non-empty) parts of a matching line, with each such + part on a separate output line. + --path-separator *SEPARATOR* : The path separator to use when printing file paths. This defaults to your platform's path separator, which is / on Unix and \\ on Windows. This flag is diff --git a/src/app.rs b/src/app.rs index f19ff8cf4..94804828a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -144,6 +144,7 @@ pub fn app() -> App<'static, 'static> { .arg(flag("no-ignore-parent")) .arg(flag("no-ignore-vcs")) .arg(flag("null").short("0")) + .arg(flag("only-matching").short("o").conflicts_with("replace")) .arg(flag("path-separator").value_name("SEPARATOR").takes_value(true)) .arg(flag("pretty").short("p")) .arg(flag("replace").short("r").value_name("ARG").takes_value(true)) @@ -421,6 +422,10 @@ lazy_static! { printing a list of matching files such as with --count, \ --files-with-matches and --files. This option is useful for use \ with xargs."); + doc!(h, "only-matching", + "Print only matched parts of a line.", + "Print only the matched (non-empty) parts of a matching line, \ + with each such part on a separate output line."); doc!(h, "path-separator", "Path separator to use when printing file paths.", "The path separator to use when printing file paths. This \ diff --git a/src/args.rs b/src/args.rs index 90e5a9a4b..34f38792d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -65,6 +65,7 @@ pub struct Args { no_ignore_vcs: bool, no_messages: bool, null: bool, + only_matching: bool, path_separator: Option, quiet: bool, quiet_matched: QuietMatched, @@ -141,6 +142,7 @@ impl Args { .heading(self.heading) .line_per_match(self.line_per_match) .null(self.null) + .only_matching(self.only_matching) .path_separator(self.path_separator) .with_filename(self.with_filename) .max_columns(self.max_columns); @@ -345,6 +347,7 @@ impl<'a> ArgMatches<'a> { no_ignore_vcs: self.no_ignore_vcs(), no_messages: self.is_present("no-messages"), null: self.is_present("null"), + only_matching: self.is_present("only-matching"), path_separator: try!(self.path_separator()), quiet: quiet, quiet_matched: QuietMatched::new(quiet), diff --git a/src/printer.rs b/src/printer.rs index c1fc0eb81..c624da2a3 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -58,6 +58,8 @@ pub struct Printer { /// Whether to print NUL bytes after a file path instead of new lines /// or `:`. null: bool, + /// Print only the matched (non-empty) parts of a matching line + only_matching: bool, /// A string to use as a replacement of each match in a matching line. replace: Option>, /// Whether to prefix each match with the corresponding file name. @@ -83,6 +85,7 @@ impl Printer { heading: false, line_per_match: false, null: false, + only_matching: false, replace: None, with_filename: false, colors: ColorSpecs::default(), @@ -144,6 +147,12 @@ impl Printer { self } + /// Print only the matched (non-empty) parts of a matching line + pub fn only_matching(mut self, yes: bool) -> Printer { + self.only_matching = yes; + self + } + /// A separator to use when printing file paths. When empty, use the /// default separator for the current platform. (/ on Unix, \ on Windows.) pub fn path_separator(mut self, sep: Option) -> Printer { @@ -232,7 +241,7 @@ impl Printer { end: usize, line_number: Option, ) { - if !self.line_per_match { + if !self.line_per_match && !self.only_matching { let column = if self.column { Some(re.find(&buf[start..end]) @@ -298,7 +307,13 @@ impl Printer { self.write_eol(); } } else { - self.write_matched_line(re, &buf[start..end]); + let line_buf = if self.only_matching { + let m = re.find(&buf[start..end]).unwrap(); + &buf[start + m.start()..start + m.end()] + } else { + &buf[start..end] + }; + self.write_matched_line(re, line_buf); // write_matched_line guarantees to write a newline. } } diff --git a/tests/tests.rs b/tests/tests.rs index 9457f452c..578dbcbaa 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1166,6 +1166,32 @@ be, to a very large extent, the result of luck. Sherlock Holmes assert_eq!(lines, expected); }); +// See: https://github.com/BurntSushi/ripgrep/issues/34 +sherlock!(feature_34_only_matching, "Sherlock", ".", +|wd: WorkDir, mut cmd: Command| { + cmd.arg("--only-matching"); + + let lines: String = wd.stdout(&mut cmd); + let expected = "\ +sherlock:Sherlock +sherlock:Sherlock +"; + assert_eq!(lines, expected); +}); + +// See: https://github.com/BurntSushi/ripgrep/issues/34 +sherlock!(feature_34_only_matching_line_column, "Sherlock", ".", +|wd: WorkDir, mut cmd: Command| { + cmd.arg("--only-matching").arg("--column").arg("--line-number"); + + let lines: String = wd.stdout(&mut cmd); + let expected = "\ +sherlock:1:57:Sherlock +sherlock:3:49:Sherlock +"; + assert_eq!(lines, expected); +}); + // See: https://github.com/BurntSushi/ripgrep/issues/45 sherlock!(feature_45_relative_cwd, "test", ".", |wd: WorkDir, mut cmd: Command| {