Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --files-without-matches flag. #239

Merged
merged 1 commit into from
Nov 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/rg.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions doc/rg.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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 \
Expand Down
5 changes: 4 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"),
Expand Down
20 changes: 20 additions & 0 deletions src/search_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -277,6 +289,14 @@ and exhibited clearly, with a label attached.\
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]
fn max_count() {
let (count, out) = search(
Expand Down
33 changes: 27 additions & 6 deletions src/search_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -986,6 +999,14 @@ fn main() {
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]
fn max_count() {
let (count, out) = search_smallcap(
Expand Down
12 changes: 12 additions & 0 deletions src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1058,6 +1066,16 @@ sherlock!(feature_89_files_with_matches, "Sherlock", ".",
assert_eq!(lines, "sherlock\x00");
});

// See: https://github.com/BurntSushi/ripgrep/issues/89
sherlock!(feature_89_files_without_matches, "Sherlock", ".",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should say feature_138.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually meant issue #89, since the test is about the interaction between --null and this feature.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, missed that. Sounds good!

|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| {
Expand Down