1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #2322 from miDeb/seq/improvements

seq: improve compatibility
This commit is contained in:
Sylvestre Ledru 2021-05-31 23:09:13 +02:00 committed by GitHub
commit 8618771f2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 191 additions and 46 deletions

2
Cargo.lock generated
View file

@ -2421,6 +2421,8 @@ name = "uu_seq"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"num-bigint",
"num-traits",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]

View file

@ -16,6 +16,8 @@ path = "src/seq.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
num-bigint = "0.4.0"
num-traits = "0.2.14"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -7,8 +7,13 @@
extern crate uucore; extern crate uucore;
use clap::{App, AppSettings, Arg}; use clap::{App, AppSettings, Arg};
use num_bigint::BigInt;
use num_traits::One;
use num_traits::Zero;
use num_traits::{Num, ToPrimitive};
use std::cmp; use std::cmp;
use std::io::{stdout, Write}; use std::io::{stdout, Write};
use std::str::FromStr;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT.";
@ -29,25 +34,51 @@ fn get_usage() -> String {
#[derive(Clone)] #[derive(Clone)]
struct SeqOptions { struct SeqOptions {
separator: String, separator: String,
terminator: Option<String>, terminator: String,
widths: bool, widths: bool,
} }
fn parse_float(mut s: &str) -> Result<f64, String> { enum Number {
BigInt(BigInt),
F64(f64),
}
impl Number {
fn is_zero(&self) -> bool {
match self {
Number::BigInt(n) => n.is_zero(),
Number::F64(n) => n.is_zero(),
}
}
fn into_f64(self) -> f64 {
match self {
// BigInt::to_f64() can not return None.
Number::BigInt(n) => n.to_f64().unwrap(),
Number::F64(n) => n,
}
}
}
impl FromStr for Number {
type Err = String;
/// Tries to parse this string as a BigInt, or if that fails as an f64.
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
if s.starts_with('+') { if s.starts_with('+') {
s = &s[1..]; s = &s[1..];
} }
match s.parse() {
Ok(n) => Ok(n), match s.parse::<BigInt>() {
Ok(n) => Ok(Number::BigInt(n)),
Err(_) => match s.parse::<f64>() {
Ok(n) => Ok(Number::F64(n)),
Err(e) => Err(format!( Err(e) => Err(format!(
"seq: invalid floating point argument `{}`: {}", "seq: invalid floating point argument `{}`: {}",
s, e s, e
)), )),
},
}
} }
}
fn escape_sequences(s: &str) -> String {
s.replace("\\n", "\n").replace("\\t", "\t")
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -69,7 +100,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(OPT_TERMINATOR) Arg::with_name(OPT_TERMINATOR)
.short("t") .short("t")
.long("terminator") .long("terminator")
.help("Terminator character (defaults to separator)") .help("Terminator character (defaults to \\n)")
.takes_value(true) .takes_value(true)
.number_of_values(1), .number_of_values(1),
) )
@ -84,20 +115,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.multiple(true) .multiple(true)
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true) .allow_hyphen_values(true)
.max_values(3), .max_values(3)
.required(true),
) )
.get_matches_from(args); .get_matches_from(args);
let numbers = matches.values_of(ARG_NUMBERS).unwrap().collect::<Vec<_>>(); let numbers = matches.values_of(ARG_NUMBERS).unwrap().collect::<Vec<_>>();
let mut options = SeqOptions { let options = SeqOptions {
separator: "\n".to_owned(), separator: matches.value_of(OPT_SEPARATOR).unwrap_or("\n").to_string(),
terminator: None, terminator: matches.value_of(OPT_TERMINATOR).unwrap_or("\n").to_string(),
widths: false, widths: matches.is_present(OPT_WIDTHS),
}; };
options.separator = matches.value_of(OPT_SEPARATOR).unwrap_or("\n").to_string();
options.terminator = matches.value_of(OPT_TERMINATOR).map(String::from);
options.widths = matches.is_present(OPT_WIDTHS);
let mut largest_dec = 0; let mut largest_dec = 0;
let mut padding = 0; let mut padding = 0;
@ -107,7 +136,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let dec = slice.find('.').unwrap_or(len); let dec = slice.find('.').unwrap_or(len);
largest_dec = len - dec; largest_dec = len - dec;
padding = dec; padding = dec;
match parse_float(slice) { match slice.parse() {
Ok(n) => n, Ok(n) => n,
Err(s) => { Err(s) => {
show_error!("{}", s); show_error!("{}", s);
@ -115,7 +144,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
} else { } else {
1.0 Number::BigInt(BigInt::one())
}; };
let increment = if numbers.len() > 2 { let increment = if numbers.len() > 2 {
let slice = numbers[1]; let slice = numbers[1];
@ -123,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let dec = slice.find('.').unwrap_or(len); let dec = slice.find('.').unwrap_or(len);
largest_dec = cmp::max(largest_dec, len - dec); largest_dec = cmp::max(largest_dec, len - dec);
padding = cmp::max(padding, dec); padding = cmp::max(padding, dec);
match parse_float(slice) { match slice.parse() {
Ok(n) => n, Ok(n) => n,
Err(s) => { Err(s) => {
show_error!("{}", s); show_error!("{}", s);
@ -131,16 +160,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
} else { } else {
1.0 Number::BigInt(BigInt::one())
}; };
if increment == 0.0 { if increment.is_zero() {
show_error!("increment value: '{}'", numbers[1]); show_error!("increment value: '{}'", numbers[1]);
return 1; return 1;
} }
let last = { let last = {
let slice = numbers[numbers.len() - 1]; let slice = numbers[numbers.len() - 1];
padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len())); padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len()));
match parse_float(slice) { match slice.parse::<Number>() {
Ok(n) => n, Ok(n) => n,
Err(s) => { Err(s) => {
show_error!("{}", s); show_error!("{}", s);
@ -151,33 +180,42 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if largest_dec > 0 { if largest_dec > 0 {
largest_dec -= 1; largest_dec -= 1;
} }
let separator = escape_sequences(&options.separator[..]);
let terminator = match options.terminator { match (first, last, increment) {
Some(term) => escape_sequences(&term[..]), (Number::BigInt(first), Number::BigInt(last), Number::BigInt(increment)) => {
None => separator.clone(), print_seq_integers(
};
print_seq(
first, first,
increment, increment,
last, last,
largest_dec, options.separator,
separator, options.terminator,
terminator,
options.widths, options.widths,
padding, padding,
); )
}
(first, last, increment) => print_seq(
first.into_f64(),
increment.into_f64(),
last.into_f64(),
largest_dec,
options.separator,
options.terminator,
options.widths,
padding,
),
}
0 0
} }
fn done_printing(next: f64, increment: f64, last: f64) -> bool { fn done_printing<T: Num + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
if increment >= 0f64 { if increment >= &T::zero() {
next > last next > last
} else { } else {
next < last next < last
} }
} }
/// Floating point based code path
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn print_seq( fn print_seq(
first: f64, first: f64,
@ -191,7 +229,7 @@ fn print_seq(
) { ) {
let mut i = 0isize; let mut i = 0isize;
let mut value = first + i as f64 * increment; let mut value = first + i as f64 * increment;
while !done_printing(value, increment, last) { while !done_printing(&value, &increment, &last) {
let istr = format!("{:.*}", largest_dec, value); let istr = format!("{:.*}", largest_dec, value);
let ilen = istr.len(); let ilen = istr.len();
let before_dec = istr.find('.').unwrap_or(ilen); let before_dec = istr.find('.').unwrap_or(ilen);
@ -203,7 +241,7 @@ fn print_seq(
print!("{}", istr); print!("{}", istr);
i += 1; i += 1;
value = first + i as f64 * increment; value = first + i as f64 * increment;
if !done_printing(value, increment, last) { if !done_printing(&value, &increment, &last) {
print!("{}", separator); print!("{}", separator);
} }
} }
@ -212,3 +250,33 @@ fn print_seq(
} }
crash_if_err!(1, stdout().flush()); crash_if_err!(1, stdout().flush());
} }
/// BigInt based code path
fn print_seq_integers(
first: BigInt,
increment: BigInt,
last: BigInt,
separator: String,
terminator: String,
pad: bool,
padding: usize,
) {
let mut value = first;
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
print!("{}", separator);
}
is_first_iteration = false;
if pad {
print!("{number:>0width$}", number = value, width = padding);
} else {
print!("{}", value);
}
value += &increment;
}
if !is_first_iteration {
print!("{}", terminator);
}
}

View file

@ -1,5 +1,7 @@
use crate::common::util::*; use crate::common::util::*;
// ---- Tests for the big integer based path ----
#[test] #[test]
fn test_count_up() { fn test_count_up() {
new_ucmd!() new_ucmd!()
@ -26,6 +28,18 @@ fn test_separator_and_terminator() {
.args(&["-s", ",", "-t", "!", "2", "6"]) .args(&["-s", ",", "-t", "!", "2", "6"])
.run() .run()
.stdout_is("2,3,4,5,6!"); .stdout_is("2,3,4,5,6!");
new_ucmd!()
.args(&["-s", ",", "2", "6"])
.run()
.stdout_is("2,3,4,5,6\n");
new_ucmd!()
.args(&["-s", "\n", "2", "6"])
.run()
.stdout_is("2\n3\n4\n5\n6\n");
new_ucmd!()
.args(&["-s", "\\n", "2", "6"])
.run()
.stdout_is("2\\n3\\n4\\n5\\n6\n");
} }
#[test] #[test]
@ -45,3 +59,62 @@ fn test_seq_wrong_arg() {
fn test_zero_step() { fn test_zero_step() {
new_ucmd!().args(&["10", "0", "32"]).fails(); new_ucmd!().args(&["10", "0", "32"]).fails();
} }
#[test]
fn test_big_numbers() {
new_ucmd!()
.args(&[
"1000000000000000000000000000",
"1000000000000000000000000001",
])
.succeeds()
.stdout_only("1000000000000000000000000000\n1000000000000000000000000001\n");
}
// ---- Tests for the floating point based path ----
#[test]
fn test_count_up_floats() {
new_ucmd!()
.args(&["10.0"])
.run()
.stdout_is("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n");
}
#[test]
fn test_count_down_floats() {
new_ucmd!()
.args(&["--", "5", "-1.0", "1"])
.run()
.stdout_is("5.0\n4.0\n3.0\n2.0\n1.0\n");
new_ucmd!()
.args(&["5", "-1", "1.0"])
.run()
.stdout_is("5\n4\n3\n2\n1\n");
}
#[test]
fn test_separator_and_terminator_floats() {
new_ucmd!()
.args(&["-s", ",", "-t", "!", "2.0", "6"])
.run()
.stdout_is("2.0,3.0,4.0,5.0,6.0!");
}
#[test]
fn test_equalize_widths_floats() {
new_ucmd!()
.args(&["-w", "5", "10.0"])
.run()
.stdout_is("05\n06\n07\n08\n09\n10\n");
}
#[test]
fn test_seq_wrong_arg_floats() {
new_ucmd!().args(&["-w", "5", "10.0", "33", "32"]).fails();
}
#[test]
fn test_zero_step_floats() {
new_ucmd!().args(&["10.0", "0", "32"]).fails();
}