From 6829e7f359ba93cc5f1d06a0fd164d87a1af0e57 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Sun, 14 Mar 2021 20:39:41 +0800 Subject: [PATCH 1/3] expand: replace getopts with clap expand has one odd behavior that allows two format for tabstop From expand --help ``` -t, --tabs=N have tabs N characters apart, not 8 -t, --tabs=LIST use comma separated list of tab positions ``` This patch use one `value_name("N, LIST")` for tabstop and deal with above behavior in `parse_tabstop`. Close #1795 --- src/uu/expand/Cargo.toml | 2 +- src/uu/expand/src/expand.rs | 72 ++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index d9861a0c7..b5c0343e7 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/expand.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" unicode-width = "0.1.5" uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 353c3e6bc..52537dcc2 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -12,19 +12,25 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg, ArgMatches}; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::iter::repeat; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; -static SYNTAX: &str = "[OPTION]... [FILE]..."; -static SUMMARY: &str = "Convert tabs in each FILE to spaces, writing to standard output. +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. With no FILE, or when FILE is -, read standard input."; + static LONG_HELP: &str = ""; static DEFAULT_TABSTOP: usize = 8; +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]...", executable!()) +} + fn tabstops_parse(s: String) -> Vec { let words = s.split(','); @@ -58,14 +64,14 @@ struct Options { } impl Options { - fn new(matches: getopts::Matches) -> Options { - let tabstops = match matches.opt_str("t") { + fn new(matches: &ArgMatches) -> Options { + let tabstops = match matches.value_of("tabstops") { + Some(s) => tabstops_parse(s.to_string()), None => vec![DEFAULT_TABSTOP], - Some(s) => tabstops_parse(s), }; - let iflag = matches.opt_present("i"); - let uflag = !matches.opt_present("U"); + let iflag = matches.is_present("iflag"); + let uflag = !matches.is_present("uflag"); // avoid allocations when dumping out long sequences of spaces // by precomputing the longest string of spaces we will ever need @@ -80,10 +86,9 @@ impl Options { .unwrap(); // length of tabstops is guaranteed >= 1 let tspaces = repeat(' ').take(nspaces).collect(); - let files = if matches.free.is_empty() { - vec!["-".to_owned()] - } else { - matches.free + let files: Vec = match matches.values_of("files") { + Some(s) => s.map(|v| v.to_string()).collect(), + None => vec!["-".to_owned()], }; Options { @@ -97,31 +102,34 @@ impl Options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); - - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag("i", "initial", "do not convert tabs after non blanks") - .optopt( - "t", - "tabs", - "have tabs NUMBER characters apart, not 8", - "NUMBER", + let usage = get_usage(); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(LONG_HELP) + .arg( + Arg::with_name("iflag") + .long("initial") + .short("i") + .help("do not convert tabs after non blanks"), ) - .optopt( - "t", - "tabs", - "use comma separated list of explicit tab positions", - "LIST", + .arg( + Arg::with_name("tabstops") + .long("tabs") + .short("t") + .value_name("N, LIST") + .takes_value(true) + .help("have tabs N characters apart, not 8 or use comma separated list of explicit tab positions"), ) - .optflag( - "U", - "no-utf8", - "interpret input file as 8-bit ASCII rather than UTF-8", + .arg( + Arg::with_name("uflags").long("no-utf8").short("U").help("interpret input file as 8-bit ASCII rather than UTF-8"), + ).arg( + Arg::with_name("files").multiple(true).hidden(true).takes_value(true) ) - .parse(args); - - expand(Options::new(matches)); + .get_matches_from(args); + expand(Options::new(&matches)); 0 } From cd775ed704d897e06395134ab1922f7a58725031 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Mon, 15 Mar 2021 21:28:47 +0800 Subject: [PATCH 2/3] Expand: use mod::options --- src/uu/expand/src/expand.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 52537dcc2..67d24086c 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -23,6 +23,13 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. With no FILE, or when FILE is -, read standard input."; +pub mod options { + pub static TABS: &str = "tabs"; + pub static INITIAL: &str = "initial"; + pub static NO_UTF8: &str = "no-utf8"; + pub static FILES: &str = "FILES"; +} + static LONG_HELP: &str = ""; static DEFAULT_TABSTOP: usize = 8; @@ -65,13 +72,13 @@ struct Options { impl Options { fn new(matches: &ArgMatches) -> Options { - let tabstops = match matches.value_of("tabstops") { + let tabstops = match matches.value_of(options::TABS) { Some(s) => tabstops_parse(s.to_string()), None => vec![DEFAULT_TABSTOP], }; - let iflag = matches.is_present("iflag"); - let uflag = !matches.is_present("uflag"); + let iflag = matches.is_present(options::INITIAL); + let uflag = !matches.is_present(options::NO_UTF8); // avoid allocations when dumping out long sequences of spaces // by precomputing the longest string of spaces we will ever need @@ -86,7 +93,7 @@ impl Options { .unwrap(); // length of tabstops is guaranteed >= 1 let tspaces = repeat(' ').take(nspaces).collect(); - let files: Vec = match matches.values_of("files") { + let files: Vec = match matches.values_of(options::FILES) { Some(s) => s.map(|v| v.to_string()).collect(), None => vec!["-".to_owned()], }; @@ -109,23 +116,29 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .usage(&usage[..]) .after_help(LONG_HELP) .arg( - Arg::with_name("iflag") - .long("initial") + Arg::with_name(options::INITIAL) + .long(options::INITIAL) .short("i") .help("do not convert tabs after non blanks"), ) .arg( - Arg::with_name("tabstops") - .long("tabs") + Arg::with_name(options::TABS) + .long(options::TABS) .short("t") .value_name("N, LIST") .takes_value(true) .help("have tabs N characters apart, not 8 or use comma separated list of explicit tab positions"), ) .arg( - Arg::with_name("uflags").long("no-utf8").short("U").help("interpret input file as 8-bit ASCII rather than UTF-8"), + Arg::with_name(options::NO_UTF8) + .long(options::NO_UTF8) + .short("U") + .help("interpret input file as 8-bit ASCII rather than UTF-8"), ).arg( - Arg::with_name("files").multiple(true).hidden(true).takes_value(true) + Arg::with_name(options::FILES) + .multiple(true) + .hidden(true) + .takes_value(true) ) .get_matches_from(args); From f60808471051187eeaf03fc74b936a45d18c26c0 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Mon, 15 Mar 2021 21:29:28 +0800 Subject: [PATCH 3/3] Expand: add test for multiple files --- tests/by-util/test_expand.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/by-util/test_expand.rs b/tests/by-util/test_expand.rs index 121ccccec..801bf9d98 100644 --- a/tests/by-util/test_expand.rs +++ b/tests/by-util/test_expand.rs @@ -46,3 +46,13 @@ fn test_with_space() { assert!(result.success); assert!(result.stdout.contains(" return")); } + +#[test] +fn test_with_multiple_files() { + let (_, mut ucmd) = at_and_ucmd!(); + + let result = ucmd.arg("with-spaces.txt").arg("with-tab.txt").run(); + assert!(result.success); + assert!(result.stdout.contains(" return")); + assert!(result.stdout.contains(" ")); +}