mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2024-12-12 19:18:24 +02:00
Add -m/--max-count flag.
This flag limits the number of matches printed *per file*. Closes #159
This commit is contained in:
parent
351eddc17e
commit
58aca2efb2
5
doc/rg.1
5
doc/rg.1
@ -207,6 +207,11 @@ Follow symlinks.
|
|||||||
.RS
|
.RS
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B \-m, \-\-max\-count NUM
|
||||||
|
Limit the number of matching lines per file searched to NUM.
|
||||||
|
.RS
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B \-\-maxdepth \f[I]NUM\f[]
|
.B \-\-maxdepth \f[I]NUM\f[]
|
||||||
Descend at most NUM directories below the command line arguments.
|
Descend at most NUM directories below the command line arguments.
|
||||||
A value of zero searches only the starting\-points themselves.
|
A value of zero searches only the starting\-points themselves.
|
||||||
|
@ -135,6 +135,9 @@ Project home page: https://github.com/BurntSushi/ripgrep
|
|||||||
-L, --follow
|
-L, --follow
|
||||||
: Follow symlinks.
|
: Follow symlinks.
|
||||||
|
|
||||||
|
-m, --max-count NUM
|
||||||
|
: Limit the number of matching lines per file searched to NUM.
|
||||||
|
|
||||||
--maxdepth *NUM*
|
--maxdepth *NUM*
|
||||||
: Descend at most NUM directories below the command line arguments.
|
: Descend at most NUM directories below the command line arguments.
|
||||||
A value of zero searches only the starting-points themselves.
|
A value of zero searches only the starting-points themselves.
|
||||||
|
12
src/args.rs
12
src/args.rs
@ -141,6 +141,9 @@ Less common options:
|
|||||||
-L, --follow
|
-L, --follow
|
||||||
Follow symlinks.
|
Follow symlinks.
|
||||||
|
|
||||||
|
-m, --max-count NUM
|
||||||
|
Limit the number of matching lines per file searched to NUM.
|
||||||
|
|
||||||
--maxdepth NUM
|
--maxdepth NUM
|
||||||
Descend at most NUM directories below the command line arguments.
|
Descend at most NUM directories below the command line arguments.
|
||||||
A value of zero only searches the starting-points themselves.
|
A value of zero only searches the starting-points themselves.
|
||||||
@ -245,6 +248,7 @@ pub struct RawArgs {
|
|||||||
flag_invert_match: bool,
|
flag_invert_match: bool,
|
||||||
flag_line_number: bool,
|
flag_line_number: bool,
|
||||||
flag_fixed_strings: bool,
|
flag_fixed_strings: bool,
|
||||||
|
flag_max_count: Option<usize>,
|
||||||
flag_maxdepth: Option<usize>,
|
flag_maxdepth: Option<usize>,
|
||||||
flag_mmap: bool,
|
flag_mmap: bool,
|
||||||
flag_no_heading: bool,
|
flag_no_heading: bool,
|
||||||
@ -296,6 +300,7 @@ pub struct Args {
|
|||||||
invert_match: bool,
|
invert_match: bool,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
line_per_match: bool,
|
line_per_match: bool,
|
||||||
|
max_count: Option<u64>,
|
||||||
maxdepth: Option<usize>,
|
maxdepth: Option<usize>,
|
||||||
mmap: bool,
|
mmap: bool,
|
||||||
no_ignore: bool,
|
no_ignore: bool,
|
||||||
@ -414,6 +419,7 @@ impl RawArgs {
|
|||||||
invert_match: self.flag_invert_match,
|
invert_match: self.flag_invert_match,
|
||||||
line_number: !self.flag_no_line_number && self.flag_line_number,
|
line_number: !self.flag_no_line_number && self.flag_line_number,
|
||||||
line_per_match: self.flag_vimgrep,
|
line_per_match: self.flag_vimgrep,
|
||||||
|
max_count: self.flag_max_count.map(|max| max as u64),
|
||||||
maxdepth: self.flag_maxdepth,
|
maxdepth: self.flag_maxdepth,
|
||||||
mmap: mmap,
|
mmap: mmap,
|
||||||
no_ignore: no_ignore,
|
no_ignore: no_ignore,
|
||||||
@ -629,6 +635,11 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given arguments are known to never produce a match.
|
||||||
|
pub fn never_match(&self) -> bool {
|
||||||
|
self.max_count == Some(0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new buffer for use with searching.
|
/// Create a new buffer for use with searching.
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
pub fn outbuf(&self) -> ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> {
|
pub fn outbuf(&self) -> ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> {
|
||||||
@ -677,6 +688,7 @@ impl Args {
|
|||||||
.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)
|
||||||
|
.max_count(self.max_count)
|
||||||
.mmap(self.mmap)
|
.mmap(self.mmap)
|
||||||
.quiet(self.quiet)
|
.quiet(self.quiet)
|
||||||
.text(self.text)
|
.text(self.text)
|
||||||
|
@ -73,6 +73,9 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run(args: Arc<Args>) -> Result<u64> {
|
fn run(args: Arc<Args>) -> Result<u64> {
|
||||||
|
if args.never_match() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
let args = args.clone();
|
let args = args.clone();
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
|
@ -81,6 +81,14 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Limit the number of matches to the given count.
|
||||||
|
///
|
||||||
|
/// The default is None, which corresponds to no limit.
|
||||||
|
pub fn max_count(mut self, count: Option<u64>) -> Self {
|
||||||
|
self.opts.max_count = count;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// If enabled, don't show any output and quit searching after the first
|
/// If enabled, don't show any output and quit searching after the first
|
||||||
/// match is found.
|
/// match is found.
|
||||||
pub fn quiet(mut self, yes: bool) -> Self {
|
pub fn quiet(mut self, yes: bool) -> Self {
|
||||||
@ -111,11 +119,11 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
|||||||
self.print_match(m.start(), m.end());
|
self.print_match(m.start(), m.end());
|
||||||
}
|
}
|
||||||
last_end = m.end();
|
last_end = m.end();
|
||||||
if self.opts.stop_after_first_match() {
|
if self.opts.terminate(self.match_count) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.opts.invert_match {
|
if self.opts.invert_match && !self.opts.terminate(self.match_count) {
|
||||||
let upto = self.buf.len();
|
let upto = self.buf.len();
|
||||||
self.print_inverted_matches(last_end, upto);
|
self.print_inverted_matches(last_end, upto);
|
||||||
}
|
}
|
||||||
@ -146,6 +154,9 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
|
|||||||
debug_assert!(self.opts.invert_match);
|
debug_assert!(self.opts.invert_match);
|
||||||
let mut it = IterLines::new(self.opts.eol, start);
|
let mut it = IterLines::new(self.opts.eol, start);
|
||||||
while let Some((s, e)) = it.next(&self.buf[..end]) {
|
while let Some((s, e)) = it.next(&self.buf[..end]) {
|
||||||
|
if self.opts.terminate(self.match_count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.print_match(s, e);
|
self.print_match(s, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,6 +277,26 @@ and exhibited clearly, with a label attached.\
|
|||||||
assert_eq!(out, "/baz.rs\n");
|
assert_eq!(out, "/baz.rs\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_count() {
|
||||||
|
let (count, out) = search(
|
||||||
|
"Sherlock", SHERLOCK, |s| s.max_count(Some(1)));
|
||||||
|
assert_eq!(1, count);
|
||||||
|
assert_eq!(out, "\
|
||||||
|
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invert_match_max_count() {
|
||||||
|
let (count, out) = search(
|
||||||
|
"zzzz", SHERLOCK, |s| s.invert_match(true).max_count(Some(1)));
|
||||||
|
assert_eq!(1, count);
|
||||||
|
assert_eq!(out, "\
|
||||||
|
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invert_match() {
|
fn invert_match() {
|
||||||
let (count, out) = search(
|
let (count, out) = search(
|
||||||
|
@ -86,6 +86,7 @@ pub struct Options {
|
|||||||
pub eol: u8,
|
pub eol: u8,
|
||||||
pub invert_match: bool,
|
pub invert_match: bool,
|
||||||
pub line_number: bool,
|
pub line_number: bool,
|
||||||
|
pub max_count: Option<u64>,
|
||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
pub text: bool,
|
pub text: bool,
|
||||||
}
|
}
|
||||||
@ -100,6 +101,7 @@ impl Default for Options {
|
|||||||
eol: b'\n',
|
eol: b'\n',
|
||||||
invert_match: false,
|
invert_match: false,
|
||||||
line_number: false,
|
line_number: false,
|
||||||
|
max_count: None,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
text: false,
|
text: false,
|
||||||
}
|
}
|
||||||
@ -119,6 +121,17 @@ impl Options {
|
|||||||
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.quiet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the search should terminate based on the match count.
|
||||||
|
pub fn terminate(&self, match_count: u64) -> bool {
|
||||||
|
if match_count > 0 && self.stop_after_first_match() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if self.max_count.map_or(false, |max| match_count >= max) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
|
impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
|
||||||
@ -207,6 +220,14 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Limit the number of matches to the given count.
|
||||||
|
///
|
||||||
|
/// The default is None, which corresponds to no limit.
|
||||||
|
pub fn max_count(mut self, count: Option<u64>) -> Self {
|
||||||
|
self.opts.max_count = count;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// If enabled, don't show any output and quit searching after the first
|
/// If enabled, don't show any output and quit searching after the first
|
||||||
/// match is found.
|
/// match is found.
|
||||||
pub fn quiet(mut self, yes: bool) -> Self {
|
pub fn quiet(mut self, yes: bool) -> Self {
|
||||||
@ -282,7 +303,7 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn terminate(&self) -> bool {
|
fn terminate(&self) -> bool {
|
||||||
self.match_count > 0 && self.opts.stop_after_first_match()
|
self.opts.terminate(self.match_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -319,6 +340,9 @@ impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
|
|||||||
debug_assert!(self.opts.invert_match);
|
debug_assert!(self.opts.invert_match);
|
||||||
let mut it = IterLines::new(self.opts.eol, self.inp.pos);
|
let mut it = IterLines::new(self.opts.eol, self.inp.pos);
|
||||||
while let Some((start, end)) = it.next(&self.inp.buf[..upto]) {
|
while let Some((start, end)) = it.next(&self.inp.buf[..upto]) {
|
||||||
|
if self.terminate() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.print_match(start, end);
|
self.print_match(start, end);
|
||||||
self.inp.pos = end;
|
self.inp.pos = end;
|
||||||
}
|
}
|
||||||
@ -962,6 +986,26 @@ fn main() {
|
|||||||
assert_eq!(out, "/baz.rs\n");
|
assert_eq!(out, "/baz.rs\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_count() {
|
||||||
|
let (count, out) = search_smallcap(
|
||||||
|
"Sherlock", SHERLOCK, |s| s.max_count(Some(1)));
|
||||||
|
assert_eq!(1, count);
|
||||||
|
assert_eq!(out, "\
|
||||||
|
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invert_match_max_count() {
|
||||||
|
let (count, out) = search(
|
||||||
|
"zzzz", SHERLOCK, |s| s.invert_match(true).max_count(Some(1)));
|
||||||
|
assert_eq!(1, count);
|
||||||
|
assert_eq!(out, "\
|
||||||
|
/baz.rs:For the Doctor Watsons of this world, as opposed to the Sherlock
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invert_match() {
|
fn invert_match() {
|
||||||
let (count, out) = search_smallcap(
|
let (count, out) = search_smallcap(
|
||||||
|
@ -34,6 +34,7 @@ struct Options {
|
|||||||
eol: u8,
|
eol: u8,
|
||||||
invert_match: bool,
|
invert_match: bool,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
|
max_count: Option<u64>,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
text: bool,
|
text: bool,
|
||||||
}
|
}
|
||||||
@ -49,6 +50,7 @@ impl Default for Options {
|
|||||||
eol: b'\n',
|
eol: b'\n',
|
||||||
invert_match: false,
|
invert_match: false,
|
||||||
line_number: false,
|
line_number: false,
|
||||||
|
max_count: None,
|
||||||
quiet: false,
|
quiet: false,
|
||||||
text: false,
|
text: false,
|
||||||
}
|
}
|
||||||
@ -128,6 +130,14 @@ impl WorkerBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Limit the number of matches to the given count.
|
||||||
|
///
|
||||||
|
/// The default is None, which corresponds to no limit.
|
||||||
|
pub fn max_count(mut self, count: Option<u64>) -> Self {
|
||||||
|
self.opts.max_count = count;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// If enabled, try to use memory maps for searching if possible.
|
/// If enabled, try to use memory maps for searching if possible.
|
||||||
pub fn mmap(mut self, yes: bool) -> Self {
|
pub fn mmap(mut self, yes: bool) -> Self {
|
||||||
self.opts.mmap = yes;
|
self.opts.mmap = yes;
|
||||||
@ -217,6 +227,7 @@ impl Worker {
|
|||||||
.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)
|
||||||
|
.max_count(self.opts.max_count)
|
||||||
.quiet(self.opts.quiet)
|
.quiet(self.opts.quiet)
|
||||||
.text(self.opts.text)
|
.text(self.opts.text)
|
||||||
.run()
|
.run()
|
||||||
@ -246,6 +257,7 @@ impl Worker {
|
|||||||
.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)
|
||||||
|
.max_count(self.opts.max_count)
|
||||||
.quiet(self.opts.quiet)
|
.quiet(self.opts.quiet)
|
||||||
.text(self.opts.text)
|
.text(self.opts.text)
|
||||||
.run())
|
.run())
|
||||||
|
@ -1071,6 +1071,21 @@ clean!(feature_109_case_sensitive_part2, "test", ".",
|
|||||||
wd.assert_err(&mut cmd);
|
wd.assert_err(&mut cmd);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
||||||
|
clean!(feature_159_works, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\ntest");
|
||||||
|
cmd.arg("-m1");
|
||||||
|
let lines: String = wd.stdout(&mut cmd);
|
||||||
|
assert_eq!(lines, "foo:test\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/BurntSushi/ripgrep/issues/159
|
||||||
|
clean!(feature_159_zero_max, "test", ".", |wd: WorkDir, mut cmd: Command| {
|
||||||
|
wd.create("foo", "test\ntest");
|
||||||
|
cmd.arg("-m0");
|
||||||
|
wd.assert_err(&mut cmd);
|
||||||
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn binary_nosearch() {
|
fn binary_nosearch() {
|
||||||
let wd = WorkDir::new("binary_nosearch");
|
let wd = WorkDir::new("binary_nosearch");
|
||||||
|
Loading…
Reference in New Issue
Block a user