From 7602fa960c6ca54df50c8f27bd545affd35cfdd3 Mon Sep 17 00:00:00 2001 From: Nathan Moreau Date: Thu, 11 Apr 2019 00:30:56 +0200 Subject: [PATCH 1/8] Add path-separator option. Example usage: `fd.exe --path-separator /` on windows. --- src/app.rs | 10 ++++++++++ src/internal/opts.rs | 3 +++ src/main.rs | 15 +++++++++++++++ src/output.rs | 16 +++++++++++++++- 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 950ec1ef6..38b81543b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -207,6 +207,12 @@ pub fn build_app() -> App<'static, 'static> { .hidden_short_help(true), ) .arg(arg("pattern")) + .arg( + arg("path-separator") + .takes_value(true) + .long("path-separator") + .number_of_values(1), + ) .arg(arg("path").multiple(true)) .arg( arg("search-path") @@ -249,6 +255,10 @@ fn usage() -> HashMap<&'static str, Help> { doc!(h, "absolute-path" , "Show absolute instead of relative paths" , "Shows the full path starting from the root as opposed to relative paths."); + doc!(h, "path-separator" + , "Set the path separator to use when printing file paths." + , "Set the path separator to use when printing file paths. \ + A path separator is limited to a single byte."); doc!(h, "follow" , "Follow symbolic links" , "By default, fd does not descend into symlinked directories. Using this flag, symbolic \ diff --git a/src/internal/opts.rs b/src/internal/opts.rs index 4fd3dac19..6c509d32b 100644 --- a/src/internal/opts.rs +++ b/src/internal/opts.rs @@ -75,4 +75,7 @@ pub struct FdOptions { /// Whether or not to display filesystem errors pub show_filesystem_errors: bool, + + /// The separator used to print file paths. + pub path_separator: Option, } diff --git a/src/main.rs b/src/main.rs index d24eb9605..8217ba4aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,6 +113,20 @@ fn main() { _ => atty::is(Stream::Stdout), }; + let path_separator: Option = match matches.value_of("path-separator") { + Some(sep_str) => { + let sep = sep_str.as_bytes(); + if sep.len() != 1 { + print_error_and_exit!( + "'{}' is not a valid path separator. See 'fd --help'.", + sep_str + ); + } + Some(sep[0]) + } + None => None, + }; + #[cfg(windows)] let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok(); @@ -243,6 +257,7 @@ fn main() { size_constraints: size_limits, time_constraints, show_filesystem_errors: matches.is_present("show-errors"), + path_separator, }; match RegexBuilder::new(&pattern_regex) diff --git a/src/output.rs b/src/output.rs index a5d0c1e49..414a29612 100644 --- a/src/output.rs +++ b/src/output.rs @@ -67,7 +67,21 @@ fn print_entry_colorized( .map(Style::to_ansi_term_style) .unwrap_or(default_style); - write!(stdout, "{}", style.paint(component.to_string_lossy()))?; + let path_string = component.to_string_lossy(); + + match config.path_separator { + None => write!(stdout, "{}", style.paint(path_string))?, + Some(sep) => { + let mut path_bytes = path_string.as_bytes().to_vec(); + for b in &mut path_bytes { + if *b == b'/' || (cfg!(windows) && *b == b'\\') { + *b = sep; + } + } + let path_string = String::from_utf8_lossy(&path_bytes); + write!(stdout, "{}", style.paint(path_string))? + } + } if wants_to_quit.load(Ordering::Relaxed) { writeln!(stdout)?; From f4fc52d38eb53974c21303d86e5e0911742d692f Mon Sep 17 00:00:00 2001 From: Nathan Moreau Date: Fri, 12 Apr 2019 00:46:07 +0200 Subject: [PATCH 2/8] Amend clap configuration for path-separator. --- src/app.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 38b81543b..cd7066eea 100644 --- a/src/app.rs +++ b/src/app.rs @@ -210,8 +210,9 @@ pub fn build_app() -> App<'static, 'static> { .arg( arg("path-separator") .takes_value(true) + .value_name("separator") .long("path-separator") - .number_of_values(1), + .hidden_short_help(true), ) .arg(arg("path").multiple(true)) .arg( From 3c2c00dd0d16cd66cb700f8889421be6cfddd321 Mon Sep 17 00:00:00 2001 From: Nathan Moreau Date: Fri, 12 Apr 2019 01:33:51 +0200 Subject: [PATCH 3/8] Handle any string replacement. --- src/app.rs | 3 +-- src/internal/opts.rs | 2 +- src/main.rs | 15 ++------------- src/output.rs | 10 ++-------- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/app.rs b/src/app.rs index cd7066eea..9eb259296 100644 --- a/src/app.rs +++ b/src/app.rs @@ -258,8 +258,7 @@ fn usage() -> HashMap<&'static str, Help> { , "Shows the full path starting from the root as opposed to relative paths."); doc!(h, "path-separator" , "Set the path separator to use when printing file paths." - , "Set the path separator to use when printing file paths. \ - A path separator is limited to a single byte."); + , "Set the path separator to use when printing file paths."); doc!(h, "follow" , "Follow symbolic links" , "By default, fd does not descend into symlinked directories. Using this flag, symbolic \ diff --git a/src/internal/opts.rs b/src/internal/opts.rs index 6c509d32b..9775c3bbb 100644 --- a/src/internal/opts.rs +++ b/src/internal/opts.rs @@ -77,5 +77,5 @@ pub struct FdOptions { pub show_filesystem_errors: bool, /// The separator used to print file paths. - pub path_separator: Option, + pub path_separator: Option, } diff --git a/src/main.rs b/src/main.rs index 8217ba4aa..0836b7309 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,19 +113,8 @@ fn main() { _ => atty::is(Stream::Stdout), }; - let path_separator: Option = match matches.value_of("path-separator") { - Some(sep_str) => { - let sep = sep_str.as_bytes(); - if sep.len() != 1 { - print_error_and_exit!( - "'{}' is not a valid path separator. See 'fd --help'.", - sep_str - ); - } - Some(sep[0]) - } - None => None, - }; + let path_separator: Option = + matches.value_of("path-separator").map(|str| str.to_owned()); #[cfg(windows)] let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok(); diff --git a/src/output.rs b/src/output.rs index 414a29612..fdb1ceb58 100644 --- a/src/output.rs +++ b/src/output.rs @@ -69,16 +69,10 @@ fn print_entry_colorized( let path_string = component.to_string_lossy(); - match config.path_separator { + match &config.path_separator { None => write!(stdout, "{}", style.paint(path_string))?, Some(sep) => { - let mut path_bytes = path_string.as_bytes().to_vec(); - for b in &mut path_bytes { - if *b == b'/' || (cfg!(windows) && *b == b'\\') { - *b = sep; - } - } - let path_string = String::from_utf8_lossy(&path_bytes); + let path_string = path_string.replace(std::path::MAIN_SEPARATOR, &sep); write!(stdout, "{}", style.paint(path_string))? } } From d452e82cfc539086140b72ac5d1ea33dca3573d6 Mon Sep 17 00:00:00 2001 From: Nathan Moreau Date: Fri, 12 Apr 2019 02:11:55 +0200 Subject: [PATCH 4/8] Factor into a helper function; adapt to print_entry_uncolorized. --- src/output.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/output.rs b/src/output.rs index fdb1ceb58..18064e96d 100644 --- a/src/output.rs +++ b/src/output.rs @@ -10,6 +10,7 @@ use crate::exit_codes::ExitCode; use crate::internal::opts::FdOptions; use lscolors::{LsColors, Style}; +use std::borrow::Cow; use std::io::{self, StdoutLock, Write}; use std::path::{Component, Path, PathBuf}; use std::process; @@ -52,6 +53,13 @@ pub fn print_entry( } } +fn replace_path_separator<'a>(config: &FdOptions, str: Cow<'a, str>) -> Cow<'a, str> { + match &config.path_separator { + None => str, + Some(sep) => Cow::from(str.replace(std::path::MAIN_SEPARATOR, &sep)), + } +} + fn print_entry_colorized( stdout: &mut StdoutLock, path: &Path, @@ -68,14 +76,8 @@ fn print_entry_colorized( .unwrap_or(default_style); let path_string = component.to_string_lossy(); - - match &config.path_separator { - None => write!(stdout, "{}", style.paint(path_string))?, - Some(sep) => { - let path_string = path_string.replace(std::path::MAIN_SEPARATOR, &sep); - write!(stdout, "{}", style.paint(path_string))? - } - } + let path_string = replace_path_separator(&config, path_string); + write!(stdout, "{}", style.paint(path_string))?; if wants_to_quit.load(Ordering::Relaxed) { writeln!(stdout)?; @@ -98,5 +100,6 @@ fn print_entry_uncolorized( let separator = if config.null_separator { "\0" } else { "\n" }; let path_str = path.to_string_lossy(); + let path_str = replace_path_separator(&config, path_str); write!(stdout, "{}{}", path_str, separator) } From c504124525de2da2d27bc0b563b9ca80e6dc19b5 Mon Sep 17 00:00:00 2001 From: Nathan Moreau Date: Fri, 12 Apr 2019 02:16:02 +0200 Subject: [PATCH 5/8] Remove type annotation. --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0836b7309..79824b934 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,8 +113,7 @@ fn main() { _ => atty::is(Stream::Stdout), }; - let path_separator: Option = - matches.value_of("path-separator").map(|str| str.to_owned()); + let path_separator = matches.value_of("path-separator").map(|str| str.to_owned()); #[cfg(windows)] let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok(); From 117c95da6ab5061914875de20a9ddb5f3cf457d4 Mon Sep 17 00:00:00 2001 From: Nathan Moreau Date: Sun, 14 Apr 2019 17:03:18 +0200 Subject: [PATCH 6/8] Add a non regression test. --- tests/tests.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index d33aa07b9..299632b16 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1231,3 +1231,17 @@ fn test_modified_asolute() { "30dec2017", ); } + +#[test] +fn test_custom_path_separator() { + let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); + + te.assert_output( + &["foo", "one", "--path-separator", "="], + "one=b.foo + one=two=c.foo + one=two=C.Foo2 + one=two=three=d.foo + one=two=three=directory_foo", + ); +} From 6b7055e75d8b06bb8e0925918bf7bbb4eda5b4b1 Mon Sep 17 00:00:00 2001 From: sharkdp Date: Sun, 15 Sep 2019 10:36:10 +0200 Subject: [PATCH 7/8] Extend help text --- src/app.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 9eb259296..65678f8e3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -258,7 +258,8 @@ fn usage() -> HashMap<&'static str, Help> { , "Shows the full path starting from the root as opposed to relative paths."); doc!(h, "path-separator" , "Set the path separator to use when printing file paths." - , "Set the path separator to use when printing file paths."); + , "Set the path separator to use when printing file paths. The default is the OS-specific \ + separator ('/' on Unix, '\\' on Windows)."); doc!(h, "follow" , "Follow symbolic links" , "By default, fd does not descend into symlinked directories. Using this flag, symbolic \ From 293b0b71921d128e646e129cfa09558f44e1274d Mon Sep 17 00:00:00 2001 From: sharkdp Date: Sun, 15 Sep 2019 10:36:40 +0200 Subject: [PATCH 8/8] Use existing Cow pointer --- src/output.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/output.rs b/src/output.rs index 18064e96d..4cc4e5891 100644 --- a/src/output.rs +++ b/src/output.rs @@ -53,10 +53,12 @@ pub fn print_entry( } } -fn replace_path_separator<'a>(config: &FdOptions, str: Cow<'a, str>) -> Cow<'a, str> { +fn replace_path_separator<'a>(config: &FdOptions, path: &mut Cow<'a, str>) { match &config.path_separator { - None => str, - Some(sep) => Cow::from(str.replace(std::path::MAIN_SEPARATOR, &sep)), + None => {} + Some(sep) => { + *path.to_mut() = path.replace(std::path::MAIN_SEPARATOR, &sep); + } } } @@ -75,8 +77,8 @@ fn print_entry_colorized( .map(Style::to_ansi_term_style) .unwrap_or(default_style); - let path_string = component.to_string_lossy(); - let path_string = replace_path_separator(&config, path_string); + let mut path_string = component.to_string_lossy(); + replace_path_separator(&config, &mut path_string); write!(stdout, "{}", style.paint(path_string))?; if wants_to_quit.load(Ordering::Relaxed) { @@ -99,7 +101,7 @@ fn print_entry_uncolorized( ) -> io::Result<()> { let separator = if config.null_separator { "\0" } else { "\n" }; - let path_str = path.to_string_lossy(); - let path_str = replace_path_separator(&config, path_str); + let mut path_str = path.to_string_lossy(); + replace_path_separator(&config, &mut path_str); write!(stdout, "{}{}", path_str, separator) }