From 4fbe2b2b5ed22da9fe9cbe79e614c43e226950c1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 25 Jan 2022 20:45:10 -0500 Subject: [PATCH] seq: implement -f FORMAT option Add support for the `-f FORMAT` option to `seq`. This option instructs the program to render each value in the generated sequence using a given `printf`-style floating point format. For example, $ seq -f %.2f 0.0 0.1 0.5 0.00 0.10 0.20 0.30 0.40 0.50 Fixes issue #2616. --- src/uu/seq/Cargo.toml | 2 +- src/uu/seq/src/seq.rs | 68 +++++++++++++++++++++++++++++++++------ tests/by-util/test_seq.rs | 8 +++++ 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index ba446a3ec..669ba1c53 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -20,7 +20,7 @@ bigdecimal = "0.3" clap = { version = "3.0", features = ["wrap_help", "cargo"] } num-bigint = "0.4.0" num-traits = "0.2.14" -uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["memo"] } uucore_procs = { version=">=0.0.8", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 0e621197e..9653a2b82 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -11,6 +11,7 @@ use num_traits::Zero; use uucore::error::FromIo; use uucore::error::UResult; +use uucore::memo::Memo; mod error; mod extendedbigdecimal; @@ -27,6 +28,7 @@ static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT. static OPT_SEPARATOR: &str = "separator"; static OPT_TERMINATOR: &str = "terminator"; static OPT_WIDTHS: &str = "widths"; +static OPT_FORMAT: &str = "format"; static ARG_NUMBERS: &str = "numbers"; @@ -39,10 +41,11 @@ fn usage() -> String { ) } #[derive(Clone)] -struct SeqOptions { +struct SeqOptions<'a> { separator: String, terminator: String, widths: bool, + format: Option<&'a str>, } /// A range of integers. @@ -66,6 +69,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { separator: matches.value_of(OPT_SEPARATOR).unwrap_or("\n").to_string(), terminator: matches.value_of(OPT_TERMINATOR).unwrap_or("\n").to_string(), widths: matches.is_present(OPT_WIDTHS), + format: matches.value_of(OPT_FORMAT), }; let first = if numbers.len() > 1 { @@ -115,6 +119,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { options.terminator, options.widths, padding, + options.format, ) } (first, increment, last) => print_seq( @@ -128,6 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { options.terminator, options.widths, padding, + options.format, ), }; match result { @@ -165,6 +171,14 @@ pub fn uu_app<'a>() -> App<'a> { .long("widths") .help("Equalize widths of all numbers by padding with zeros"), ) + .arg( + Arg::new(OPT_FORMAT) + .short('f') + .long(OPT_FORMAT) + .help("use printf style floating-point FORMAT") + .takes_value(true) + .number_of_values(1), + ) .arg( Arg::new(ARG_NUMBERS) .multiple_occurrences(true) @@ -254,6 +268,7 @@ fn print_seq( terminator: String, pad: bool, padding: usize, + format: Option<&str>, ) -> std::io::Result<()> { let stdout = stdout(); let mut stdout = stdout.lock(); @@ -265,13 +280,34 @@ fn print_seq( if !is_first_iteration { write!(stdout, "{}", separator)?; } - write_value_float( - &mut stdout, - &value, - padding, - largest_dec, - is_first_iteration, - )?; + // If there was an argument `-f FORMAT`, then use that format + // template instead of the default formatting strategy. + // + // The `Memo::run_all()` function takes in the template and + // the current value and writes the result to `stdout`. + // + // TODO The `run_all()` method takes a string as its second + // parameter but we have an `ExtendedBigDecimal`. In order to + // satisfy the signature of the function, we convert the + // `ExtendedBigDecimal` into a string. The `Memo::run_all()` + // logic will subsequently parse that string into something + // similar to an `ExtendedBigDecimal` again before rendering + // it as a string and ultimately writing to `stdout`. We + // shouldn't have to do so much converting back and forth via + // strings. + match format { + Some(f) => { + let s = format!("{}", value); + Memo::run_all(f, &[s]); + } + None => write_value_float( + &mut stdout, + &value, + padding, + largest_dec, + is_first_iteration, + )?, + } // TODO Implement augmenting addition. value = value + increment.clone(); is_first_iteration = false; @@ -303,6 +339,7 @@ fn print_seq_integers( terminator: String, pad: bool, padding: usize, + format: Option<&str>, ) -> std::io::Result<()> { let stdout = stdout(); let mut stdout = stdout.lock(); @@ -313,7 +350,20 @@ fn print_seq_integers( if !is_first_iteration { write!(stdout, "{}", separator)?; } - write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?; + // If there was an argument `-f FORMAT`, then use that format + // template instead of the default formatting strategy. + // + // The `Memo::run_all()` function takes in the template and + // the current value and writes the result to `stdout`. + // + // TODO See similar comment about formatting in `print_seq()`. + match format { + Some(f) => { + let s = format!("{}", value); + Memo::run_all(f, &[s]); + } + None => write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?, + } // TODO Implement augmenting addition. value = value + increment.clone(); is_first_iteration = false; diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index e6f4bce0b..2c805e3d5 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -693,3 +693,11 @@ fn test_parse_error_hex() { .fails() .usage_error("invalid hexadecimal argument: '0xlmnop'"); } + +#[test] +fn test_format_option() { + new_ucmd!() + .args(&["-f", "%.2f", "0.0", "0.1", "0.5"]) + .succeeds() + .stdout_only("0.00\n0.10\n0.20\n0.30\n0.40\n0.50\n"); +}