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:
commit
8618771f2e
4 changed files with 191 additions and 46 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue