1
0
mirror of https://github.com/BurntSushi/ripgrep.git synced 2025-05-13 21:26:27 +02:00

Merge pull request #239 from mernen/files-without-matches

Add --files-without-matches flag.
This commit is contained in:
Andrew Gallant 2016-11-19 20:05:00 -05:00 committed by GitHub
commit 61663e2307
8 changed files with 92 additions and 7 deletions

View File

@ -182,6 +182,11 @@ Only show path of each file with matches.
.RS .RS
.RE .RE
.TP .TP
.B \-\-files\-without\-matches
Only show path of each file with no matches.
.RS
.RE
.TP
.B \-H, \-\-with\-filename .B \-H, \-\-with\-filename
Prefix each match with the file name that contains it. Prefix each match with the file name that contains it.
This is the default when more than one file is searched. This is the default when more than one file is searched.

View File

@ -119,6 +119,9 @@ Project home page: https://github.com/BurntSushi/ripgrep
-l, --files-with-matches -l, --files-with-matches
: Only show path of each file 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 -H, --with-filename
: Prefix each match with the file name that contains it. This is the : Prefix each match with the file name that contains it. This is the
default when more than one file is searched. default when more than one file is searched.

View File

@ -124,6 +124,7 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
.value_name("FILE").takes_value(true) .value_name("FILE").takes_value(true)
.multiple(true).number_of_values(1)) .multiple(true).number_of_values(1))
.arg(flag("files-with-matches").short("l")) .arg(flag("files-with-matches").short("l"))
.arg(flag("files-without-matches"))
.arg(flag("with-filename").short("H")) .arg(flag("with-filename").short("H"))
.arg(flag("no-filename")) .arg(flag("no-filename"))
.arg(flag("heading")) .arg(flag("heading"))
@ -304,6 +305,8 @@ lazy_static! {
lines, and the newline is not counted as part of the pattern."); lines, and the newline is not counted as part of the pattern.");
doc!(h, "files-with-matches", doc!(h, "files-with-matches",
"Only show the path of each file with at least one match."); "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", doc!(h, "with-filename",
"Show file name for each match.", "Show file name for each match.",
"Prefix each match with the file name that contains it. This is \ "Prefix each match with the file name that contains it. This is \

View File

@ -44,6 +44,7 @@ pub struct Args {
context_separator: Vec<u8>, context_separator: Vec<u8>,
count: bool, count: bool,
files_with_matches: bool, files_with_matches: bool,
files_without_matches: bool,
eol: u8, eol: u8,
files: bool, files: bool,
follow: bool, follow: bool,
@ -158,7 +159,7 @@ impl Args {
/// Retrieve the configured file separator. /// Retrieve the configured file separator.
pub fn file_separator(&self) -> Option<Vec<u8>> { 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()) Some(b"".to_vec())
} else if self.before_context > 0 || self.after_context > 0 { } else if self.before_context > 0 || self.after_context > 0 {
Some(self.context_separator.clone()) Some(self.context_separator.clone())
@ -217,6 +218,7 @@ impl Args {
.before_context(self.before_context) .before_context(self.before_context)
.count(self.count) .count(self.count)
.files_with_matches(self.files_with_matches) .files_with_matches(self.files_with_matches)
.files_without_matches(self.files_without_matches)
.eol(self.eol) .eol(self.eol)
.line_number(self.line_number) .line_number(self.line_number)
.invert_match(self.invert_match) .invert_match(self.invert_match)
@ -314,6 +316,7 @@ impl<'a> ArgMatches<'a> {
context_separator: self.context_separator(), context_separator: self.context_separator(),
count: self.is_present("count"), count: self.is_present("count"),
files_with_matches: self.is_present("files-with-matches"), files_with_matches: self.is_present("files-with-matches"),
files_without_matches: self.is_present("files-without-matches"),
eol: b'\n', eol: b'\n',
files: self.is_present("files"), files: self.is_present("files"),
follow: self.is_present("follow"), follow: self.is_present("follow"),

View File

@ -61,6 +61,15 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
self 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. /// Set the end-of-line byte used by this searcher.
pub fn eol(mut self, eol: u8) -> Self { pub fn eol(mut self, eol: u8) -> Self {
self.opts.eol = eol; 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 { if self.opts.files_with_matches && self.match_count > 0 {
self.printer.path(self.path); self.printer.path(self.path);
} }
if self.opts.files_without_matches && self.match_count == 0 {
self.printer.path(self.path);
}
self.match_count self.match_count
} }
@ -277,6 +289,14 @@ and exhibited clearly, with a label attached.\
assert_eq!(out, "/baz.rs\n"); assert_eq!(out, "/baz.rs\n");
} }
#[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] #[test]
fn max_count() { fn max_count() {
let (count, out) = search( let (count, out) = search(

View File

@ -82,6 +82,7 @@ pub struct Options {
pub before_context: usize, pub before_context: usize,
pub count: bool, pub count: bool,
pub files_with_matches: bool, pub files_with_matches: bool,
pub files_without_matches: bool,
pub eol: u8, pub eol: u8,
pub invert_match: bool, pub invert_match: bool,
pub line_number: bool, pub line_number: bool,
@ -97,6 +98,7 @@ impl Default for Options {
before_context: 0, before_context: 0,
count: false, count: false,
files_with_matches: false, files_with_matches: false,
files_without_matches: false,
eol: b'\n', eol: b'\n',
invert_match: false, invert_match: false,
line_number: false, line_number: false,
@ -109,16 +111,17 @@ impl Default for Options {
} }
impl Options { impl Options {
/// Several options (--quiet, --count, --files-with-matches) imply that /// Several options (--quiet, --count, --files-with-matches,
/// we shouldn't ever display matches. /// --files-without-matches) imply that we shouldn't ever display matches.
pub fn skip_matches(&self) -> bool { 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 /// Some options (--quiet, --files-with-matches, --files-without-matches)
/// searching after the first match. /// imply that we can stop searching after the first match.
pub fn stop_after_first_match(&self) -> bool { 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. /// 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 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. /// Set the end-of-line byte used by this searcher.
pub fn eol(mut self, eol: u8) -> Self { pub fn eol(mut self, eol: u8) -> Self {
self.opts.eol = eol; 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 { } else if self.opts.files_with_matches {
self.printer.path(self.path); 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) Ok(self.match_count)
} }
@ -986,6 +999,14 @@ fn main() {
assert_eq!(out, "/baz.rs\n"); assert_eq!(out, "/baz.rs\n");
} }
#[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] #[test]
fn max_count() { fn max_count() {
let (count, out) = search_smallcap( let (count, out) = search_smallcap(

View File

@ -31,6 +31,7 @@ struct Options {
before_context: usize, before_context: usize,
count: bool, count: bool,
files_with_matches: bool, files_with_matches: bool,
files_without_matches: bool,
eol: u8, eol: u8,
invert_match: bool, invert_match: bool,
line_number: bool, line_number: bool,
@ -48,6 +49,7 @@ impl Default for Options {
before_context: 0, before_context: 0,
count: false, count: false,
files_with_matches: false, files_with_matches: false,
files_without_matches: false,
eol: b'\n', eol: b'\n',
invert_match: false, invert_match: false,
line_number: false, line_number: false,
@ -112,6 +114,14 @@ impl WorkerBuilder {
self 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. /// Set the end-of-line byte used by this searcher.
pub fn eol(mut self, eol: u8) -> Self { pub fn eol(mut self, eol: u8) -> Self {
self.opts.eol = eol; self.opts.eol = eol;
@ -230,6 +240,7 @@ impl Worker {
.before_context(self.opts.before_context) .before_context(self.opts.before_context)
.count(self.opts.count) .count(self.opts.count)
.files_with_matches(self.opts.files_with_matches) .files_with_matches(self.opts.files_with_matches)
.files_without_matches(self.opts.files_without_matches)
.eol(self.opts.eol) .eol(self.opts.eol)
.line_number(self.opts.line_number) .line_number(self.opts.line_number)
.invert_match(self.opts.invert_match) .invert_match(self.opts.invert_match)
@ -260,6 +271,7 @@ impl Worker {
Ok(searcher Ok(searcher
.count(self.opts.count) .count(self.opts.count)
.files_with_matches(self.opts.files_with_matches) .files_with_matches(self.opts.files_with_matches)
.files_without_matches(self.opts.files_without_matches)
.eol(self.opts.eol) .eol(self.opts.eol)
.line_number(self.opts.line_number) .line_number(self.opts.line_number)
.invert_match(self.opts.invert_match) .invert_match(self.opts.invert_match)

View File

@ -339,6 +339,14 @@ sherlock!(files_with_matches, "Sherlock", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, expected); 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| { sherlock!(after_context, |wd: WorkDir, mut cmd: Command| {
cmd.arg("-A").arg("1"); cmd.arg("-A").arg("1");
let lines: String = wd.stdout(&mut cmd); let lines: String = wd.stdout(&mut cmd);
@ -1058,6 +1066,16 @@ sherlock!(feature_89_files_with_matches, "Sherlock", ".",
assert_eq!(lines, "sherlock\x00"); assert_eq!(lines, "sherlock\x00");
}); });
// 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 // See: https://github.com/BurntSushi/ripgrep/issues/89
sherlock!(feature_89_count, "Sherlock", ".", sherlock!(feature_89_count, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| { |wd: WorkDir, mut cmd: Command| {