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:
commit
61663e2307
5
doc/rg.1
5
doc/rg.1
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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 \
|
||||||
|
@ -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"),
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
|
@ -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| {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user