Add a --null flag.

This flag causes a NUL byte to follow any file path in ripgrep's output.

Closes #89.
This commit is contained in:
Andrew Gallant 2016-09-26 19:21:17 -04:00
parent d306403440
commit 7a3fd1f23f
5 changed files with 102 additions and 5 deletions

View File

@ -239,6 +239,14 @@ Note that .ignore files will continue to be respected.
.RS .RS
.RE .RE
.TP .TP
.B \-\-null
Whenever a file name is printed, follow it with a NUL byte.
This includes printing filenames before matches, and when printing a
list of matching files such as with \-\-count, \-\-files\-with\-matches
and \-\-files.
.RS
.RE
.TP
.B \-p, \-\-pretty .B \-p, \-\-pretty
Alias for \-\-color=always \-\-heading \-n. Alias for \-\-color=always \-\-heading \-n.
.RS .RS

View File

@ -155,6 +155,12 @@ the raw speed of grep.
: Don't respect version control ignore files (e.g., .gitignore). : Don't respect version control ignore files (e.g., .gitignore).
Note that .ignore files will continue to be respected. Note that .ignore files will continue to be respected.
--null
: Whenever a file name is printed, follow it with a NUL byte.
This includes printing filenames before matches, and when printing
a list of matching files such as with --count, --files-with-matches
and --files.
-p, --pretty -p, --pretty
: Alias for --color=always --heading -n. : Alias for --color=always --heading -n.

View File

