Add --path-separator flag.

This flag permits setting the path separator used for all file paths
printed by ripgrep in normal operation.

Fixes #275
This commit is contained in:
Andrew Gallant 2017-01-10 18:16:15 -05:00
parent 2143bcf9cb
commit 8751e55706
6 changed files with 89 additions and 5 deletions

View File

@ -186,7 +186,7 @@ One byte is equal to one column.
.RS .RS
.RE .RE
.TP .TP
.B \-\-context\-separator \f[I]ARG\f[] .B \-\-context\-separator \f[I]SEPARATOR\f[]
The string to use when separating non\-continuous context lines. The string to use when separating non\-continuous context lines.
Escape sequences may be used. Escape sequences may be used.
[default: \-\-] [default: \-\-]
@ -328,6 +328,16 @@ and \-\-files.
.RS .RS
.RE .RE
.TP .TP
.B \-\-path\-separator \f[I]SEPARATOR\f[]
The path separator to use when printing file paths.
This defaults to your platform\[aq]s path separator, which is / on Unix
and \\ on Windows.
This flag is intended for overriding the default when the environment
demands it (e.g., cygwin).
A path separator is limited to a single byte.
.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

@ -128,7 +128,7 @@ Project home page: https://github.com/BurntSushi/ripgrep
numbers for the first match on each line. Note that this doesn't try numbers for the first match on each line. Note that this doesn't try
to account for Unicode. One byte is equal to one column. to account for Unicode. One byte is equal to one column.
--context-separator *ARG* --context-separator *SEPARATOR*
: The string to use when separating non-continuous context lines. Escape : The string to use when separating non-continuous context lines. Escape
sequences may be used. [default: --] sequences may be used. [default: --]
@ -221,6 +221,12 @@ Project home page: https://github.com/BurntSushi/ripgrep
a list of matching files such as with --count, --files-with-matches a list of matching files such as with --count, --files-with-matches
and --files. and --files.
--path-separator *SEPARATOR*
: The path separator to use when printing file paths. This defaults to your
platform's path separator, which is / on Unix and \\ on Windows. This flag is
intended for overriding the default when the environment demands it (e.g.,
cygwin). A path separator is limited to a single byte.
-p, --pretty -p, --pretty
: Alias for --color=always --heading -n. : Alias for --color=always --heading -n.

View File

