summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Luz <dev@mernen.com>2016-11-19 21:48:59 -0200
committerDaniel Luz <dev@mernen.com>2016-11-19 21:48:59 -0200
commitbd3e7eedb1d8698a46e18afd2041e675f6597947 (patch)
tree327d6c6f2f30174ae754232480bc656ac5713105
parent1e6c2ac8e374c9f45204ecce1ddd96842ed26d15 (diff)
Add --files-without-matches flag.
Performs the opposite of --files-with-matches: only shows paths of files that contain zero matches. Closes #138
-rw-r--r--doc/rg.15
-rw-r--r--doc/rg.1.md3
-rw-r--r--src/app.rs3
-rw-r--r--src/args.rs5
-rw-r--r--src/search_buffer.rs20
-rw-r--r--src/search_stream.rs33
-rw-r--r--src/worker.rs12
-rw-r--r--tests/tests.rs18
8 files changed, 92 insertions, 7 deletions
diff --git a/doc/rg.1 b/doc/rg.1
index bc756317..2442a43f 100644
--- a/doc/rg.1
+++ b/doc/rg.1
@@ -182,6 +182,11 @@ Only show path of each file with matches.
.RS
.RE
.TP
+.B \-\-files\-without\-matches
+Only show path of each file with no matches.
+.RS
+.RE
+.TP
.B \-H, \-\-with\-filename
Prefix each match with the file name that contains it.
This is the default when more than one file is searched.
diff --git a/doc/rg.1.md b/doc/rg.1.md
index a3d37667..3980e873 100644
--- a/doc/rg.1.md
+++ b/doc/rg.1.md
@@ -119,6 +119,9 @@ Project home page: https://github.com/BurntSushi/ripgrep
-l, --files-with-matches
: Only show path of each file with matches.
+--files-without-matches
+: Only show path of each file with no matches.
+
-H, --with-filename
: Prefix each match with the file name that contains it. This is the
default when more than one file is searched.
diff --git a/src/app.rs b/src/app.rs
index 5edaf999..a549bc59 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -124,6 +124,7 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
.value_name("FILE").takes_value(true)
.multiple(true).number_of_values(1))
.arg(flag("files-with-matches").short("l"))
+ .arg(flag("files-without-matches"))
.arg(flag("with-filename").short("H"))
.arg(flag("no-filename"))
.arg(flag("heading"))
@@ -304,6 +305,8 @@ lazy_static! {
lines, and the newline is not counted as part of the pattern.");
doc!(h, "files-with-matches",
"Only show the path of each file with at least one match.");
+ doc!(h, "files-without-matches",
+ "Only show the path of each file that contains zero matches.");
doc!(h, "with-filename",
"Show file name for each match.",
"Prefix each match with the file name that contains it. This is \
diff --git a/src/args.rs b/src/args.rs
index a4955d5f..58cdee61 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -44,6 +44,7 @@ pub struct Args {
context_separator: Vec<u8>,
count: bool,
files_with_matches: bool,
+ files_without_matches: bool,
eol: u8,
files: bool,
follow: bool,
@@ -158,7 +159,7 @@ impl Args {
/// Retrieve the configured file separator.
pub fn file_separator(&self) -> Option<Vec<u8>> {
- if self.heading && !self.count && !self.files_with_matches {
+ if self.heading && !self.count && !self.files_with_matches && !self.files_without_matches {
Some(b"".to_vec())
} else if self.before_context > 0 || self.after_context > 0 {
Some(self.context_separator.clone())
@@ -217,6 +218,7 @@ impl Args {
.before_context(self.before_context)
.count(self.count)
.files_with_matches(self.files_with_matches)
+ .files_without_matches(self.files_without_matches)
.eol(self.eol)
.line_number(self.line_number)
.invert_match(self.invert_match)
@@ -314,6 +316,7 @@ impl<'a> ArgMatches<'a> {
context_separator: self.context_separator(),
count: self.is_present("count"),
files_with_matches: self.is_present("files-with-matches"),
+ files_without_matches: self.is_present("files-without-matches"),
eol: b'\n',
files: self.is_present("files"),
follow: self.is_present("follow"),
diff --git a/src/search_buffer.rs b/src/search_buffer.rs
index c7c3bca0..16a161e1 100644
--- a/src/search_buffer.rs
+++ b/src/search_buffer.rs
@@ -61,6 +61,15 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
self
}
+ /// If enabled, searching will print the path of files that *don't* match
+ /// the given pattern.
+ ///
+ /// Disabled by default.
+ pub fn files_without_matches(mut self, yes: bool) -> Self {
+ self.opts.files_without_matches = yes;
+ self
+ }
+
/// Set the end-of-line byte used by this searcher.
pub fn eol(mut self, eol: u8) -> Self {
self.opts.eol = eol;
@@ -133,6 +142,9 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
if self.opts.files_with_matches && self.match_count > 0 {
self.printer.path(self.path);
}
+ if self.opts.files_without_matches && self.match_count == 0 {
+ self.printer.path(self.path);
+ }
self.match_count
}
@@ -278,6 +290,14 @@ and exhibited clearly, with a label attached.\
}
#[test]
+ fn files_without_matches() {
+ let (count, out) = search(
+ "zzzz", SHERLOCK, |s| s.files_without_matches(true));
+ assert_eq!(0, count);
+ assert_eq!(out, "/baz.rs\n");
+ }
+
+ #[test]
fn max_count() {
let (count, out) = search(
"Sherlock", SHERLOCK, |s| s.max_count(Some(1)));
diff --git a/src/search_stream.rs b/src/search_stream.rs
index 6ef8c451..b92fa806 100644
--- a/src/search_stream.rs
+++ b/src/search_stream.rs
@@ -82,6 +82,7 @@ pub struct Options {
pub before_context: usize,
pub count: bool,
pub files_with_matches: bool,
+ pub files_without_matches: bool,
pub eol: u8,
pub invert_match: bool,
pub line_number: bool,
@@ -97,6 +98,7 @@ impl Default for Options {
before_context: 0,
count: false,
files_with_matches: false,
+ files_without_matches: false,
eol: b'\n',
invert_match: false,
line_number: false,
@@ -109,16 +111,17 @@ impl Default for Options {
}
impl Options {
- /// Several options (--quiet, --count, --files-with-matches) imply that
- /// we shouldn't ever display matches.
+ /// Several options (--quiet, --count, --files-with-matches,
+ /// --files-without-matches) imply that we shouldn't ever display matches.
pub fn skip_matches(&self) -> bool {
- self.count || self.files_with_matches || self.quiet
+ self.count || self.files_with_matches || self.files_without_matches
+ || self.quiet
}
- /// Some options (--quiet, --files-with-matches) imply that we can stop
- /// searching after the first match.
+ /// Some options (--quiet, --files-with-matches, --files-without-matches)
+ /// imply that we can stop searching after the first match.
pub fn stop_after_first_match(&self) -> bool {
- self.files_with_matches || self.quiet
+ self.files_with_matches || self.files_without_matches || self.quiet
}
/// Returns true if the search should terminate based on the match count.
@@ -199,6 +202,14 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
self
}
+ /// If enabled, searching will print the path of files without any matches.
+ ///
+ /// Disabled by default.
+ pub fn files_without_matches(mut self, yes: bool) -> Self {
+ self.opts.files_without_matches = yes;
+ self
+ }
+
/// Set the end-of-line byte used by this searcher.
pub fn eol(mut self, eol: u8) -> Self {
self.opts.eol = eol;
@@ -296,6 +307,8 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
} else if self.opts.files_with_matches {
self.printer.path(self.path);
}
+ } else if self.match_count == 0 && self.opts.files_without_matches {
+ self.printer.path(self.path);
}
Ok(self.match_count)
}
@@ -987,6 +1000,14 @@ fn main() {
}
#[test]
+ fn files_without_matches() {
+ let (count, out) = search_smallcap(
+ "zzzz", SHERLOCK, |s| s.files_without_matches(true));
+ assert_eq!(0, count);
+ assert_eq!(out, "/baz.rs\n");
+ }
+
+ #[test]
fn max_count() {
let (count, out) = search_smallcap(
"Sherlock", SHERLOCK, |s| s.max_count(Some(1)));
diff --git a/src/worker.rs b/src/worker.rs
index 0ade140a..23ed7549 100644
--- a/src/worker.rs
+++ b/src/worker.rs
@@ -31,6 +31,7 @@ struct Options {
before_context: usize,
count: bool,
files_with_matches: bool,
+ files_without_matches: bool,
eol: u8,
invert_match: bool,
line_number: bool,
@@ -48,6 +49,7 @@ impl Default for Options {
before_context: 0,
count: false,
files_with_matches: false,
+ files_without_matches: false,
eol: b'\n',
invert_match: false,
line_number: false,
@@ -112,6 +114,14 @@ impl WorkerBuilder {
self
}
+ /// If enabled, searching will print the path of files without any matches.
+ ///
+ /// Disabled by default.
+ pub fn files_without_matches(mut self, yes: bool) -> Self {
+ self.opts.files_without_matches = yes;
+ self
+ }
+
/// Set the end-of-line byte used by this searcher.
pub fn eol(mut self, eol: u8) -> Self {
self.opts.eol = eol;
@@ -230,6 +240,7 @@ impl Worker {
.before_context(self.opts.before_context)
.count(self.opts.count)
.files_with_matches(self.opts.files_with_matches)
+ .files_without_matches(self.opts.files_without_matches)
.eol(self.opts.eol)
.line_number(self.opts.line_number)
.invert_match(self.opts.invert_match)
@@ -260,6 +271,7 @@ impl Worker {
Ok(searcher
.count(self.opts.count)
.files_with_matches(self.opts.files_with_matches)
+ .files_without_matches(self.opts.files_without_matches)
.eol(self.opts.eol)
.line_number(self.opts.line_number)
.invert_match(self.opts.invert_match)
diff --git a/tests/tests.rs b/tests/tests.rs
index bdafb298..66c2e51c 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -339,6 +339,14 @@ sherlock!(files_with_matches, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, expected);
});
+sherlock!(files_without_matches, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
+ wd.create("file.py", "foo");
+ cmd.arg("--files-without-matches");
+ let lines: String = wd.stdout(&mut cmd);
+ let expected = "file.py\n";
+ assert_eq!(lines, expected);
+});
+
sherlock!(after_context, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-A").arg("1");
let lines: String = wd.stdout(&mut cmd);
@@ -1059,6 +1067,16 @@ sherlock!(feature_89_files_with_matches, "Sherlock", ".",
});
// See: https://github.com/BurntSushi/ripgrep/issues/89
+sherlock!(feature_89_files_without_matches, "Sherlock", ".",
+|wd: WorkDir, mut cmd: Command| {
+ wd.create("file.py", "foo");
+ cmd.arg("--null").arg("--files-without-matches");
+
+ let lines: String = wd.stdout(&mut cmd);
+ assert_eq!(lines, "file.py\x00");
+});
+
+// See: https://github.com/BurntSushi/ripgrep/issues/89
sherlock!(feature_89_count, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("--null").arg("--count");