From edf4fee48f7fbaf8c9e10637f8214fe85f690b4f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 17 Jun 2022 12:59:02 +0200 Subject: [PATCH] unexpand: implement "tabs" shortcuts --- src/uu/unexpand/src/unexpand.rs | 57 ++++++++++++++++++++++++++++++--- tests/by-util/test_unexpand.rs | 35 ++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 31856bc61..477d2d917 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -7,7 +7,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (ToDO) nums aflag uflag scol prevtab amode ctype cwidth nbytes lastcol pctype +// spell-checker:ignore (ToDO) nums aflag uflag scol prevtab amode ctype cwidth nbytes lastcol pctype Preprocess #[macro_use] extern crate uucore; @@ -97,9 +97,9 @@ struct Options { impl Options { fn new(matches: &clap::ArgMatches) -> Result { - let tabstops = match matches.value_of(options::TABS) { + let tabstops = match matches.values_of(options::TABS) { None => vec![DEFAULT_TABSTOP], - Some(s) => tabstops_parse(s)?, + Some(s) => tabstops_parse(&s.collect::>().join(","))?, }; let aflag = (matches.is_present(options::ALL) || matches.is_present(options::TABS)) @@ -120,13 +120,49 @@ impl Options { } } +/// Decide whether the character is either a digit or a comma. +fn is_digit_or_comma(c: char) -> bool { + c.is_ascii_digit() || c == ',' +} + +/// Preprocess command line arguments and expand shortcuts. For example, "-7" is expanded to +/// "--tabs=7 --first-only" and "-1,3" to "--tabs=1 --tabs=3 --first-only". However, if "-a" or +/// "--all" is provided, "--first-only" is omitted. +fn expand_shortcuts(args: &[String]) -> Vec { + let mut processed_args = Vec::with_capacity(args.len()); + let mut is_all_arg_provided = false; + let mut has_shortcuts = false; + + for arg in args { + if arg.starts_with('-') && arg[1..].chars().all(is_digit_or_comma) { + arg[1..] + .split(',') + .filter(|s| !s.is_empty()) + .for_each(|s| processed_args.push(format!("--tabs={}", s))); + has_shortcuts = true; + } else { + processed_args.push(arg.to_string()); + + if arg == "--all" || arg == "-a" { + is_all_arg_provided = true; + } + } + } + + if has_shortcuts && !is_all_arg_provided { + processed_args.push("--first-only".into()); + } + + processed_args +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = uu_app().get_matches_from(args); + let matches = uu_app().get_matches_from(expand_shortcuts(&args)); unexpand(&Options::new(&matches)?).map_err_context(String::new) } @@ -163,6 +199,7 @@ pub fn uu_app<'a>() -> Command<'a> { .long(options::TABS) .help("use comma separated LIST of tab positions or have tabs N characters apart instead of 8 (enables -a)") .takes_value(true) + .multiple_occurrences(true) .value_name("N, LIST") ) .arg( @@ -379,3 +416,15 @@ fn unexpand(options: &Options) -> std::io::Result<()> { } output.flush() } + +#[cfg(test)] +mod tests { + use crate::is_digit_or_comma; + + #[test] + fn test_is_digit_or_comma() { + assert!(is_digit_or_comma('1')); + assert!(is_digit_or_comma(',')); + assert!(!is_digit_or_comma('a')); + } +} diff --git a/tests/by-util/test_unexpand.rs b/tests/by-util/test_unexpand.rs index ccb217393..e71e76d55 100644 --- a/tests/by-util/test_unexpand.rs +++ b/tests/by-util/test_unexpand.rs @@ -156,6 +156,41 @@ fn unexpand_read_from_two_file() { .success(); } +#[test] +fn test_tabs_shortcut() { + new_ucmd!() + .arg("-3") + .pipe_in(" a b") + .run() + .stdout_is("\ta b"); +} + +#[test] +fn test_tabs_shortcut_combined_with_all_arg() { + fn run_cmd(all_arg: &str) { + new_ucmd!() + .args(&[all_arg, "-3"]) + .pipe_in("a b c") + .run() + .stdout_is("a\tb\tc"); + } + + let all_args = vec!["-a", "--all"]; + + for arg in all_args { + run_cmd(arg); + } +} + +#[test] +fn test_comma_separated_tabs_shortcut() { + new_ucmd!() + .args(&["-a", "-3,9"]) + .pipe_in("a b c") + .run() + .stdout_is("a\tb\tc"); +} + #[test] fn test_tabs_cannot_be_zero() { new_ucmd!()