@ -125,7 +125,8 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
.value_name("NUM").takes_value(true) .value_name("NUM").takes_value(true)
.validator(validate_number)) .validator(validate_number))
.arg(flag("column")) .arg(flag("column"))
.arg(flag("context-separator").value_name("ARG").takes_value(true)) .arg(flag("context-separator")
.value_name("SEPARATOR").takes_value(true))
.arg(flag("debug")) .arg(flag("debug"))
.arg(flag("file").short("f") .arg(flag("file").short("f")
.value_name("FILE").takes_value(true) .value_name("FILE").takes_value(true)
@ -154,6 +155,7 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
.arg(flag("no-ignore-parent")) .arg(flag("no-ignore-parent"))
.arg(flag("no-ignore-vcs")) .arg(flag("no-ignore-vcs"))
.arg(flag("null")) .arg(flag("null"))
.arg(flag("path-separator").value_name("SEPARATOR").takes_value(true))
.arg(flag("pretty").short("p")) .arg(flag("pretty").short("p"))
.arg(flag("replace").short("r").value_name("ARG").takes_value(true)) .arg(flag("replace").short("r").value_name("ARG").takes_value(true))
.arg(flag("case-sensitive").short("s")) .arg(flag("case-sensitive").short("s"))
@ -410,6 +412,13 @@ lazy_static! {
printing a list of matching files such as with --count, \ printing a list of matching files such as with --count, \
--files-with-matches and --files. This option is useful for use \ --files-with-matches and --files. This option is useful for use \
with xargs."); with xargs.");
doc!(h, "path-separator",
"Path separator to use when printing file paths.",
"The path separator to use when printing file paths. This \
defaults to your platform's path separator, which is / on Unix \
and \\ on Windows. This flag is intended for overriding the \
default when the environment demands it (e.g., cygwin). A path \
separator is limited to a single byte.");
doc!(h, "pretty", doc!(h, "pretty",
"Alias for --color always --heading -n."); "Alias for --color always --heading -n.");
doc!(h, "replace", doc!(h, "replace",

View File

@ -62,6 +62,7 @@ pub struct Args {
no_ignore_vcs: bool, no_ignore_vcs: bool,
no_messages: bool, no_messages: bool,
null: bool, null: bool,
path_separator: Option<u8>,
quiet: bool, quiet: bool,
quiet_matched: QuietMatched, quiet_matched: QuietMatched,
replace: Option<Vec<u8>>, replace: Option<Vec<u8>>,
@ -151,6 +152,7 @@ impl Args {
.heading(self.heading) .heading(self.heading)
.line_per_match(self.line_per_match) .line_per_match(self.line_per_match)
.null(self.null) .null(self.null)
.path_separator(self.path_separator)
.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());
@ -347,6 +349,7 @@ impl<'a> ArgMatches<'a> {
no_ignore_vcs: self.no_ignore_vcs(), no_ignore_vcs: self.no_ignore_vcs(),
no_messages: self.is_present("no-messages"), no_messages: self.is_present("no-messages"),
null: self.is_present("null"), null: self.is_present("null"),
path_separator: try!(self.path_separator()),
quiet: quiet, quiet: quiet,
quiet_matched: QuietMatched::new(quiet), quiet_matched: QuietMatched::new(quiet),
replace: self.replace(), replace: self.replace(),
@ -616,6 +619,25 @@ impl<'a> ArgMatches<'a> {
} }
} }
/// Returns the unescaped path separator in UTF-8 bytes.
fn path_separator(&self) -> Result<Option<u8>> {
match self.value_of_lossy("path-separator") {
None => Ok(None),
Some(sep) => {
let sep = unescape(&sep);
if sep.is_empty() {
Ok(None)
} else if sep.len() > 1 {
Err(From::from(format!(
"A path separator must be exactly one byte, but \
the given separator is {} bytes.", sep.len())))
} else {
Ok(Some(sep[0]))
}
}
}
}
/// Returns the before and after contexts from the command line. /// Returns the before and after contexts from the command line.
/// ///
/// If a context setting was absent, then `0` is returned. /// If a context setting was absent, then `0` is returned.

View File

@ -44,6 +44,8 @@ pub struct Printer<W> {
with_filename: bool, with_filename: bool,
/// The color specifications. /// The color specifications.
colors: ColorSpecs, colors: ColorSpecs,
/// The separator to use for file paths. If empty, this is ignored.
path_separator: Option<u8>,
} }
impl<W: WriteColor> Printer<W> { impl<W: WriteColor> Printer<W> {
@ -62,6 +64,7 @@ impl<W: WriteColor> Printer<W> {
replace: None, replace: None,
with_filename: false, with_filename: false,
colors: ColorSpecs::default(), colors: ColorSpecs::default(),
path_separator: None,
} }
} }
@ -118,6 +121,13 @@ impl<W: WriteColor> Printer<W> {
self self
} }
/// A separator to use when printing file paths. When empty, use the
/// default separator for the current platform. (/ on Unix, \ on Windows.)
pub fn path_separator(mut self, sep: Option<u8>) -> Printer<W> {
self.path_separator = sep;
self
}
/// Replace every match in each matching line with the replacement string /// Replace every match in each matching line with the replacement string
/// given. /// given.
/// ///
@ -342,12 +352,29 @@ impl<W: WriteColor> Printer<W> {
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
let path = path.as_ref().as_os_str().as_bytes(); let path = path.as_ref().as_os_str().as_bytes();
self.write(path); match self.path_separator {
None => self.write(path),
Some(sep) => self.write_path_with_sep(path, sep),
}
} }
#[cfg(not(unix))] #[cfg(not(unix))]
fn write_path<P: AsRef<Path>>(&mut self, path: P) { fn write_path<P: AsRef<Path>>(&mut self, path: P) {
self.write(path.as_ref().to_string_lossy().as_bytes()); let path = path.as_ref().to_string_lossy();
match self.path_separator {
None => self.write(path.as_bytes()),
Some(sep) => self.write_path_with_sep(path.as_bytes(), sep),
}
}
fn write_path_with_sep(&mut self, path: &[u8], sep: u8) {
let mut path = path.to_vec();
for b in &mut path {
if *b == b'/' || (cfg!(windows) && *b == b'\\') {
*b = sep;
}
}
self.write(&path);
} }
fn write(&mut self, buf: &[u8]) { fn write(&mut self, buf: &[u8]) {

View File

@ -1226,6 +1226,16 @@ clean!(feature_263_sort_files, "test", ".", |wd: WorkDir, mut cmd: Command| {
assert_eq!(lines, "abc:test\nbar:test\nfoo:test\nzoo:test\n"); assert_eq!(lines, "abc:test\nbar:test\nfoo:test\nzoo:test\n");
}); });
// See: https://github.com/BurntSushi/ripgrep/issues/275
clean!(feature_275_pathsep, "test", ".", |wd: WorkDir, mut cmd: Command| {
wd.create_dir("foo");
wd.create("foo/bar", "test");
cmd.arg("--path-separator").arg("Z");
let lines: String = wd.stdout(&mut cmd);
assert_eq!(lines, "fooZbar:test\n");
});
#[test] #[test]
fn binary_nosearch() { fn binary_nosearch() {
let wd = WorkDir::new("binary_nosearch"); let wd = WorkDir::new("binary_nosearch");