@ -155,6 +155,12 @@ Less common options:
Don't respect version control ignore files (e.g., .gitignore). Don't respect version control ignore files (e.g., .gitignore).
Note that .ignore files will continue to be respected. Note that .ignore files will continue to be respected.
--null
Whenever a file name is printed, follow it with a NUL byte.
This includes printing filenames before matches, and when printing
a list of matching files such as with --count, --files-with-matches
and --files.
-p, --pretty -p, --pretty
Alias for --color=always --heading -n. Alias for --color=always --heading -n.
@ -224,6 +230,7 @@ pub struct RawArgs {
flag_no_line_number: bool, flag_no_line_number: bool,
flag_no_mmap: bool, flag_no_mmap: bool,
flag_no_filename: bool, flag_no_filename: bool,
flag_null: bool,
flag_pretty: bool, flag_pretty: bool,
flag_quiet: bool, flag_quiet: bool,
flag_regexp: Vec<String>, flag_regexp: Vec<String>,
@ -269,6 +276,7 @@ pub struct Args {
no_ignore: bool, no_ignore: bool,
no_ignore_parent: bool, no_ignore_parent: bool,
no_ignore_vcs: bool, no_ignore_vcs: bool,
null: bool,
quiet: bool, quiet: bool,
replace: Option<Vec<u8>>, replace: Option<Vec<u8>>,
text: bool, text: bool,
@ -399,6 +407,7 @@ impl RawArgs {
no_ignore_vcs: no_ignore_vcs:
// --no-ignore implies --no-ignore-vcs // --no-ignore implies --no-ignore-vcs
self.flag_no_ignore_vcs || no_ignore, self.flag_no_ignore_vcs || no_ignore,
null: self.flag_null,
quiet: self.flag_quiet, quiet: self.flag_quiet,
replace: self.flag_replace.clone().map(|s| s.into_bytes()), replace: self.flag_replace.clone().map(|s| s.into_bytes()),
text: text, text: text,
@ -553,6 +562,7 @@ impl Args {
.heading(self.heading) .heading(self.heading)
.line_per_match(self.line_per_match) .line_per_match(self.line_per_match)
.quiet(self.quiet) .quiet(self.quiet)
.null(self.null)
.with_filename(self.with_filename); .with_filename(self.with_filename);
if let Some(ref rep) = self.replace { if let Some(ref rep) = self.replace {
p = p.replace(rep.clone()); p = p.replace(rep.clone());

View File

@ -33,6 +33,9 @@ pub struct Printer<W> {
line_per_match: bool, line_per_match: bool,
/// Whether to suppress all output. /// Whether to suppress all output.
quiet: bool, quiet: bool,
/// Whether to print NUL bytes after a file path instead of new lines
/// or `:`.
null: bool,
/// A string to use as a replacement of each match in a matching line. /// A string to use as a replacement of each match in a matching line.
replace: Option<Vec<u8>>, replace: Option<Vec<u8>>,
/// Whether to prefix each match with the corresponding file name. /// Whether to prefix each match with the corresponding file name.
@ -51,6 +54,7 @@ impl<W: Terminal + Send> Printer<W> {
heading: false, heading: false,
line_per_match: false, line_per_match: false,
quiet: false, quiet: false,
null: false,
replace: None, replace: None,
with_filename: false, with_filename: false,
} }
@ -89,6 +93,13 @@ impl<W: Terminal + Send> Printer<W> {
self self
} }
/// Whether to cause NUL bytes to follow file paths instead of other
/// visual separators (like `:`, `-` and `\n`).
pub fn null(mut self, yes: bool) -> Printer<W> {
self.null = yes;
self
}
/// When set, all output is suppressed. /// When set, all output is suppressed.
pub fn quiet(mut self, yes: bool) -> Printer<W> { pub fn quiet(mut self, yes: bool) -> Printer<W> {
self.quiet = yes; self.quiet = yes;
@ -146,15 +157,23 @@ impl<W: Terminal + Send> Printer<W> {
pub fn path<P: AsRef<Path>>(&mut self, path: P) { pub fn path<P: AsRef<Path>>(&mut self, path: P) {
let path = strip_prefix("./", path.as_ref()).unwrap_or(path.as_ref()); let path = strip_prefix("./", path.as_ref()).unwrap_or(path.as_ref());
self.write_path(path); self.write_path(path);
if self.null {
self.write(b"\x00");
} else {
self.write_eol(); self.write_eol();
} }
}
/// Prints the given path and a count of the number of matches found. /// Prints the given path and a count of the number of matches found.
pub fn path_count<P: AsRef<Path>>(&mut self, path: P, count: u64) { pub fn path_count<P: AsRef<Path>>(&mut self, path: P, count: u64) {
if self.with_filename { if self.with_filename {
self.write_path(path); self.write_path(path);
if self.null {
self.write(b"\x00");
} else {
self.write(b":"); self.write(b":");
} }
}
self.write(count.to_string().as_bytes()); self.write(count.to_string().as_bytes());
self.write_eol(); self.write_eol();
} }
@ -214,8 +233,12 @@ impl<W: Terminal + Send> Printer<W> {
self.write_heading(path.as_ref()); self.write_heading(path.as_ref());
} else if !self.heading && self.with_filename { } else if !self.heading && self.with_filename {
self.write_path(path.as_ref()); self.write_path(path.as_ref());
if self.null {
self.write(b"\x00");
} else {
self.write(b":"); self.write(b":");
} }
}
if let Some(line_number) = line_number { if let Some(line_number) = line_number {
self.line_number(line_number, b':'); self.line_number(line_number, b':');
} }
@ -264,8 +287,12 @@ impl<W: Terminal + Send> Printer<W> {
self.write_heading(path.as_ref()); self.write_heading(path.as_ref());
} else if !self.heading && self.with_filename { } else if !self.heading && self.with_filename {
self.write_path(path.as_ref()); self.write_path(path.as_ref());
if self.null {
self.write(b"\x00");
} else {
self.write(b"-"); self.write(b"-");
} }
}
if let Some(line_number) = line_number { if let Some(line_number) = line_number {
self.line_number(line_number, b'-'); self.line_number(line_number, b'-');
} }
@ -281,7 +308,11 @@ impl<W: Terminal + Send> Printer<W> {
let _ = self.wtr.attr(Attr::Bold); let _ = self.wtr.attr(Attr::Bold);
} }
self.write_path(path.as_ref()); self.write_path(path.as_ref());
if self.null {
self.write(b"\x00");
} else {
self.write_eol(); self.write_eol();
}
if self.wtr.supports_color() { if self.wtr.supports_color() {
let _ = self.wtr.reset(); let _ = self.wtr.reset();
} }

View File

@ -770,6 +770,48 @@ sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
assert_eq!(lines, expected); assert_eq!(lines, expected);
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/89
sherlock!(feature_89_files_with_matches, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("--null").arg("--files-with-matches");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "sherlock\x00");
});
// See: https://github.com/BurntSushi/ripgrep/issues/89
sherlock!(feature_89_count, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("--null").arg("--count");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "sherlock\x002\n");
});
// See: https://github.com/BurntSushi/ripgrep/issues/89
sherlock!(feature_89_files, "NADA", ".",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("--null").arg("--files");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "sherlock\x00");
});
// See: https://github.com/BurntSushi/ripgrep/issues/89
sherlock!(feature_89_match, "Sherlock", ".",
|wd: WorkDir, mut cmd: Command| {
cmd.arg("--null").arg("-C1");
let lines: String = wd.stdout(&mut cmd);
let expected = "\
sherlock\x00For the Doctor Watsons of this world, as opposed to the Sherlock
sherlock\x00Holmeses, success in the province of detective work must always
sherlock\x00be, to a very large extent, the result of luck. Sherlock Holmes
sherlock\x00can extract a clew from a wisp of straw or a flake of cigar ash;
";
assert_eq!(lines, expected);
});
#[test] #[test]
fn binary_nosearch() { fn binary_nosearch() {
let wd = WorkDir::new("binary_nosearch"); let wd = WorkDir::new("binary_nosearch");