From cda3d5a29bfb732a0ea24fc9e79c3cab80eaf30c Mon Sep 17 00:00:00 2001 From: equal-l2 Date: Sat, 6 Nov 2021 03:05:31 +0900 Subject: [PATCH 01/12] ls: add possible value for `--color=` --- src/uu/ls/src/ls.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 1b6a73b39..32707da36 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1111,6 +1111,9 @@ only ignore '.' and '..'.", .long(options::COLOR) .help("Color output based on file type.") .takes_value(true) + .possible_values(&[ + "always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none", + ]) .require_equals(true) .min_values(0), ) From 2e12316ae1cea76e6b8a1ff696b6766c44f8a904 Mon Sep 17 00:00:00 2001 From: jfinkels Date: Sat, 6 Nov 2021 10:44:42 -0400 Subject: [PATCH 02/12] seq: use BigDecimal to represent floats (#2698) * seq: use BigDecimal to represent floats Use `BigDecimal` to represent arbitrary precision floats in order to prevent numerical precision issues when iterating over a sequence of numbers. This commit makes several changes at once to accomplish this goal. First, it creates a new struct, `PreciseNumber`, that is responsible for storing not only the number itself but also the number of digits (both integer and decimal) needed to display it. This information is collected at the time of parsing the number, which lives in the new `numberparse.rs` module. Second, it uses the `BigDecimal` struct to store arbitrary precision floating point numbers instead of the previous `f64` primitive type. This protects against issues of numerical precision when repeatedly accumulating a very small increment. Third, since neither the `BigDecimal` nor `BigInt` types have a representation of infinity, minus infinity, minus zero, or NaN, we add the `ExtendedBigDecimal` and `ExtendedBigInt` enumerations which extend the basic types with these concepts. * fixup! seq: use BigDecimal to represent floats * fixup! seq: use BigDecimal to represent floats * fixup! seq: use BigDecimal to represent floats * fixup! seq: use BigDecimal to represent floats * fixup! seq: use BigDecimal to represent floats --- Cargo.lock | 12 + src/uu/seq/BENCHMARKING.md | 19 + src/uu/seq/Cargo.toml | 2 + src/uu/seq/src/digits.rs | 190 --------- src/uu/seq/src/extendedbigdecimal.rs | 290 +++++++++++++ src/uu/seq/src/extendedbigint.rs | 218 ++++++++++ src/uu/seq/src/number.rs | 119 ++++++ src/uu/seq/src/numberparse.rs | 589 +++++++++++++++++++++++++++ src/uu/seq/src/seq.rs | 378 +++++++---------- tests/by-util/test_seq.rs | 79 +++- 10 files changed, 1471 insertions(+), 425 deletions(-) create mode 100644 src/uu/seq/BENCHMARKING.md delete mode 100644 src/uu/seq/src/digits.rs create mode 100644 src/uu/seq/src/extendedbigdecimal.rs create mode 100644 src/uu/seq/src/extendedbigint.rs create mode 100644 src/uu/seq/src/number.rs create mode 100644 src/uu/seq/src/numberparse.rs diff --git a/Cargo.lock b/Cargo.lock index 4a40fd883..5bd0e776a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "binary-heap-plus" version = "0.4.1" @@ -2912,6 +2923,7 @@ dependencies = [ name = "uu_seq" version = "0.0.8" dependencies = [ + "bigdecimal", "clap", "num-bigint", "num-traits", diff --git a/src/uu/seq/BENCHMARKING.md b/src/uu/seq/BENCHMARKING.md new file mode 100644 index 000000000..4d2f82afe --- /dev/null +++ b/src/uu/seq/BENCHMARKING.md @@ -0,0 +1,19 @@ +# Benchmarking to measure performance + +To compare the performance of the `uutils` version of `seq` with the +GNU version of `seq`, you can use a benchmarking tool like +[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by +running + + sudo apt-get install hyperfine + +Next, build the `seq` binary under the release profile: + + cargo build --release -p uu_seq + +Finally, you can compare the performance of the two versions of `head` +by running, for example, + + hyperfine "seq 1000000" "target/release/seq 1000000" + +[0]: https://github.com/sharkdp/hyperfine diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index f5a23310f..5aeffd3b9 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,3 +1,4 @@ +# spell-checker:ignore bigdecimal [package] name = "uu_seq" version = "0.0.8" @@ -15,6 +16,7 @@ edition = "2018" path = "src/seq.rs" [dependencies] +bigdecimal = "0.3" clap = { version = "2.33", features = ["wrap_help"] } num-bigint = "0.4.0" num-traits = "0.2.14" diff --git a/src/uu/seq/src/digits.rs b/src/uu/seq/src/digits.rs deleted file mode 100644 index bde933978..000000000 --- a/src/uu/seq/src/digits.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! Counting number of digits needed to represent a number. -//! -//! The [`num_integral_digits`] and [`num_fractional_digits`] functions -//! count the number of digits needed to represent a number in decimal -//! notation (like "123.456"). -use std::convert::TryInto; -use std::num::ParseIntError; - -use uucore::display::Quotable; - -/// The number of digits after the decimal point in a given number. -/// -/// The input `s` is a string representing a number, either an integer -/// or a floating point number in either decimal notation or scientific -/// notation. This function returns the number of digits after the -/// decimal point needed to print the number in decimal notation. -/// -/// # Examples -/// -/// ```rust,ignore -/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3); -/// ``` -pub fn num_fractional_digits(s: &str) -> Result { - match (s.find('.'), s.find('e')) { - // For example, "123456". - (None, None) => Ok(0), - - // For example, "123e456". - (None, Some(j)) => { - let exponent: i64 = s[j + 1..].parse()?; - if exponent < 0 { - Ok(-exponent as usize) - } else { - Ok(0) - } - } - - // For example, "123.456". - (Some(i), None) => Ok(s.len() - (i + 1)), - - // For example, "123.456e789". - (Some(i), Some(j)) if i < j => { - // Because of the match guard, this subtraction will not underflow. - let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64; - let exponent: i64 = s[j + 1..].parse()?; - if num_digits_between_decimal_point_and_e < exponent { - Ok(0) - } else { - Ok((num_digits_between_decimal_point_and_e - exponent) - .try_into() - .unwrap()) - } - } - _ => crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - s.quote(), - uucore::execution_phrase() - ), - } -} - -/// The number of digits before the decimal point in a given number. -/// -/// The input `s` is a string representing a number, either an integer -/// or a floating point number in either decimal notation or scientific -/// notation. This function returns the number of digits before the -/// decimal point needed to print the number in decimal notation. -/// -/// # Examples -/// -/// ```rust,ignore -/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 2); -/// ``` -pub fn num_integral_digits(s: &str) -> Result { - match (s.find('.'), s.find('e')) { - // For example, "123456". - (None, None) => Ok(s.len()), - - // For example, "123e456". - (None, Some(j)) => { - let exponent: i64 = s[j + 1..].parse()?; - let total = j as i64 + exponent; - if total < 1 { - Ok(1) - } else { - Ok(total.try_into().unwrap()) - } - } - - // For example, "123.456". - (Some(i), None) => Ok(i), - - // For example, "123.456e789". - (Some(i), Some(j)) => { - let exponent: i64 = s[j + 1..].parse()?; - let minimum: usize = { - let integral_part: f64 = crash_if_err!(1, s[..j].parse()); - if integral_part == -0.0 && integral_part.is_sign_negative() { - 2 - } else { - 1 - } - }; - - let total = i as i64 + exponent; - if total < minimum as i64 { - Ok(minimum) - } else { - Ok(total.try_into().unwrap()) - } - } - } -} - -#[cfg(test)] -mod tests { - - mod test_num_integral_digits { - use crate::num_integral_digits; - - #[test] - fn test_integer() { - assert_eq!(num_integral_digits("123").unwrap(), 3); - } - - #[test] - fn test_decimal() { - assert_eq!(num_integral_digits("123.45").unwrap(), 3); - } - - #[test] - fn test_scientific_no_decimal_positive_exponent() { - assert_eq!(num_integral_digits("123e4").unwrap(), 3 + 4); - } - - #[test] - fn test_scientific_with_decimal_positive_exponent() { - assert_eq!(num_integral_digits("123.45e6").unwrap(), 3 + 6); - } - - #[test] - fn test_scientific_no_decimal_negative_exponent() { - assert_eq!(num_integral_digits("123e-4").unwrap(), 1); - } - - #[test] - fn test_scientific_with_decimal_negative_exponent() { - assert_eq!(num_integral_digits("123.45e-6").unwrap(), 1); - assert_eq!(num_integral_digits("123.45e-1").unwrap(), 2); - } - } - - mod test_num_fractional_digits { - use crate::num_fractional_digits; - - #[test] - fn test_integer() { - assert_eq!(num_fractional_digits("123").unwrap(), 0); - } - - #[test] - fn test_decimal() { - assert_eq!(num_fractional_digits("123.45").unwrap(), 2); - } - - #[test] - fn test_scientific_no_decimal_positive_exponent() { - assert_eq!(num_fractional_digits("123e4").unwrap(), 0); - } - - #[test] - fn test_scientific_with_decimal_positive_exponent() { - assert_eq!(num_fractional_digits("123.45e6").unwrap(), 0); - assert_eq!(num_fractional_digits("123.45e1").unwrap(), 1); - } - - #[test] - fn test_scientific_no_decimal_negative_exponent() { - assert_eq!(num_fractional_digits("123e-4").unwrap(), 4); - assert_eq!(num_fractional_digits("123e-1").unwrap(), 1); - } - - #[test] - fn test_scientific_with_decimal_negative_exponent() { - assert_eq!(num_fractional_digits("123.45e-6").unwrap(), 8); - assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3); - } - } -} diff --git a/src/uu/seq/src/extendedbigdecimal.rs b/src/uu/seq/src/extendedbigdecimal.rs new file mode 100644 index 000000000..6cad83dad --- /dev/null +++ b/src/uu/seq/src/extendedbigdecimal.rs @@ -0,0 +1,290 @@ +// spell-checker:ignore bigdecimal extendedbigdecimal extendedbigint +//! An arbitrary precision float that can also represent infinity, NaN, etc. +//! +//! The finite values are stored as [`BigDecimal`] instances. Because +//! the `bigdecimal` library does not represent infinity, NaN, etc., we +//! need to represent them explicitly ourselves. The +//! [`ExtendedBigDecimal`] enumeration does that. +//! +//! # Examples +//! +//! Addition works for [`ExtendedBigDecimal`] as it does for floats. For +//! example, adding infinity to any finite value results in infinity: +//! +//! ```rust,ignore +//! let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); +//! let summand2 = ExtendedBigDecimal::Infinity; +//! assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity); +//! ``` +use std::cmp::Ordering; +use std::fmt::Display; +use std::ops::Add; + +use bigdecimal::BigDecimal; +use num_bigint::BigInt; +use num_bigint::ToBigInt; +use num_traits::One; +use num_traits::Zero; + +use crate::extendedbigint::ExtendedBigInt; + +#[derive(Debug, Clone)] +pub enum ExtendedBigDecimal { + /// Arbitrary precision floating point number. + BigDecimal(BigDecimal), + + /// Floating point positive infinity. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support infinity, see [here][0]. + /// + /// [0]: https://github.com/akubera/bigdecimal-rs/issues/67 + Infinity, + + /// Floating point negative infinity. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support infinity, see [here][0]. + /// + /// [0]: https://github.com/akubera/bigdecimal-rs/issues/67 + MinusInfinity, + + /// Floating point negative zero. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support negative zero. + MinusZero, + + /// Floating point NaN. + /// + /// This is represented as its own enumeration member instead of as + /// a [`BigDecimal`] because the `bigdecimal` library does not + /// support NaN, see [here][0]. + /// + /// [0]: https://github.com/akubera/bigdecimal-rs/issues/67 + Nan, +} + +/// The smallest integer greater than or equal to this number. +fn ceil(x: BigDecimal) -> BigInt { + if x.is_integer() { + // Unwrapping the Option because it always returns Some + x.to_bigint().unwrap() + } else { + (x + BigDecimal::one().half()).round(0).to_bigint().unwrap() + } +} + +/// The largest integer less than or equal to this number. +fn floor(x: BigDecimal) -> BigInt { + if x.is_integer() { + // Unwrapping the Option because it always returns Some + x.to_bigint().unwrap() + } else { + (x - BigDecimal::one().half()).round(0).to_bigint().unwrap() + } +} + +impl ExtendedBigDecimal { + /// The smallest integer greater than or equal to this number. + pub fn ceil(self) -> ExtendedBigInt { + match self { + ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(ceil(x)), + other => From::from(other), + } + } + + /// The largest integer less than or equal to this number. + pub fn floor(self) -> ExtendedBigInt { + match self { + ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(floor(x)), + other => From::from(other), + } + } +} + +impl From for ExtendedBigDecimal { + fn from(big_int: ExtendedBigInt) -> Self { + match big_int { + ExtendedBigInt::BigInt(n) => Self::BigDecimal(BigDecimal::from(n)), + ExtendedBigInt::Infinity => ExtendedBigDecimal::Infinity, + ExtendedBigInt::MinusInfinity => ExtendedBigDecimal::MinusInfinity, + ExtendedBigInt::MinusZero => ExtendedBigDecimal::MinusZero, + ExtendedBigInt::Nan => ExtendedBigDecimal::Nan, + } + } +} + +impl Display for ExtendedBigDecimal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExtendedBigDecimal::BigDecimal(x) => { + let (n, p) = x.as_bigint_and_exponent(); + match p { + 0 => ExtendedBigDecimal::BigDecimal(BigDecimal::new(n * 10, 1)).fmt(f), + _ => x.fmt(f), + } + } + ExtendedBigDecimal::Infinity => f32::INFINITY.fmt(f), + ExtendedBigDecimal::MinusInfinity => f32::NEG_INFINITY.fmt(f), + ExtendedBigDecimal::MinusZero => { + // FIXME In Rust version 1.53.0 and later, the display + // of floats was updated to allow displaying negative + // zero. See + // https://github.com/rust-lang/rust/pull/78618. Currently, + // this just formats "0.0". + (0.0f32).fmt(f) + } + ExtendedBigDecimal::Nan => "nan".fmt(f), + } + } +} + +impl Zero for ExtendedBigDecimal { + fn zero() -> Self { + ExtendedBigDecimal::BigDecimal(BigDecimal::zero()) + } + fn is_zero(&self) -> bool { + match self { + Self::BigDecimal(n) => n.is_zero(), + Self::MinusZero => true, + _ => false, + } + } +} + +impl Add for ExtendedBigDecimal { + type Output = Self; + + fn add(self, other: Self) -> Self { + match (self, other) { + (Self::BigDecimal(m), Self::BigDecimal(n)) => Self::BigDecimal(m.add(n)), + (Self::BigDecimal(_), Self::MinusInfinity) => Self::MinusInfinity, + (Self::BigDecimal(_), Self::Infinity) => Self::Infinity, + (Self::BigDecimal(_), Self::Nan) => Self::Nan, + (Self::BigDecimal(m), Self::MinusZero) => Self::BigDecimal(m), + (Self::Infinity, Self::BigDecimal(_)) => Self::Infinity, + (Self::Infinity, Self::Infinity) => Self::Infinity, + (Self::Infinity, Self::MinusZero) => Self::Infinity, + (Self::Infinity, Self::MinusInfinity) => Self::Nan, + (Self::Infinity, Self::Nan) => Self::Nan, + (Self::MinusInfinity, Self::BigDecimal(_)) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity, + (Self::MinusInfinity, Self::Infinity) => Self::Nan, + (Self::MinusInfinity, Self::Nan) => Self::Nan, + (Self::Nan, _) => Self::Nan, + (Self::MinusZero, other) => other, + } + } +} + +impl PartialEq for ExtendedBigDecimal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::BigDecimal(m), Self::BigDecimal(n)) => m.eq(n), + (Self::BigDecimal(_), Self::MinusInfinity) => false, + (Self::BigDecimal(_), Self::Infinity) => false, + (Self::BigDecimal(_), Self::Nan) => false, + (Self::BigDecimal(_), Self::MinusZero) => false, + (Self::Infinity, Self::BigDecimal(_)) => false, + (Self::Infinity, Self::Infinity) => true, + (Self::Infinity, Self::MinusZero) => false, + (Self::Infinity, Self::MinusInfinity) => false, + (Self::Infinity, Self::Nan) => false, + (Self::MinusInfinity, Self::BigDecimal(_)) => false, + (Self::MinusInfinity, Self::Infinity) => false, + (Self::MinusInfinity, Self::MinusZero) => false, + (Self::MinusInfinity, Self::MinusInfinity) => true, + (Self::MinusInfinity, Self::Nan) => false, + (Self::Nan, _) => false, + (Self::MinusZero, Self::BigDecimal(_)) => false, + (Self::MinusZero, Self::Infinity) => false, + (Self::MinusZero, Self::MinusZero) => true, + (Self::MinusZero, Self::MinusInfinity) => false, + (Self::MinusZero, Self::Nan) => false, + } + } +} + +impl PartialOrd for ExtendedBigDecimal { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::BigDecimal(m), Self::BigDecimal(n)) => m.partial_cmp(n), + (Self::BigDecimal(_), Self::MinusInfinity) => Some(Ordering::Greater), + (Self::BigDecimal(_), Self::Infinity) => Some(Ordering::Less), + (Self::BigDecimal(_), Self::Nan) => None, + (Self::BigDecimal(m), Self::MinusZero) => m.partial_cmp(&BigDecimal::zero()), + (Self::Infinity, Self::BigDecimal(_)) => Some(Ordering::Greater), + (Self::Infinity, Self::Infinity) => Some(Ordering::Equal), + (Self::Infinity, Self::MinusZero) => Some(Ordering::Greater), + (Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::Infinity, Self::Nan) => None, + (Self::MinusInfinity, Self::BigDecimal(_)) => Some(Ordering::Less), + (Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal), + (Self::MinusInfinity, Self::Nan) => None, + (Self::Nan, _) => None, + (Self::MinusZero, Self::BigDecimal(n)) => BigDecimal::zero().partial_cmp(n), + (Self::MinusZero, Self::Infinity) => Some(Ordering::Less), + (Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal), + (Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::MinusZero, Self::Nan) => None, + } + } +} + +#[cfg(test)] +mod tests { + + use bigdecimal::BigDecimal; + use num_traits::Zero; + + use crate::extendedbigdecimal::ExtendedBigDecimal; + + #[test] + fn test_addition_infinity() { + let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); + let summand2 = ExtendedBigDecimal::Infinity; + assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity); + } + + #[test] + fn test_addition_minus_infinity() { + let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); + let summand2 = ExtendedBigDecimal::MinusInfinity; + assert_eq!(summand1 + summand2, ExtendedBigDecimal::MinusInfinity); + } + + #[test] + fn test_addition_nan() { + let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero()); + let summand2 = ExtendedBigDecimal::Nan; + let sum = summand1 + summand2; + match sum { + ExtendedBigDecimal::Nan => (), + _ => unreachable!(), + } + } + + #[test] + fn test_display() { + assert_eq!( + format!("{}", ExtendedBigDecimal::BigDecimal(BigDecimal::zero())), + "0.0" + ); + assert_eq!(format!("{}", ExtendedBigDecimal::Infinity), "inf"); + assert_eq!(format!("{}", ExtendedBigDecimal::MinusInfinity), "-inf"); + assert_eq!(format!("{}", ExtendedBigDecimal::Nan), "nan"); + // FIXME In Rust version 1.53.0 and later, the display of floats + // was updated to allow displaying negative zero. Until then, we + // just display `MinusZero` as "0.0". + // + // assert_eq!(format!("{}", ExtendedBigDecimal::MinusZero), "-0.0"); + // + } +} diff --git a/src/uu/seq/src/extendedbigint.rs b/src/uu/seq/src/extendedbigint.rs new file mode 100644 index 000000000..4a33fa617 --- /dev/null +++ b/src/uu/seq/src/extendedbigint.rs @@ -0,0 +1,218 @@ +// spell-checker:ignore bigint extendedbigint extendedbigdecimal +//! An arbitrary precision integer that can also represent infinity, NaN, etc. +//! +//! Usually infinity, NaN, and negative zero are only represented for +//! floating point numbers. The [`ExtendedBigInt`] enumeration provides +//! a representation of those things with the set of integers. The +//! finite values are stored as [`BigInt`] instances. +//! +//! # Examples +//! +//! Addition works for [`ExtendedBigInt`] as it does for floats. For +//! example, adding infinity to any finite value results in infinity: +//! +//! ```rust,ignore +//! let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); +//! let summand2 = ExtendedBigInt::Infinity; +//! assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity); +//! ``` +use std::cmp::Ordering; +use std::fmt::Display; +use std::ops::Add; + +use num_bigint::BigInt; +use num_bigint::ToBigInt; +use num_traits::One; +use num_traits::Zero; + +use crate::extendedbigdecimal::ExtendedBigDecimal; + +#[derive(Debug, Clone)] +pub enum ExtendedBigInt { + BigInt(BigInt), + Infinity, + MinusInfinity, + MinusZero, + Nan, +} + +impl ExtendedBigInt { + /// The integer number one. + pub fn one() -> Self { + // We would like to implement `num_traits::One`, but it requires + // a multiplication implementation, and we don't want to + // implement that here. + ExtendedBigInt::BigInt(BigInt::one()) + } +} + +impl From for ExtendedBigInt { + fn from(big_decimal: ExtendedBigDecimal) -> Self { + match big_decimal { + // TODO When can this fail? + ExtendedBigDecimal::BigDecimal(x) => Self::BigInt(x.to_bigint().unwrap()), + ExtendedBigDecimal::Infinity => ExtendedBigInt::Infinity, + ExtendedBigDecimal::MinusInfinity => ExtendedBigInt::MinusInfinity, + ExtendedBigDecimal::MinusZero => ExtendedBigInt::MinusZero, + ExtendedBigDecimal::Nan => ExtendedBigInt::Nan, + } + } +} + +impl Display for ExtendedBigInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExtendedBigInt::BigInt(n) => n.fmt(f), + ExtendedBigInt::Infinity => f32::INFINITY.fmt(f), + ExtendedBigInt::MinusInfinity => f32::NEG_INFINITY.fmt(f), + ExtendedBigInt::MinusZero => { + // FIXME Come up with a way of formatting this with a + // "-" prefix. + 0.fmt(f) + } + ExtendedBigInt::Nan => "nan".fmt(f), + } + } +} + +impl Zero for ExtendedBigInt { + fn zero() -> Self { + ExtendedBigInt::BigInt(BigInt::zero()) + } + fn is_zero(&self) -> bool { + match self { + Self::BigInt(n) => n.is_zero(), + Self::MinusZero => true, + _ => false, + } + } +} + +impl Add for ExtendedBigInt { + type Output = Self; + + fn add(self, other: Self) -> Self { + match (self, other) { + (Self::BigInt(m), Self::BigInt(n)) => Self::BigInt(m.add(n)), + (Self::BigInt(_), Self::MinusInfinity) => Self::MinusInfinity, + (Self::BigInt(_), Self::Infinity) => Self::Infinity, + (Self::BigInt(_), Self::Nan) => Self::Nan, + (Self::BigInt(m), Self::MinusZero) => Self::BigInt(m), + (Self::Infinity, Self::BigInt(_)) => Self::Infinity, + (Self::Infinity, Self::Infinity) => Self::Infinity, + (Self::Infinity, Self::MinusZero) => Self::Infinity, + (Self::Infinity, Self::MinusInfinity) => Self::Nan, + (Self::Infinity, Self::Nan) => Self::Nan, + (Self::MinusInfinity, Self::BigInt(_)) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity, + (Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity, + (Self::MinusInfinity, Self::Infinity) => Self::Nan, + (Self::MinusInfinity, Self::Nan) => Self::Nan, + (Self::Nan, _) => Self::Nan, + (Self::MinusZero, other) => other, + } + } +} + +impl PartialEq for ExtendedBigInt { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::BigInt(m), Self::BigInt(n)) => m.eq(n), + (Self::BigInt(_), Self::MinusInfinity) => false, + (Self::BigInt(_), Self::Infinity) => false, + (Self::BigInt(_), Self::Nan) => false, + (Self::BigInt(_), Self::MinusZero) => false, + (Self::Infinity, Self::BigInt(_)) => false, + (Self::Infinity, Self::Infinity) => true, + (Self::Infinity, Self::MinusZero) => false, + (Self::Infinity, Self::MinusInfinity) => false, + (Self::Infinity, Self::Nan) => false, + (Self::MinusInfinity, Self::BigInt(_)) => false, + (Self::MinusInfinity, Self::Infinity) => false, + (Self::MinusInfinity, Self::MinusZero) => false, + (Self::MinusInfinity, Self::MinusInfinity) => true, + (Self::MinusInfinity, Self::Nan) => false, + (Self::Nan, _) => false, + (Self::MinusZero, Self::BigInt(_)) => false, + (Self::MinusZero, Self::Infinity) => false, + (Self::MinusZero, Self::MinusZero) => true, + (Self::MinusZero, Self::MinusInfinity) => false, + (Self::MinusZero, Self::Nan) => false, + } + } +} + +impl PartialOrd for ExtendedBigInt { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::BigInt(m), Self::BigInt(n)) => m.partial_cmp(n), + (Self::BigInt(_), Self::MinusInfinity) => Some(Ordering::Greater), + (Self::BigInt(_), Self::Infinity) => Some(Ordering::Less), + (Self::BigInt(_), Self::Nan) => None, + (Self::BigInt(m), Self::MinusZero) => m.partial_cmp(&BigInt::zero()), + (Self::Infinity, Self::BigInt(_)) => Some(Ordering::Greater), + (Self::Infinity, Self::Infinity) => Some(Ordering::Equal), + (Self::Infinity, Self::MinusZero) => Some(Ordering::Greater), + (Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::Infinity, Self::Nan) => None, + (Self::MinusInfinity, Self::BigInt(_)) => Some(Ordering::Less), + (Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less), + (Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal), + (Self::MinusInfinity, Self::Nan) => None, + (Self::Nan, _) => None, + (Self::MinusZero, Self::BigInt(n)) => BigInt::zero().partial_cmp(n), + (Self::MinusZero, Self::Infinity) => Some(Ordering::Less), + (Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal), + (Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater), + (Self::MinusZero, Self::Nan) => None, + } + } +} + +#[cfg(test)] +mod tests { + + use num_bigint::BigInt; + use num_traits::Zero; + + use crate::extendedbigint::ExtendedBigInt; + + #[test] + fn test_addition_infinity() { + let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); + let summand2 = ExtendedBigInt::Infinity; + assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity); + } + + #[test] + fn test_addition_minus_infinity() { + let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); + let summand2 = ExtendedBigInt::MinusInfinity; + assert_eq!(summand1 + summand2, ExtendedBigInt::MinusInfinity); + } + + #[test] + fn test_addition_nan() { + let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); + let summand2 = ExtendedBigInt::Nan; + let sum = summand1 + summand2; + match sum { + ExtendedBigInt::Nan => (), + _ => unreachable!(), + } + } + + #[test] + fn test_display() { + assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "0"); + assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf"); + assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf"); + assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan"); + // FIXME Come up with a way of displaying negative zero as + // "-0". Currently it displays as just "0". + // + // assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0"); + // + } +} diff --git a/src/uu/seq/src/number.rs b/src/uu/seq/src/number.rs new file mode 100644 index 000000000..9062fa1a1 --- /dev/null +++ b/src/uu/seq/src/number.rs @@ -0,0 +1,119 @@ +// spell-checker:ignore extendedbigdecimal extendedbigint +//! A type to represent the possible start, increment, and end values for seq. +//! +//! The [`Number`] enumeration represents the possible values for the +//! start, increment, and end values for `seq`. These may be integers, +//! floating point numbers, negative zero, etc. A [`Number`] can be +//! parsed from a string by calling [`parse`]. +use num_traits::Zero; + +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::extendedbigint::ExtendedBigInt; + +/// An integral or floating point number. +#[derive(Debug, PartialEq)] +pub enum Number { + Int(ExtendedBigInt), + Float(ExtendedBigDecimal), +} + +impl Number { + /// Decide whether this number is zero (either positive or negative). + pub fn is_zero(&self) -> bool { + // We would like to implement `num_traits::Zero`, but it + // requires an addition implementation, and we don't want to + // implement that here. + match self { + Number::Int(n) => n.is_zero(), + Number::Float(x) => x.is_zero(), + } + } + + /// Convert this number into an `ExtendedBigDecimal`. + pub fn into_extended_big_decimal(self) -> ExtendedBigDecimal { + match self { + Number::Int(n) => ExtendedBigDecimal::from(n), + Number::Float(x) => x, + } + } + + /// The integer number one. + pub fn one() -> Self { + // We would like to implement `num_traits::One`, but it requires + // a multiplication implementation, and we don't want to + // implement that here. + Number::Int(ExtendedBigInt::one()) + } + + /// Round this number towards the given other number. + /// + /// If `other` is greater, then round up. If `other` is smaller, + /// then round down. + pub fn round_towards(self, other: &ExtendedBigInt) -> ExtendedBigInt { + match self { + // If this number is already an integer, it is already + // rounded to the nearest integer in the direction of + // `other`. + Number::Int(num) => num, + // Otherwise, if this number is a float, we need to decide + // whether `other` is larger or smaller than it, and thus + // whether to round up or round down, respectively. + Number::Float(num) => { + let other: ExtendedBigDecimal = From::from(other.clone()); + if other > num { + num.ceil() + } else { + // If they are equal, then `self` is already an + // integer, so calling `floor()` does no harm and + // will just return that integer anyway. + num.floor() + } + } + } + } +} + +/// A number with a specified number of integer and fractional digits. +/// +/// This struct can be used to represent a number along with information +/// on how many significant digits to use when displaying the number. +/// The [`num_integral_digits`] field also includes the width needed to +/// display the "-" character for a negative number. +/// +/// You can get an instance of this struct by calling [`str::parse`]. +#[derive(Debug)] +pub struct PreciseNumber { + pub number: Number, + pub num_integral_digits: usize, + pub num_fractional_digits: usize, +} + +impl PreciseNumber { + pub fn new( + number: Number, + num_integral_digits: usize, + num_fractional_digits: usize, + ) -> PreciseNumber { + PreciseNumber { + number, + num_integral_digits, + num_fractional_digits, + } + } + + /// The integer number one. + pub fn one() -> Self { + // We would like to implement `num_traits::One`, but it requires + // a multiplication implementation, and we don't want to + // implement that here. + PreciseNumber::new(Number::one(), 1, 0) + } + + /// Decide whether this number is zero (either positive or negative). + pub fn is_zero(&self) -> bool { + // We would like to implement `num_traits::Zero`, but it + // requires an addition implementation, and we don't want to + // implement that here. + self.number.is_zero() + } +} diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs new file mode 100644 index 000000000..da235790d --- /dev/null +++ b/src/uu/seq/src/numberparse.rs @@ -0,0 +1,589 @@ +// spell-checker:ignore extendedbigdecimal extendedbigint bigdecimal numberparse +//! Parsing numbers for use in `seq`. +//! +//! This module provides an implementation of [`FromStr`] for the +//! [`PreciseNumber`] struct. +use std::convert::TryInto; +use std::str::FromStr; + +use bigdecimal::BigDecimal; +use num_bigint::BigInt; +use num_bigint::Sign; +use num_traits::Num; +use num_traits::Zero; + +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::extendedbigint::ExtendedBigInt; +use crate::number::Number; +use crate::number::PreciseNumber; + +/// An error returned when parsing a number fails. +#[derive(Debug, PartialEq)] +pub enum ParseNumberError { + Float, + Nan, + Hex, +} + +/// Decide whether a given string and its parsed `BigInt` is negative zero. +fn is_minus_zero_int(s: &str, n: &BigInt) -> bool { + s.starts_with('-') && n == &BigInt::zero() +} + +/// Decide whether a given string and its parsed `BigDecimal` is negative zero. +fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool { + s.starts_with('-') && x == &BigDecimal::zero() +} + +/// Parse a number with neither a decimal point nor an exponent. +/// +/// # Errors +/// +/// This function returns an error if the input string is a variant of +/// "NaN" or if no [`BigInt`] could be parsed from the string. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "0".parse::().unwrap().number; +/// let expected = Number::BigInt(BigInt::zero()); +/// assert_eq!(actual, expected); +/// ``` +fn parse_no_decimal_no_exponent(s: &str) -> Result { + match s.parse::() { + Ok(n) => { + // If `s` is '-0', then `parse()` returns `BigInt::zero()`, + // but we need to return `Number::MinusZeroInt` instead. + if is_minus_zero_int(s, &n) { + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::MinusZero), + s.len(), + 0, + )) + } else { + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(n)), + s.len(), + 0, + )) + } + } + Err(_) => { + // Possibly "NaN" or "inf". + // + // TODO In Rust v1.53.0, this change + // https://github.com/rust-lang/rust/pull/78618 improves the + // parsing of floats to include being able to parse "NaN" + // and "inf". So when the minimum version of this crate is + // increased to 1.53.0, we should just use the built-in + // `f32` parsing instead. + if s.eq_ignore_ascii_case("inf") { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::Infinity), + 0, + 0, + )) + } else if s.eq_ignore_ascii_case("-inf") { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusInfinity), + 0, + 0, + )) + } else if s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case("-nan") { + Err(ParseNumberError::Nan) + } else { + Err(ParseNumberError::Float) + } + } + } +} + +/// Parse a number with an exponent but no decimal point. +/// +/// # Errors +/// +/// This function returns an error if `s` is not a valid number. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "1e2".parse::().unwrap().number; +/// let expected = "100".parse::().unwrap(); +/// assert_eq!(actual, expected); +/// ``` +fn parse_exponent_no_decimal(s: &str, j: usize) -> Result { + let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?; + // If the exponent is strictly less than zero, then the number + // should be treated as a floating point number that will be + // displayed in decimal notation. For example, "1e-2" will be + // displayed as "0.01", but "1e2" will be displayed as "100", + // without a decimal point. + let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?; + let num_integral_digits = if is_minus_zero_float(s, &x) { + 2 + } else { + let total = j as i64 + exponent; + let result = if total < 1 { + 1 + } else { + total.try_into().unwrap() + }; + if x.sign() == Sign::Minus { + result + 1 + } else { + result + } + }; + let num_fractional_digits = if exponent < 0 { -exponent as usize } else { 0 }; + + if exponent < 0 { + if is_minus_zero_float(s, &x) { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::BigDecimal(x)), + num_integral_digits, + num_fractional_digits, + )) + } + } else { + let zeros = "0".repeat(exponent.try_into().unwrap()); + let expanded = [&s[0..j], &zeros].concat(); + parse_no_decimal_no_exponent(&expanded) + } +} + +/// Parse a number with a decimal point but no exponent. +/// +/// # Errors +/// +/// This function returns an error if `s` is not a valid number. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "1.2".parse::().unwrap().number; +/// let expected = "1.2".parse::().unwrap(); +/// assert_eq!(actual, expected); +/// ``` +fn parse_decimal_no_exponent(s: &str, i: usize) -> Result { + let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?; + let num_integral_digits = i; + let num_fractional_digits = s.len() - (i + 1); + if is_minus_zero_float(s, &x) { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::BigDecimal(x)), + num_integral_digits, + num_fractional_digits, + )) + } +} + +/// Parse a number with both a decimal point and an exponent. +/// +/// # Errors +/// +/// This function returns an error if `s` is not a valid number. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "1.2e3".parse::().unwrap().number; +/// let expected = "1200".parse::().unwrap(); +/// assert_eq!(actual, expected); +/// ``` +fn parse_decimal_and_exponent( + s: &str, + i: usize, + j: usize, +) -> Result { + // Because of the match guard, this subtraction will not underflow. + let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64; + let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?; + let val: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?; + + let num_integral_digits = { + let minimum: usize = { + let integral_part: f64 = s[..j].parse().map_err(|_| ParseNumberError::Float)?; + if integral_part == -0.0 && integral_part.is_sign_negative() { + 2 + } else { + 1 + } + }; + + let total = i as i64 + exponent; + if total < minimum as i64 { + minimum + } else { + total.try_into().unwrap() + } + }; + let num_fractional_digits = if num_digits_between_decimal_point_and_e < exponent { + 0 + } else { + (num_digits_between_decimal_point_and_e - exponent) + .try_into() + .unwrap() + }; + + if num_digits_between_decimal_point_and_e <= exponent { + if is_minus_zero_float(s, &val) { + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + let zeros: String = "0".repeat( + (exponent - num_digits_between_decimal_point_and_e) + .try_into() + .unwrap(), + ); + let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat(); + let n = expanded + .parse::() + .map_err(|_| ParseNumberError::Float)?; + Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(n)), + num_integral_digits, + num_fractional_digits, + )) + } + } else if is_minus_zero_float(s, &val) { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusZero), + num_integral_digits, + num_fractional_digits, + )) + } else { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::BigDecimal(val)), + num_integral_digits, + num_fractional_digits, + )) + } +} + +/// Parse a hexadecimal integer from a string. +/// +/// # Errors +/// +/// This function returns an error if no [`BigInt`] could be parsed from +/// the string. +/// +/// # Examples +/// +/// ```rust,ignore +/// let actual = "0x0".parse::().unwrap().number; +/// let expected = Number::BigInt(BigInt::zero()); +/// assert_eq!(actual, expected); +/// ``` +fn parse_hexadecimal(s: &str) -> Result { + let (is_neg, s) = if s.starts_with('-') { + (true, &s[3..]) + } else { + (false, &s[2..]) + }; + + if s.starts_with('-') || s.starts_with('+') { + // Even though this is more like an invalid hexadecimal number, + // GNU reports this as an invalid floating point number, so we + // use `ParseNumberError::Float` to match that behavior. + return Err(ParseNumberError::Float); + } + + let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?; + + match (is_neg, num == BigInt::zero()) { + (true, true) => Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::MinusZero), + 2, + 0, + )), + (true, false) => Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(-num)), + 0, + 0, + )), + (false, _) => Ok(PreciseNumber::new( + Number::Int(ExtendedBigInt::BigInt(num)), + 0, + 0, + )), + } +} + +impl FromStr for PreciseNumber { + type Err = ParseNumberError; + fn from_str(mut s: &str) -> Result { + // Trim leading whitespace. + s = s.trim_start(); + + // Trim a single leading "+" character. + if s.starts_with('+') { + s = &s[1..]; + } + + // Check if the string seems to be in hexadecimal format. + // + // May be 0x123 or -0x123, so the index `i` may be either 0 or 1. + if let Some(i) = s.to_lowercase().find("0x") { + if i <= 1 { + return parse_hexadecimal(s); + } + } + + // Find the decimal point and the exponent symbol. Parse the + // number differently depending on its form. This is important + // because the form of the input dictates how the output will be + // presented. + match (s.find('.'), s.find('e')) { + // For example, "123456" or "inf". + (None, None) => parse_no_decimal_no_exponent(s), + // For example, "123e456" or "1e-2". + (None, Some(j)) => parse_exponent_no_decimal(s, j), + // For example, "123.456". + (Some(i), None) => parse_decimal_no_exponent(s, i), + // For example, "123.456e789". + (Some(i), Some(j)) if i < j => parse_decimal_and_exponent(s, i, j), + // For example, "1e2.3" or "1.2.3". + _ => Err(ParseNumberError::Float), + } + } +} + +#[cfg(test)] +mod tests { + + use bigdecimal::BigDecimal; + use num_bigint::BigInt; + use num_traits::Zero; + + use crate::extendedbigdecimal::ExtendedBigDecimal; + use crate::extendedbigint::ExtendedBigInt; + use crate::number::Number; + use crate::number::PreciseNumber; + use crate::numberparse::ParseNumberError; + + /// Convenience function for parsing a [`Number`] and unwrapping. + fn parse(s: &str) -> Number { + s.parse::().unwrap().number + } + + /// Convenience function for getting the number of integral digits. + fn num_integral_digits(s: &str) -> usize { + s.parse::().unwrap().num_integral_digits + } + + /// Convenience function for getting the number of fractional digits. + fn num_fractional_digits(s: &str) -> usize { + s.parse::().unwrap().num_fractional_digits + } + + #[test] + fn test_parse_minus_zero_int() { + assert_eq!(parse("-0e0"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0e-0"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0e1"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0e+1"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0.0e1"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0x0"), Number::Int(ExtendedBigInt::MinusZero)); + } + + #[test] + fn test_parse_minus_zero_float() { + assert_eq!(parse("-0.0"), Number::Float(ExtendedBigDecimal::MinusZero)); + assert_eq!(parse("-0e-1"), Number::Float(ExtendedBigDecimal::MinusZero)); + assert_eq!( + parse("-0.0e-1"), + Number::Float(ExtendedBigDecimal::MinusZero) + ); + } + + #[test] + fn test_parse_big_int() { + assert_eq!(parse("0"), Number::Int(ExtendedBigInt::zero())); + assert_eq!(parse("0.1e1"), Number::Int(ExtendedBigInt::one())); + assert_eq!( + parse("1.0e1"), + Number::Int(ExtendedBigInt::BigInt("10".parse::().unwrap())) + ); + } + + #[test] + fn test_parse_hexadecimal_big_int() { + assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero())); + assert_eq!( + parse("0x10"), + Number::Int(ExtendedBigInt::BigInt("16".parse::().unwrap())) + ); + } + + #[test] + fn test_parse_big_decimal() { + assert_eq!( + parse("0.0"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "0.0".parse::().unwrap() + )) + ); + assert_eq!( + parse(".0"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "0.0".parse::().unwrap() + )) + ); + assert_eq!( + parse("1.0"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "1.0".parse::().unwrap() + )) + ); + assert_eq!( + parse("10e-1"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "1.0".parse::().unwrap() + )) + ); + assert_eq!( + parse("-1e-3"), + Number::Float(ExtendedBigDecimal::BigDecimal( + "-0.001".parse::().unwrap() + )) + ); + } + + #[test] + fn test_parse_inf() { + assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("-inf"), + Number::Float(ExtendedBigDecimal::MinusInfinity) + ); + } + + #[test] + fn test_parse_invalid_float() { + assert_eq!( + "1.2.3".parse::().unwrap_err(), + ParseNumberError::Float + ); + assert_eq!( + "1e2e3".parse::().unwrap_err(), + ParseNumberError::Float + ); + assert_eq!( + "1e2.3".parse::().unwrap_err(), + ParseNumberError::Float + ); + assert_eq!( + "-+-1".parse::().unwrap_err(), + ParseNumberError::Float + ); + } + + #[test] + fn test_parse_invalid_hex() { + assert_eq!( + "0xg".parse::().unwrap_err(), + ParseNumberError::Hex + ); + } + + #[test] + fn test_parse_invalid_nan() { + assert_eq!( + "nan".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "NAN".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "NaN".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "nAn".parse::().unwrap_err(), + ParseNumberError::Nan + ); + assert_eq!( + "-nan".parse::().unwrap_err(), + ParseNumberError::Nan + ); + } + + #[test] + fn test_num_integral_digits() { + // no decimal, no exponent + assert_eq!(num_integral_digits("123"), 3); + // decimal, no exponent + assert_eq!(num_integral_digits("123.45"), 3); + // exponent, no decimal + assert_eq!(num_integral_digits("123e4"), 3 + 4); + assert_eq!(num_integral_digits("123e-4"), 1); + assert_eq!(num_integral_digits("-1e-3"), 2); + // decimal and exponent + assert_eq!(num_integral_digits("123.45e6"), 3 + 6); + assert_eq!(num_integral_digits("123.45e-6"), 1); + assert_eq!(num_integral_digits("123.45e-1"), 2); + // minus zero int + assert_eq!(num_integral_digits("-0e0"), 2); + assert_eq!(num_integral_digits("-0e-0"), 2); + assert_eq!(num_integral_digits("-0e1"), 3); + assert_eq!(num_integral_digits("-0e+1"), 3); + assert_eq!(num_integral_digits("-0.0e1"), 3); + // minus zero float + assert_eq!(num_integral_digits("-0.0"), 2); + assert_eq!(num_integral_digits("-0e-1"), 2); + assert_eq!(num_integral_digits("-0.0e-1"), 2); + + // TODO In GNU `seq`, the `-w` option does not seem to work with + // hexadecimal arguments. In order to match that behavior, we + // report the number of integral digits as zero for hexadecimal + // inputs. + assert_eq!(num_integral_digits("0xff"), 0); + } + + #[test] + fn test_num_fractional_digits() { + // no decimal, no exponent + assert_eq!(num_fractional_digits("123"), 0); + assert_eq!(num_fractional_digits("0xff"), 0); + // decimal, no exponent + assert_eq!(num_fractional_digits("123.45"), 2); + // exponent, no decimal + assert_eq!(num_fractional_digits("123e4"), 0); + assert_eq!(num_fractional_digits("123e-4"), 4); + assert_eq!(num_fractional_digits("123e-1"), 1); + assert_eq!(num_fractional_digits("-1e-3"), 3); + // decimal and exponent + assert_eq!(num_fractional_digits("123.45e6"), 0); + assert_eq!(num_fractional_digits("123.45e1"), 1); + assert_eq!(num_fractional_digits("123.45e-6"), 8); + assert_eq!(num_fractional_digits("123.45e-1"), 3); + // minus zero int + assert_eq!(num_fractional_digits("-0e0"), 0); + assert_eq!(num_fractional_digits("-0e-0"), 0); + assert_eq!(num_fractional_digits("-0e1"), 0); + assert_eq!(num_fractional_digits("-0e+1"), 0); + assert_eq!(num_fractional_digits("-0.0e1"), 0); + // minus zero float + assert_eq!(num_fractional_digits("-0.0"), 1); + assert_eq!(num_fractional_digits("-0e-1"), 1); + assert_eq!(num_fractional_digits("-0.0e-1"), 2); + } +} diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index a76a23c4e..f28b4d6e8 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -1,23 +1,24 @@ // TODO: Make -w flag work with decimals // TODO: Support -f flag -// spell-checker:ignore (ToDO) istr chiter argptr ilen +// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse #[macro_use] extern crate uucore; use clap::{crate_version, 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::io::{stdout, ErrorKind, Write}; -use std::str::FromStr; -mod digits; -use crate::digits::num_fractional_digits; -use crate::digits::num_integral_digits; +mod extendedbigdecimal; +mod extendedbigint; +mod number; +mod numberparse; +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::extendedbigint::ExtendedBigInt; +use crate::number::Number; +use crate::number::PreciseNumber; +use crate::numberparse::ParseNumberError; use uucore::display::Quotable; @@ -43,124 +44,55 @@ struct SeqOptions { widths: bool, } -enum Number { - /// Negative zero, as if it were an integer. - MinusZero, - BigInt(BigInt), - F64(f64), -} - -impl Number { - fn is_zero(&self) -> bool { - match self { - Number::MinusZero => true, - Number::BigInt(n) => n.is_zero(), - Number::F64(n) => n.is_zero(), - } - } - - fn into_f64(self) -> f64 { - match self { - Number::MinusZero => -0., - // BigInt::to_f64() can not return None. - Number::BigInt(n) => n.to_f64().unwrap(), - Number::F64(n) => n, - } - } - - /// Convert this number into a bigint, consuming it. - /// - /// For floats, this returns the [`BigInt`] corresponding to the - /// floor of the number. - fn into_bigint(self) -> BigInt { - match self { - Number::MinusZero => BigInt::zero(), - Number::F64(x) => BigInt::from(x.floor() as i64), - Number::BigInt(n) => n, - } - } -} - -impl FromStr for Number { - type Err = String; - fn from_str(mut s: &str) -> Result { - s = s.trim_start(); - if s.starts_with('+') { - s = &s[1..]; - } - let is_neg = s.starts_with('-'); - - match s.to_lowercase().find("0x") { - Some(i) if i <= 1 => match &s.as_bytes()[i + 2] { - b'-' | b'+' => Err(format!( - "invalid hexadecimal argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - // TODO: hexadecimal floating point parsing (see #2660) - b'.' => Err(format!( - "NotImplemented: hexadecimal floating point numbers: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - _ => { - let num = BigInt::from_str_radix(&s[i + 2..], 16) - .map_err(|_| format!( - "invalid hexadecimal argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - ))?; - match (is_neg, num == BigInt::zero()) { - (true, true) => Ok(Number::MinusZero), - (true, false) => Ok(Number::BigInt(-num)), - (false, _) => Ok(Number::BigInt(num)), - } - } - }, - Some(_) => Err(format!( - "invalid hexadecimal argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - - None => match s.parse::() { - Ok(n) => { - // If `s` is '-0', then `parse()` returns - // `BigInt::zero()`, but we need to return - // `Number::MinusZero` instead. - if n == BigInt::zero() && is_neg { - Ok(Number::MinusZero) - } else { - Ok(Number::BigInt(n)) - } - } - Err(_) => match s.parse::() { - Ok(value) if value.is_nan() => Err(format!( - "invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - Ok(value) => Ok(Number::F64(value)), - Err(_) => Err(format!( - "invalid floating point argument: {}\nTry '{} --help' for more information.", - s.quote(), - uucore::execution_phrase(), - )), - }, - }, - } - } -} - /// A range of integers. /// /// The elements are (first, increment, last). -type RangeInt = (BigInt, BigInt, BigInt); +type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt); -/// A range of f64. +/// A range of floats. /// /// The elements are (first, increment, last). -type RangeF64 = (f64, f64, f64); +type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal); + +/// Terminate the process with error code 1. +/// +/// Before terminating the process, this function prints an error +/// message that depends on `arg` and `e`. +/// +/// Although the signature of this function states that it returns a +/// [`PreciseNumber`], it never reaches the return statement. It is just +/// there to make it easier to use this function when unwrapping the +/// result of calling [`str::parse`] when attempting to parse a +/// [`PreciseNumber`]. +/// +/// # Examples +/// +/// ```rust,ignore +/// let s = "1.2e-3"; +/// s.parse::.unwrap_or_else(|e| exit_with_error(s, e)) +/// ``` +fn exit_with_error(arg: &str, e: ParseNumberError) -> ! { + match e { + ParseNumberError::Float => crash!( + 1, + "invalid floating point argument: {}\nTry '{} --help' for more information.", + arg.quote(), + uucore::execution_phrase() + ), + ParseNumberError::Nan => crash!( + 1, + "invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.", + arg.quote(), + uucore::execution_phrase() + ), + ParseNumberError::Hex => crash!( + 1, + "invalid hexadecimal argument: {}\nTry '{} --help' for more information.", + arg.quote(), + uucore::execution_phrase() + ), + } +} pub fn uumain(args: impl uucore::Args) -> i32 { let usage = usage(); @@ -174,53 +106,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { widths: matches.is_present(OPT_WIDTHS), }; - let mut largest_dec = 0; - let mut padding = 0; let first = if numbers.len() > 1 { let slice = numbers[0]; - largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - padding = num_integral_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - crash_if_err!(1, slice.parse()) + slice.parse().unwrap_or_else(|e| exit_with_error(slice, e)) } else { - Number::BigInt(BigInt::one()) + PreciseNumber::one() }; let increment = if numbers.len() > 2 { let slice = numbers[1]; - let dec = num_fractional_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - let int_digits = num_integral_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - largest_dec = cmp::max(largest_dec, dec); - padding = cmp::max(padding, int_digits); - crash_if_err!(1, slice.parse()) + slice.parse().unwrap_or_else(|e| exit_with_error(slice, e)) } else { - Number::BigInt(BigInt::one()) + PreciseNumber::one() }; if increment.is_zero() { show_error!( @@ -230,54 +126,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); return 1; } - let last: Number = { + let last: PreciseNumber = { let slice = numbers[numbers.len() - 1]; - let int_digits = num_integral_digits(slice).unwrap_or_else(|_| { - crash!( - 1, - "invalid floating point argument: {}\n Try '{} --help' for more information.", - slice.quote(), - uucore::execution_phrase() - ) - }); - padding = cmp::max(padding, int_digits); - crash_if_err!(1, slice.parse()) + slice.parse().unwrap_or_else(|e| exit_with_error(slice, e)) }; - let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0; - let result = match (first, last, increment) { - // For example, `seq -0 1 2` or `seq -0 1 2.0`. - (Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers( - (BigInt::zero(), increment, last.into_bigint()), - options.separator, - options.terminator, - options.widths, - padding, - true, - ), - // For example, `seq -0e0 1 2` or `seq -0e0 1 2.0`. - (Number::F64(x), last, Number::BigInt(increment)) if is_negative_zero_f64(x) => { + let padding = first + .num_integral_digits + .max(increment.num_integral_digits) + .max(last.num_integral_digits); + let largest_dec = first + .num_fractional_digits + .max(increment.num_fractional_digits); + + let result = match (first.number, increment.number, last.number) { + (Number::Int(first), Number::Int(increment), last) => { + let last = last.round_towards(&first); print_seq_integers( - (BigInt::zero(), increment, last.into_bigint()), + (first, increment, last), options.separator, options.terminator, options.widths, padding, - true, ) } - // For example, `seq 0 1 2` or `seq 0 1 2.0`. - (Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers( - (first, increment, last.into_bigint()), - options.separator, - options.terminator, - options.widths, - padding, - false, - ), - // For example, `seq 0 0.5 1` or `seq 0.0 0.5 1` or `seq 0.0 0.5 1.0`. - (first, last, increment) => print_seq( - (first.into_f64(), increment.into_f64(), last.into_f64()), + (first, increment, last) => print_seq( + ( + first.into_extended_big_decimal(), + increment.into_extended_big_decimal(), + last.into_extended_big_decimal(), + ), largest_dec, options.separator, options.terminator, @@ -329,7 +207,7 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn done_printing(next: &T, increment: &T, last: &T) -> bool { +fn done_printing(next: &T, increment: &T, last: &T) -> bool { if increment >= &T::zero() { next > last } else { @@ -337,9 +215,65 @@ fn done_printing(next: &T, increment: &T, last: &T) -> bool } } +/// Write a big decimal formatted according to the given parameters. +/// +/// This method is an adapter to support displaying negative zero on +/// Rust versions earlier than 1.53.0. After that version, we should be +/// able to display negative zero using the default formatting provided +/// by `-0.0f32`, for example. +fn write_value_float( + writer: &mut impl Write, + value: &ExtendedBigDecimal, + width: usize, + precision: usize, + is_first_iteration: bool, +) -> std::io::Result<()> { + let value_as_str = if *value == ExtendedBigDecimal::MinusZero && is_first_iteration { + format!( + "-{value:>0width$.precision$}", + value = value, + width = if width > 0 { width - 1 } else { width }, + precision = precision, + ) + } else { + format!( + "{value:>0width$.precision$}", + value = value, + width = width, + precision = precision, + ) + }; + write!(writer, "{}", value_as_str) +} + +/// Write a big int formatted according to the given parameters. +fn write_value_int( + writer: &mut impl Write, + value: &ExtendedBigInt, + width: usize, + pad: bool, + is_first_iteration: bool, +) -> std::io::Result<()> { + let value_as_str = if pad { + if *value == ExtendedBigInt::MinusZero && is_first_iteration { + format!("-{value:>0width$}", value = value, width = width - 1,) + } else { + format!("{value:>0width$}", value = value, width = width,) + } + } else if *value == ExtendedBigInt::MinusZero && is_first_iteration { + format!("-{}", value) + } else { + format!("{}", value) + }; + write!(writer, "{}", value_as_str) +} + +// TODO `print_seq()` and `print_seq_integers()` are nearly identical, +// they could be refactored into a single more general function. + /// Floating point based code path fn print_seq( - range: RangeF64, + range: RangeFloat, largest_dec: usize, separator: String, terminator: String, @@ -349,30 +283,23 @@ fn print_seq( let stdout = stdout(); let mut stdout = stdout.lock(); let (first, increment, last) = range; - let mut i = 0isize; - let is_first_minus_zero = first == -0.0 && first.is_sign_negative(); - let mut value = first + i as f64 * increment; + let mut value = first; let padding = if pad { padding + 1 + largest_dec } else { 0 }; let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { if !is_first_iteration { write!(stdout, "{}", separator)?; } - let mut width = padding; - if is_first_iteration && is_first_minus_zero { - write!(stdout, "-")?; - width -= 1; - } - is_first_iteration = false; - write!( - stdout, - "{value:>0width$.precision$}", - value = value, - width = width, - precision = largest_dec, + write_value_float( + &mut stdout, + &value, + padding, + largest_dec, + is_first_iteration, )?; - i += 1; - value = first + i as f64 * increment; + // TODO Implement augmenting addition. + value = value + increment.clone(); + is_first_iteration = false; } if !is_first_iteration { write!(stdout, "{}", terminator)?; @@ -401,7 +328,6 @@ fn print_seq_integers( terminator: String, pad: bool, padding: usize, - is_first_minus_zero: bool, ) -> std::io::Result<()> { let stdout = stdout(); let mut stdout = stdout.lock(); @@ -412,18 +338,10 @@ fn print_seq_integers( if !is_first_iteration { write!(stdout, "{}", separator)?; } - let mut width = padding; - if is_first_iteration && is_first_minus_zero { - write!(stdout, "-")?; - width -= 1; - } + write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?; + // TODO Implement augmenting addition. + value = value + increment.clone(); is_first_iteration = false; - if pad { - write!(stdout, "{number:>0width$}", number = value, width = width)?; - } else { - write!(stdout, "{}", value)?; - } - value += &increment; } if !is_first_iteration { diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 6ed3cb67d..2a2e31f83 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -7,25 +7,25 @@ fn test_hex_rejects_sign_after_identifier() { .args(&["0x-123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '0x-123ABC'") + .stderr_contains("invalid floating point argument: '0x-123ABC'") .stderr_contains("for more information."); new_ucmd!() .args(&["0x+123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '0x+123ABC'") + .stderr_contains("invalid floating point argument: '0x+123ABC'") .stderr_contains("for more information."); new_ucmd!() .args(&["-0x-123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '-0x-123ABC'") + .stderr_contains("invalid floating point argument: '-0x-123ABC'") .stderr_contains("for more information."); new_ucmd!() .args(&["-0x+123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '-0x+123ABC'") + .stderr_contains("invalid floating point argument: '-0x+123ABC'") .stderr_contains("for more information."); } @@ -60,7 +60,7 @@ fn test_hex_identifier_in_wrong_place() { .args(&["1234ABCD0x"]) .fails() .no_stdout() - .stderr_contains("invalid hexadecimal argument: '1234ABCD0x'") + .stderr_contains("invalid floating point argument: '1234ABCD0x'") .stderr_contains("for more information."); } @@ -479,6 +479,15 @@ fn test_width_decimal_scientific_notation_trailing_zeros_increment() { .no_stderr(); } +#[test] +fn test_width_negative_scientific_notation() { + new_ucmd!() + .args(&["-w", "-1e-3", "1"]) + .succeeds() + .stdout_is("-0.001\n00.999\n") + .no_stderr(); +} + /// Test that trailing zeros in the end argument do not contribute to width. #[test] fn test_width_decimal_scientific_notation_trailing_zeros_end() { @@ -544,3 +553,63 @@ fn test_trailing_whitespace_error() { // --help' for more information." .stderr_contains("for more information."); } + +#[test] +fn test_negative_zero_int_start_float_increment() { + new_ucmd!() + .args(&["-0", "0.1", "0.1"]) + .succeeds() + .stdout_is("-0.0\n0.1\n") + .no_stderr(); +} + +#[test] +fn test_float_precision_increment() { + new_ucmd!() + .args(&["999", "0.1", "1000.1"]) + .succeeds() + .stdout_is( + "999.0 +999.1 +999.2 +999.3 +999.4 +999.5 +999.6 +999.7 +999.8 +999.9 +1000.0 +1000.1 +", + ) + .no_stderr(); +} + +/// Test for floating point precision issues. +#[test] +fn test_negative_increment_decimal() { + new_ucmd!() + .args(&["0.1", "-0.1", "-0.2"]) + .succeeds() + .stdout_is("0.1\n0.0\n-0.1\n-0.2\n") + .no_stderr(); +} + +#[test] +fn test_zero_not_first() { + new_ucmd!() + .args(&["-w", "-0.1", "0.1", "0.1"]) + .succeeds() + .stdout_is("-0.1\n00.0\n00.1\n") + .no_stderr(); +} + +#[test] +fn test_rounding_end() { + new_ucmd!() + .args(&["1", "-1", "0.1"]) + .succeeds() + .stdout_is("1\n") + .no_stderr(); +} From 77e1570ea0624c9b71cd3d1eca1c97f2c5e8d893 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 13 Oct 2021 17:29:03 +0200 Subject: [PATCH 03/12] Move display::Quotable into its own crate The standalone version has a number of bugfixes compared to the old version. --- Cargo.lock | 14 +- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/mods/display.rs | 492 +---------------------------- 3 files changed, 15 insertions(+), 492 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bd0e776a..e262c29cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,6 +1291,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "os_display" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "748cc1d0dc55247316a5bedd8dc8c5478c8a0c2e2001176b38ce7c0ed732c7a5" +dependencies = [ + "unicode-width", +] + [[package]] name = "ouroboros" version = "0.10.1" @@ -2102,9 +2111,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -3276,6 +3285,7 @@ dependencies = [ "libc", "nix 0.20.0", "once_cell", + "os_display", "termion", "thiserror", "time", diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 952eecc28..153e3cd57 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -30,6 +30,7 @@ data-encoding-macro = { version="0.1.12", optional=true } z85 = { version="3.0.3", optional=true } libc = { version="0.2.15", optional=true } once_cell = "1.8.0" +os_display = "0.1.0" [target.'cfg(unix)'.dependencies] walkdir = { version="2.3.2", optional=true } diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index dfe64184f..95288973a 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -19,378 +19,16 @@ /// println_verbatim(path)?; // Prints "foo/bar.baz" /// # Ok::<(), std::io::Error>(()) /// ``` -// spell-checker:ignore Fbar -use std::borrow::Cow; use std::ffi::OsStr; -#[cfg(any(unix, target_os = "wasi", windows))] -use std::fmt::Write as FmtWrite; -use std::fmt::{self, Display, Formatter}; use std::io::{self, Write as IoWrite}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStrExt; -#[cfg(any(unix, target_os = "wasi"))] -use std::str::from_utf8; -/// An extension trait for displaying filenames to users. -pub trait Quotable { - /// Returns an object that implements [`Display`] for printing filenames with - /// proper quoting and escaping for the platform. - /// - /// On Unix this corresponds to sh/bash syntax, on Windows Powershell syntax - /// is used. - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use uucore::display::Quotable; - /// - /// let path = Path::new("foo/bar.baz"); - /// - /// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'" - /// ``` - fn quote(&self) -> Quoted<'_>; - - /// Like `quote()`, but don't actually add quotes unless necessary because of - /// whitespace or special characters. - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use uucore::display::Quotable; - /// use uucore::show_error; - /// - /// let foo = Path::new("foo/bar.baz"); - /// let bar = Path::new("foo bar"); - /// - /// show_error!("{}: Not found", foo.maybe_quote()); // Prints "util: foo/bar.baz: Not found" - /// show_error!("{}: Not found", bar.maybe_quote()); // Prints "util: 'foo bar': Not found" - /// ``` - fn maybe_quote(&self) -> Quoted<'_> { - let mut quoted = self.quote(); - quoted.force_quote = false; - quoted - } -} - -macro_rules! impl_as_ref { - ($type: ty) => { - impl Quotable for $type { - fn quote(&self) -> Quoted<'_> { - Quoted::new(self.as_ref()) - } - } - }; -} - -impl_as_ref!(str); -impl_as_ref!(&'_ str); -impl_as_ref!(String); -impl_as_ref!(std::path::Path); -impl_as_ref!(&'_ std::path::Path); -impl_as_ref!(std::path::PathBuf); -impl_as_ref!(std::path::Component<'_>); -impl_as_ref!(std::path::Components<'_>); -impl_as_ref!(std::path::Iter<'_>); -impl_as_ref!(std::ffi::OsStr); -impl_as_ref!(&'_ std::ffi::OsStr); -impl_as_ref!(std::ffi::OsString); - -// Cow<'_, str> does not implement AsRef and this is unlikely to be fixed -// for backward compatibility reasons. Otherwise we'd use a blanket impl. -impl Quotable for Cow<'_, str> { - fn quote(&self) -> Quoted<'_> { - let text: &str = self.as_ref(); - Quoted::new(text.as_ref()) - } -} - -impl Quotable for Cow<'_, std::path::Path> { - fn quote(&self) -> Quoted<'_> { - let text: &std::path::Path = self.as_ref(); - Quoted::new(text.as_ref()) - } -} - -/// A wrapper around [`OsStr`] for printing paths with quoting and escaping applied. -#[derive(Debug, Copy, Clone)] -pub struct Quoted<'a> { - text: &'a OsStr, - force_quote: bool, -} - -impl<'a> Quoted<'a> { - fn new(text: &'a OsStr) -> Self { - Quoted { - text, - force_quote: true, - } - } -} - -impl Display for Quoted<'_> { - #[cfg(any(windows, unix, target_os = "wasi"))] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // On Unix we emulate sh syntax. On Windows Powershell. - // They're just similar enough to share some code. - - /// Characters with special meaning outside quotes. - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - // I don't know why % is in there, and GNU doesn't quote it either. - // {} were used in a version elsewhere but seem unnecessary, GNU doesn't - // quote them. They're used in function definitions but not in a way we - // have to worry about. - #[cfg(any(unix, target_os = "wasi"))] - const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]="; - // FIXME: I'm not a PowerShell wizard and don't know if this is correct. - // I just copied the Unix version, removed \, and added ,{} based on - // experimentation. - // I have noticed that ~?*[] only get expanded in some contexts, so watch - // out for that if doing your own tests. - // Get-ChildItem seems unwilling to quote anything so it doesn't help. - // There's the additional wrinkle that Windows has stricter requirements - // for filenames: I've been testing using a Linux build of PowerShell, but - // this code doesn't even compile on Linux. - #[cfg(windows)] - const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\"'*?[]=,{}"; - - /// Characters with a special meaning at the beginning of a name. - // ~ expands a home directory. - // # starts a comment. - // ! is a common extension for expanding the shell history. - #[cfg(any(unix, target_os = "wasi"))] - const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '!']; - // Same deal as before, this is possibly incomplete. - // A single stand-alone exclamation mark seems to have some special meaning. - #[cfg(windows)] - const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '@', '!']; - - /// Characters that are interpreted specially in a double-quoted string. - #[cfg(any(unix, target_os = "wasi"))] - const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$', b'\\']; - #[cfg(windows)] - const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$']; - - let text = match self.text.to_str() { - None => return write_escaped(f, self.text), - Some(text) => text, - }; - - let mut is_single_safe = true; - let mut is_double_safe = true; - let mut requires_quote = self.force_quote; - - if let Some(first) = text.chars().next() { - if SPECIAL_SHELL_CHARS_START.contains(&first) { - requires_quote = true; - } - // Unlike in Unix, quoting an argument may stop it - // from being recognized as an option. I like that very much. - // But we don't want to quote "-" because that's a common - // special argument and PowerShell doesn't mind it. - #[cfg(windows)] - if first == '-' && text.len() > 1 { - requires_quote = true; - } - } else { - // Empty string - requires_quote = true; - } - - for ch in text.chars() { - if ch.is_ascii() { - let ch = ch as u8; - if ch == b'\'' { - is_single_safe = false; - } - if DOUBLE_UNSAFE.contains(&ch) { - is_double_safe = false; - } - if !requires_quote && SPECIAL_SHELL_CHARS.contains(&ch) { - requires_quote = true; - } - if ch.is_ascii_control() { - return write_escaped(f, self.text); - } - } - if !requires_quote && ch.is_whitespace() { - // This includes unicode whitespace. - // We maybe don't have to escape it, we don't escape other lookalike - // characters either, but it's confusing if it goes unquoted. - requires_quote = true; - } - } - - if !requires_quote { - return f.write_str(text); - } else if is_single_safe { - return write_simple(f, text, '\''); - } else if is_double_safe { - return write_simple(f, text, '\"'); - } else { - return write_single_escaped(f, text); - } - - fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { - f.write_char(quote)?; - f.write_str(text)?; - f.write_char(quote)?; - Ok(()) - } - - #[cfg(any(unix, target_os = "wasi"))] - fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { - let mut iter = text.split('\''); - if let Some(chunk) = iter.next() { - if !chunk.is_empty() { - write_simple(f, chunk, '\'')?; - } - } - for chunk in iter { - f.write_str("\\'")?; - if !chunk.is_empty() { - write_simple(f, chunk, '\'')?; - } - } - Ok(()) - } - - /// Write using the syntax described here: - /// https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html - /// - /// Supported by these shells: - /// - bash - /// - zsh - /// - busybox sh - /// - mksh - /// - /// Not supported by these: - /// - fish - /// - dash - /// - tcsh - #[cfg(any(unix, target_os = "wasi"))] - fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { - f.write_str("$'")?; - for chunk in from_utf8_iter(text.as_bytes()) { - match chunk { - Ok(chunk) => { - for ch in chunk.chars() { - match ch { - '\n' => f.write_str("\\n")?, - '\t' => f.write_str("\\t")?, - '\r' => f.write_str("\\r")?, - // We could do \b, \f, \v, etc., but those are - // rare enough to be confusing. - // \0 doesn't work consistently because of the - // octal \nnn syntax, and null bytes can't appear - // in filenames anyway. - ch if ch.is_ascii_control() => write!(f, "\\x{:02X}", ch as u8)?, - '\\' | '\'' => { - // '?' and '"' can also be escaped this way - // but AFAICT there's no reason to do so - f.write_char('\\')?; - f.write_char(ch)?; - } - ch => { - f.write_char(ch)?; - } - } - } - } - Err(unit) => write!(f, "\\x{:02X}", unit)?, - } - } - f.write_char('\'')?; - Ok(()) - } - - #[cfg(windows)] - fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { - // Quotes in Powershell can be escaped by doubling them - f.write_char('\'')?; - let mut iter = text.split('\''); - if let Some(chunk) = iter.next() { - f.write_str(chunk)?; - } - for chunk in iter { - f.write_str("''")?; - f.write_str(chunk)?; - } - f.write_char('\'')?; - Ok(()) - } - - #[cfg(windows)] - fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { - // ` takes the role of \ since \ is already used as the path separator. - // Things are UTF-16-oriented, so we escape code units as "`u{1234}". - use std::char::decode_utf16; - use std::os::windows::ffi::OsStrExt; - - f.write_char('"')?; - for ch in decode_utf16(text.encode_wide()) { - match ch { - Ok(ch) => match ch { - '\0' => f.write_str("`0")?, - '\r' => f.write_str("`r")?, - '\n' => f.write_str("`n")?, - '\t' => f.write_str("`t")?, - ch if ch.is_ascii_control() => write!(f, "`u{{{:04X}}}", ch as u8)?, - '`' => f.write_str("``")?, - '$' => f.write_str("`$")?, - '"' => f.write_str("\"\"")?, - ch => f.write_char(ch)?, - }, - Err(err) => write!(f, "`u{{{:04X}}}", err.unpaired_surrogate())?, - } - } - f.write_char('"')?; - Ok(()) - } - } - - #[cfg(not(any(unix, target_os = "wasi", windows)))] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // As a fallback, we use Rust's own escaping rules. - // This is reasonably sane and very easy to implement. - // We use single quotes because that's hardcoded in a lot of tests. - let text = self.text.to_string_lossy(); - if self.force_quote || !text.chars().all(|ch| ch.is_alphanumeric() || ch == '.') { - write!(f, "'{}'", text.escape_debug()) - } else { - f.write_str(&text) - } - } -} - -#[cfg(any(unix, target_os = "wasi"))] -fn from_utf8_iter(mut bytes: &[u8]) -> impl Iterator> { - std::iter::from_fn(move || { - if bytes.is_empty() { - return None; - } - match from_utf8(bytes) { - Ok(text) => { - bytes = &[]; - Some(Ok(text)) - } - Err(err) if err.valid_up_to() == 0 => { - let res = bytes[0]; - bytes = &bytes[1..]; - Some(Err(res)) - } - Err(err) => { - let (valid, rest) = bytes.split_at(err.valid_up_to()); - bytes = rest; - Some(Ok(from_utf8(valid).unwrap())) - } - } - }) -} +// These used to be defined here, but they live in their own crate now. +pub use os_display::{Quotable, Quoted}; /// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline, /// without losing any information if its encoding is invalid. @@ -429,129 +67,3 @@ pub fn print_verbatim>(text: S) -> io::Result<()> { write!(stdout, "{}", std::path::Path::new(text.as_ref()).display()) } } - -#[cfg(test)] -mod tests { - use super::*; - - fn verify_quote(cases: &[(impl Quotable, &str)]) { - for (case, expected) in cases { - assert_eq!(case.quote().to_string(), *expected); - } - } - - fn verify_maybe(cases: &[(impl Quotable, &str)]) { - for (case, expected) in cases { - assert_eq!(case.maybe_quote().to_string(), *expected); - } - } - - /// This should hold on any platform, or else other tests fail. - #[test] - fn test_basic() { - verify_quote(&[ - ("foo", "'foo'"), - ("", "''"), - ("foo/bar.baz", "'foo/bar.baz'"), - ]); - verify_maybe(&[ - ("foo", "foo"), - ("", "''"), - ("foo bar", "'foo bar'"), - ("$foo", "'$foo'"), - ("-", "-"), - ]); - } - - #[cfg(any(unix, target_os = "wasi", windows))] - #[test] - fn test_common() { - verify_maybe(&[ - ("a#b", "a#b"), - ("#ab", "'#ab'"), - ("a~b", "a~b"), - ("!", "'!'"), - ]); - } - - #[cfg(any(unix, target_os = "wasi"))] - #[test] - fn test_unix() { - verify_quote(&[ - ("can't", r#""can't""#), - (r#"can'"t"#, r#"'can'\''"t'"#), - (r#"can'$t"#, r#"'can'\''$t'"#), - ("foo\nb\ta\r\\\0`r", r#"$'foo\nb\ta\r\\\x00`r'"#), - ("foo\x02", r#"$'foo\x02'"#), - (r#"'$''"#, r#"\''$'\'\'"#), - ]); - verify_quote(&[(OsStr::from_bytes(b"foo\xFF"), r#"$'foo\xFF'"#)]); - verify_maybe(&[ - ("-x", "-x"), - ("a,b", "a,b"), - ("a\\b", "'a\\b'"), - ("}", ("}")), - ]); - } - - #[cfg(windows)] - #[test] - fn test_windows() { - use std::ffi::OsString; - use std::os::windows::ffi::OsStringExt; - verify_quote(&[ - (r#"foo\bar"#, r#"'foo\bar'"#), - ("can't", r#""can't""#), - (r#"can'"t"#, r#"'can''"t'"#), - (r#"can'$t"#, r#"'can''$t'"#), - ("foo\nb\ta\r\\\0`r", r#""foo`nb`ta`r\`0``r""#), - ("foo\x02", r#""foo`u{0002}""#), - (r#"'$''"#, r#"'''$'''''"#), - ]); - verify_quote(&[( - OsString::from_wide(&[b'x' as u16, 0xD800]), - r#""x`u{D800}""#, - )]); - verify_maybe(&[ - ("-x", "'-x'"), - ("a,b", "'a,b'"), - ("a\\b", "a\\b"), - ("}", "'}'"), - ]); - } - - #[cfg(any(unix, target_os = "wasi"))] - #[test] - fn test_utf8_iter() { - type ByteStr = &'static [u8]; - type Chunk = Result<&'static str, u8>; - const CASES: &[(ByteStr, &[Chunk])] = &[ - (b"", &[]), - (b"hello", &[Ok("hello")]), - // Immediately invalid - (b"\xFF", &[Err(b'\xFF')]), - // Incomplete UTF-8 - (b"\xC2", &[Err(b'\xC2')]), - (b"\xF4\x8F", &[Err(b'\xF4'), Err(b'\x8F')]), - (b"\xFF\xFF", &[Err(b'\xFF'), Err(b'\xFF')]), - (b"hello\xC2", &[Ok("hello"), Err(b'\xC2')]), - (b"\xFFhello", &[Err(b'\xFF'), Ok("hello")]), - (b"\xFF\xC2hello", &[Err(b'\xFF'), Err(b'\xC2'), Ok("hello")]), - (b"foo\xFFbar", &[Ok("foo"), Err(b'\xFF'), Ok("bar")]), - ( - b"foo\xF4\x8Fbar", - &[Ok("foo"), Err(b'\xF4'), Err(b'\x8F'), Ok("bar")], - ), - ( - b"foo\xFF\xC2bar", - &[Ok("foo"), Err(b'\xFF'), Err(b'\xC2'), Ok("bar")], - ), - ]; - for &(case, expected) in CASES { - assert_eq!( - from_utf8_iter(case).collect::>().as_slice(), - expected - ); - } - } -} From 0b86afa858be5e9149573b06a15b80ee129329c9 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 5 Oct 2021 20:59:51 -0400 Subject: [PATCH 04/12] seq: correct fixed-width spacing for inf sequences Pad infinity and negative infinity values with spaces when using the `-w` option to `seq`. This corrects the behavior of `seq` to match that of the GNU version: $ seq -w 1.000 inf inf | head -n 4 1.000 inf inf inf Previously, it incorrectly padded with 0s instead of spaces. --- src/uu/seq/src/seq.rs | 8 ++++++++ tests/by-util/test_seq.rs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index f28b4d6e8..75e9b1598 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -235,6 +235,14 @@ fn write_value_float( width = if width > 0 { width - 1 } else { width }, precision = precision, ) + } else if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity + { + format!( + "{value:>width$.precision$}", + value = value, + width = width, + precision = precision, + ) } else { format!( "{value:>0width$.precision$}", diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 2a2e31f83..312707753 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -530,6 +530,22 @@ fn test_inf() { run(&["inf"], b"1\n2\n3\n"); } +#[test] +fn test_inf_width() { + run( + &["-w", "1.000", "inf", "inf"], + b"1.000\n inf\n inf\n inf\n", + ); +} + +#[test] +fn test_neg_inf_width() { + run( + &["-w", "1.000", "-inf", "-inf"], + b"1.000\n -inf\n -inf\n -inf\n", + ); +} + #[test] fn test_ignore_leading_whitespace() { new_ucmd!() From fc300dda24faa4d659a3f8039a7beb14bb9615b1 Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 9 Nov 2021 00:09:18 -0300 Subject: [PATCH 05/12] tests/common: UCommand::new rename arg to bin_path Merge and remove unecessary `.as_ref()` --- tests/common/util.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index e71f18573..22c87a95c 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -838,12 +838,17 @@ pub struct UCommand { } impl UCommand { - pub fn new, U: AsRef>(arg: T, curdir: U, env_clear: bool) -> UCommand { + pub fn new, U: AsRef>( + bin_path: T, + curdir: U, + env_clear: bool, + ) -> UCommand { + let bin_path = bin_path.as_ref(); UCommand { tmpd: None, has_run: false, raw: { - let mut cmd = Command::new(arg.as_ref()); + let mut cmd = Command::new(bin_path); cmd.current_dir(curdir.as_ref()); if env_clear { if cfg!(windows) { @@ -863,7 +868,7 @@ impl UCommand { } cmd }, - comm_string: String::from(arg.as_ref().to_str().unwrap()), + comm_string: String::from(bin_path.to_str().unwrap()), ignore_stdin_write_error: false, bytes_into_stdin: None, stdin: None, @@ -874,9 +879,13 @@ impl UCommand { } } - pub fn new_from_tmp>(arg: T, tmpd: Rc, env_clear: bool) -> UCommand { + pub fn new_from_tmp>( + bin_path: T, + tmpd: Rc, + env_clear: bool, + ) -> UCommand { let tmpd_path_buf = String::from(&(*tmpd.as_ref().path().to_str().unwrap())); - let mut ucmd: UCommand = UCommand::new(arg.as_ref(), tmpd_path_buf, env_clear); + let mut ucmd: UCommand = UCommand::new(bin_path, tmpd_path_buf, env_clear); ucmd.tmpd = Some(tmpd); ucmd } From d4ca4371d7bf53cf3dfa6c60b3b9ef20c5599ee3 Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 9 Nov 2021 02:16:06 -0300 Subject: [PATCH 06/12] tests/common: add util_name+bin_path to UCommand --- tests/common/util.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 22c87a95c..fd2a00c4e 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -788,7 +788,7 @@ impl TestScenario { /// Returns builder for invoking any system command. Paths given are treated /// relative to the environment's unique temporary test directory. pub fn cmd>(&self, bin: S) -> UCommand { - UCommand::new_from_tmp(bin, self.tmpd.clone(), true) + UCommand::new_from_tmp::(bin, None, self.tmpd.clone(), true) } /// Returns builder for invoking any uutils command. Paths given are treated @@ -812,7 +812,7 @@ impl TestScenario { /// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call /// `Command::env_clear` (Clears the entire environment map for the child process.) pub fn cmd_keepenv>(&self, bin: S) -> UCommand { - UCommand::new_from_tmp(bin, self.tmpd.clone(), false) + UCommand::new_from_tmp::(bin, None, self.tmpd.clone(), false) } } @@ -826,6 +826,8 @@ impl TestScenario { pub struct UCommand { pub raw: Command, comm_string: String, + bin_path: String, + util_name: Option, tmpd: Option>, has_run: bool, ignore_stdin_write_error: bool, @@ -838,13 +840,16 @@ pub struct UCommand { } impl UCommand { - pub fn new, U: AsRef>( + pub fn new, S: AsRef, U: AsRef>( bin_path: T, + util_name: Option, curdir: U, env_clear: bool, ) -> UCommand { let bin_path = bin_path.as_ref(); - UCommand { + let util_name = util_name.as_ref().map(|un| un.as_ref()); + + let mut ucmd = UCommand { tmpd: None, has_run: false, raw: { @@ -869,6 +874,8 @@ impl UCommand { cmd }, comm_string: String::from(bin_path.to_str().unwrap()), + bin_path: bin_path.to_str().unwrap().to_string(), + util_name: util_name.map(|un| un.to_str().unwrap().to_string()), ignore_stdin_write_error: false, bytes_into_stdin: None, stdin: None, @@ -876,16 +883,23 @@ impl UCommand { stderr: None, #[cfg(target_os = "linux")] limits: vec![], + }; + + if let Some(un) = util_name { + ucmd.arg(un); } + + ucmd } - pub fn new_from_tmp>( + pub fn new_from_tmp, S: AsRef>( bin_path: T, + util_name: Option, tmpd: Rc, env_clear: bool, ) -> UCommand { let tmpd_path_buf = String::from(&(*tmpd.as_ref().path().to_str().unwrap())); - let mut ucmd: UCommand = UCommand::new(bin_path, tmpd_path_buf, env_clear); + let mut ucmd: UCommand = UCommand::new(bin_path, util_name, tmpd_path_buf, env_clear); ucmd.tmpd = Some(tmpd); ucmd } From ab4573bde9ee7af533a132c8817d0c28a52d8b3d Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 9 Nov 2021 04:20:43 -0300 Subject: [PATCH 07/12] tests/common: create TestScenario::composite_cmd This is made to call UCommand::new with Some(util_name) --- tests/common/util.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index fd2a00c4e..d649cd7ca 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -780,9 +780,18 @@ impl TestScenario { /// Returns builder for invoking the target uutils binary. Paths given are /// treated relative to the environment's unique temporary test directory. pub fn ucmd(&self) -> UCommand { - let mut cmd = self.cmd(&self.bin_path); - cmd.arg(&self.util_name); - cmd + self.composite_cmd(&self.bin_path, &self.util_name, true) + } + + /// Returns builder for invoking the target uutils binary. Paths given are + /// treated relative to the environment's unique temporary test directory. + pub fn composite_cmd, T: AsRef>( + &self, + bin: S, + util_name: T, + env_clear: bool, + ) -> UCommand { + UCommand::new_from_tmp(bin, Some(util_name), self.tmpd.clone(), env_clear) } /// Returns builder for invoking any system command. Paths given are treated @@ -794,17 +803,13 @@ impl TestScenario { /// Returns builder for invoking any uutils command. Paths given are treated /// relative to the environment's unique temporary test directory. pub fn ccmd>(&self, bin: S) -> UCommand { - let mut cmd = self.cmd(&self.bin_path); - cmd.arg(bin); - cmd + self.composite_cmd(&self.bin_path, bin, true) } // different names are used rather than an argument // because the need to keep the environment is exceedingly rare. pub fn ucmd_keepenv(&self) -> UCommand { - let mut cmd = self.cmd_keepenv(&self.bin_path); - cmd.arg(&self.util_name); - cmd + self.composite_cmd(&self.bin_path, &self.util_name, false) } /// Returns builder for invoking any system command. Paths given are treated From 0bbc805e43f99b2760bd9d3a0791808b015cc04b Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 9 Nov 2021 14:37:38 -0300 Subject: [PATCH 08/12] tests/common: add util_name+bin_path to CmdResult --- tests/common/util.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/common/util.rs b/tests/common/util.rs index d649cd7ca..d860e0c0b 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -62,6 +62,10 @@ fn read_scenario_fixture>(tmpd: &Option>, file_rel_p /// within a struct which has convenience assertion functions about those outputs #[derive(Debug, Clone)] pub struct CmdResult { + /// bin_path provided by `TestScenario` or `UCommand` + bin_path: String, + /// util_name provided by `TestScenario` or `UCommand` + util_name: Option, //tmpd is used for convenience functions for asserts against fixtures tmpd: Option>, /// exit status for command (if there is one) @@ -77,6 +81,8 @@ pub struct CmdResult { impl CmdResult { pub fn new( + bin_path: String, + util_name: Option, tmpd: Option>, code: Option, success: bool, @@ -84,6 +90,8 @@ impl CmdResult { stderr: &[u8], ) -> CmdResult { CmdResult { + bin_path, + util_name, tmpd, code, success, @@ -1049,6 +1057,8 @@ impl UCommand { let prog = self.run_no_wait().wait_with_output().unwrap(); CmdResult { + bin_path: self.bin_path.clone(), + util_name: self.util_name.clone(), tmpd: self.tmpd.clone(), code: prog.status.code(), success: prog.status.success(), @@ -1296,6 +1306,8 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< }; Ok(CmdResult::new( + ts.bin_path.as_os_str().to_str().unwrap().to_string(), + Some(ts.util_name.clone()), Some(result.tmpd()), Some(result.code()), result.succeeded(), @@ -1313,6 +1325,8 @@ mod tests { #[test] fn test_code_is() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: Some(32), success: false, @@ -1326,6 +1340,8 @@ mod tests { #[should_panic] fn test_code_is_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: Some(32), success: false, @@ -1338,6 +1354,8 @@ mod tests { #[test] fn test_failure() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: false, @@ -1351,6 +1369,8 @@ mod tests { #[should_panic] fn test_failure_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1363,6 +1383,8 @@ mod tests { #[test] fn test_success() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1376,6 +1398,8 @@ mod tests { #[should_panic] fn test_success_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: false, @@ -1388,6 +1412,8 @@ mod tests { #[test] fn test_no_stderr_output() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1402,6 +1428,8 @@ mod tests { #[should_panic] fn test_no_stderr_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1416,6 +1444,8 @@ mod tests { #[should_panic] fn test_no_stdout_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1429,6 +1459,8 @@ mod tests { #[test] fn test_std_does_not_contain() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1443,6 +1475,8 @@ mod tests { #[should_panic] fn test_stdout_does_not_contain_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1457,6 +1491,8 @@ mod tests { #[should_panic] fn test_stderr_does_not_contain_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1470,6 +1506,8 @@ mod tests { #[test] fn test_stdout_matches() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1486,6 +1524,8 @@ mod tests { #[should_panic] fn test_stdout_matches_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1501,6 +1541,8 @@ mod tests { #[should_panic] fn test_stdout_not_matches_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1515,6 +1557,8 @@ mod tests { #[test] fn test_normalized_newlines_stdout_is() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, @@ -1531,6 +1575,8 @@ mod tests { #[should_panic] fn test_normalized_newlines_stdout_is_fail() { let res = CmdResult { + bin_path: "".into(), + util_name: None, tmpd: None, code: None, success: true, From f43dfa9a61ece3cde6c434e2164b0046dcc7d047 Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 9 Nov 2021 14:43:55 -0300 Subject: [PATCH 09/12] tests/common: implement CmdResult::usage_error --- tests/common/util.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/common/util.rs b/tests/common/util.rs index d860e0c0b..cfde5f229 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -365,6 +365,23 @@ impl CmdResult { self } + /// asserts that + /// 1. the command resulted in stderr stream output that equals the + /// the following format when both are trimmed of trailing whitespace + /// `"{util_name}: {msg}\nTry '{bin_path} {util_name} --help' for more information."` + /// This the expected format when a UUsageError is returned or when show_error! is called + /// `msg` should be the same as the one provided to UUsageError::new or show_error! + /// + /// 2. the command resulted in empty (zero-length) stdout stream output + pub fn usage_error>(&self, msg: T) -> &CmdResult { + self.stderr_only(format!( + "{0}: {2}\nTry '{1} {0} --help' for more information.", + self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command + self.bin_path, + msg.as_ref() + )) + } + pub fn stdout_contains>(&self, cmp: T) -> &CmdResult { assert!( self.stdout_str().contains(cmp.as_ref()), From c9624725ab532f513d64392d38e5dcbab92e722e Mon Sep 17 00:00:00 2001 From: Thomas Queiroz Date: Tue, 9 Nov 2021 17:23:41 -0300 Subject: [PATCH 10/12] tests: use CmdResult::usage_error --- tests/by-util/test_base32.rs | 10 ++-------- tests/by-util/test_base64.rs | 10 ++-------- tests/by-util/test_basename.rs | 17 +++++------------ tests/by-util/test_cp.rs | 10 +++------- tests/by-util/test_more.rs | 13 ++++--------- tests/by-util/test_mv.rs | 10 +++------- tests/by-util/test_nice.rs | 13 ++++--------- tests/by-util/test_seq.rs | 28 +++++++++------------------- tests/by-util/test_stdbuf.rs | 10 ++-------- 9 files changed, 34 insertions(+), 87 deletions(-) diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index ffe2cf74c..4d244704d 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -113,18 +113,12 @@ fn test_wrap_bad_arg() { #[test] fn test_base32_extra_operand() { - let ts = TestScenario::new(util_name!()); - // Expect a failure when multiple files are specified. - ts.ucmd() + new_ucmd!() .arg("a.txt") .arg("b.txt") .fails() - .stderr_only(format!( - "{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("extra operand 'b.txt'"); } #[test] diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 87aa0db44..9a7d525bb 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -95,18 +95,12 @@ fn test_wrap_bad_arg() { #[test] fn test_base64_extra_operand() { - let ts = TestScenario::new(util_name!()); - // Expect a failure when multiple files are specified. - ts.ucmd() + new_ucmd!() .arg("a.txt") .arg("b.txt") .fails() - .stderr_only(format!( - "{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("extra operand 'b.txt'"); } #[test] diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 141745ac3..962d7373d 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -114,12 +114,7 @@ fn test_no_args() { #[test] fn test_no_args_output() { - let ts = TestScenario::new(util_name!()); - ts.ucmd().fails().stderr_is(&format!( - "{0}: missing operand\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!().fails().usage_error("missing operand"); } #[test] @@ -129,12 +124,10 @@ fn test_too_many_args() { #[test] fn test_too_many_args_output() { - let ts = TestScenario::new(util_name!()); - ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!( - "{0}: extra operand 'c'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .args(&["a", "b", "c"]) + .fails() + .usage_error("extra operand 'c'"); } #[cfg(any(unix, target_os = "redox"))] diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e86f35833..50abfe967 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -563,17 +563,13 @@ fn test_cp_backup_off() { #[test] fn test_cp_backup_no_clobber_conflicting_options() { - let ts = TestScenario::new(util_name!()); - ts.ucmd() + new_ucmd!() .arg("--backup") .arg("--no-clobber") .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) - .fails().stderr_is(&format!( - "{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .fails() + .usage_error("options --backup and --no-clobber are mutually exclusive"); } #[test] diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index 4b2719d8f..5c2b3c0f6 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -15,15 +15,10 @@ fn test_more_dir_arg() { // Maybe we could capture the error, i.e. "Device not found" in that case // but I am leaving this for later if atty::is(atty::Stream::Stdout) { - let ts = TestScenario::new(util_name!()); - let result = ts.ucmd().arg(".").run(); - result.failure(); - let expected_error_message = &format!( - "{0}: '.' is a directory.\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - ); - assert_eq!(result.stderr_str().trim(), expected_error_message); + new_ucmd!() + .arg(".") + .fails() + .usage_error("'.' is a directory."); } else { } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 8d9b00664..f6650cdba 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -522,17 +522,13 @@ fn test_mv_backup_off() { #[test] fn test_mv_backup_no_clobber_conflicting_options() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd().arg("--backup") + new_ucmd!() + .arg("--backup") .arg("--no-clobber") .arg("file1") .arg("file2") .fails() - .stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("options --backup and --no-clobber are mutually exclusive"); } #[test] diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index 7a99a333d..4a77ae24e 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -22,15 +22,10 @@ fn test_negative_adjustment() { #[test] fn test_adjustment_with_no_command_should_error() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd() - .args(&["-n", "19"]) - .run() - .stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry '{1} {0} --help' for more information.\n", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .args(&["-n", "19"]) + .fails() + .usage_error("A command must be given with an adjustment."); } #[test] diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 2a2e31f83..90166de92 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -66,24 +66,18 @@ fn test_hex_identifier_in_wrong_place() { #[test] fn test_rejects_nan() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd().args(&["NaN"]).fails().stderr_only(format!( - "{0}: invalid 'not-a-number' argument: 'NaN'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .arg("NaN") + .fails() + .usage_error("invalid 'not-a-number' argument: 'NaN'"); } #[test] fn test_rejects_non_floats() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd().args(&["foo"]).fails().stderr_only(&format!( - "{0}: invalid floating point argument: 'foo'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + new_ucmd!() + .arg("foo") + .fails() + .usage_error("invalid floating point argument: 'foo'"); } #[test] @@ -547,11 +541,7 @@ fn test_trailing_whitespace_error() { new_ucmd!() .arg("1 ") .fails() - .no_stdout() - .stderr_contains("seq: invalid floating point argument: '1 '") - // FIXME The second line of the error message is "Try 'seq - // --help' for more information." - .stderr_contains("for more information."); + .usage_error("invalid floating point argument: '1 '"); } #[test] diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index c05b65d70..3b03a1d4c 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -53,16 +53,10 @@ fn test_stdbuf_trailing_var_arg() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_line_buffering_stdin_fails() { - let ts = TestScenario::new(util_name!()); - - ts.ucmd() + new_ucmd!() .args(&["-i", "L", "head"]) .fails() - .stderr_is(&format!( - "{0}: line buffering stdin is meaningless\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + .usage_error("line buffering stdin is meaningless"); } #[cfg(not(target_os = "windows"))] From 32b0178a720286ae64d64fcdd2ced51d69126067 Mon Sep 17 00:00:00 2001 From: nicoo Date: Tue, 16 Mar 2021 21:27:24 +0100 Subject: [PATCH 11/12] factor: Update to current versions of `smallvec` smallvec 1.0 and later wasn't compatible with Rust 1.33 but the minimum supported Rust version for coreutils moved on. --- Cargo.lock | 231 +++++++++++++++++++-------------------- src/uu/factor/Cargo.toml | 4 +- 2 files changed, 116 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bd0e776a..cc33d49e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ - "memchr 2.4.0", + "memchr 2.4.1", ] [[package]] @@ -112,7 +112,7 @@ dependencies = [ "log", "peeking_take_while", "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "regex", "rustc-hash", "shlex", @@ -175,12 +175,12 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", - "memchr 2.4.0", + "memchr 2.4.1", "regex-automata", ] @@ -192,9 +192,9 @@ checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" [[package]] name = "byte-unit" -version = "4.0.12" +version = "4.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8" +checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f" dependencies = [ "utf8-width", ] @@ -213,9 +213,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.69" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cexpr" @@ -253,9 +253,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" dependencies = [ "glob", "libc", @@ -505,7 +505,7 @@ dependencies = [ "if_rust_version", "lazy_static", "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] @@ -586,9 +586,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.20.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" dependencies = [ "bitflags", "crossterm_winapi", @@ -602,30 +602,30 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi 0.3.9", ] [[package]] name = "ctor" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" dependencies = [ - "quote 1.0.9", + "quote 1.0.10", "syn", ] [[package]] name = "ctrlc" -version = "3.1.9" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" dependencies = [ - "nix 0.20.0", + "nix 0.23.0", "winapi 0.3.9", ] @@ -668,7 +668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] @@ -698,9 +698,9 @@ dependencies = [ [[package]] name = "dns-lookup" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093d88961fd18c4ecacb8c80cd0b356463ba941ba11e0e01f9cf5271380b79dc" +checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872" dependencies = [ "cfg-if 1.0.0", "libc", @@ -818,9 +818,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "gcd" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7cd301bf2ab11ae4e5bdfd79c221d97a25e46c089144a62ee9d09cb32d2b92" +checksum = "6c8763772808ee8fe3128f0fc424bed6d9942293fddbcfd595ecfa58a81fe00b" [[package]] name = "generic-array" @@ -884,9 +884,9 @@ dependencies = [ [[package]] name = "half" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" @@ -946,9 +946,9 @@ checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" [[package]] name = "instant" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", ] @@ -1001,15 +1001,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -1017,9 +1017,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] @@ -1048,12 +1048,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "md5" version = "0.3.8" @@ -1071,9 +1065,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" @@ -1095,9 +1089,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.7" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", @@ -1152,6 +1146,19 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -1166,7 +1173,7 @@ checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "bitvec", "funty", - "memchr 2.4.0", + "memchr 2.4.1", "version_check", ] @@ -1181,9 +1188,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", @@ -1237,7 +1244,7 @@ checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" dependencies = [ "proc-macro-crate", "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] @@ -1311,7 +1318,7 @@ dependencies = [ "Inflector", "proc-macro-error", "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] @@ -1326,9 +1333,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -1337,15 +1344,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", "instant", "libc", "redox_syscall", - "smallvec 1.6.1", + "smallvec", "winapi 0.3.9", ] @@ -1376,9 +1383,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" [[package]] name = "platform-info" @@ -1392,9 +1399,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "pretty_assertions" @@ -1410,9 +1417,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" dependencies = [ "thiserror", "toml", @@ -1426,7 +1433,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", "version_check", ] @@ -1438,7 +1445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "version_check", ] @@ -1450,9 +1457,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid 0.2.2", ] @@ -1489,9 +1496,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -1677,7 +1684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", - "memchr 2.4.0", + "memchr 2.4.1", "regex-syntax", ] @@ -1704,9 +1711,9 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" +checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9" [[package]] name = "rlimit" @@ -1751,9 +1758,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "selinux" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608" +checksum = "09715d6b4356e916047e61e4dce40a67ac93036851957b91713d3d9c282d1548" dependencies = [ "bitflags", "libc", @@ -1791,7 +1798,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] @@ -1828,15 +1835,15 @@ dependencies = [ [[package]] name = "shlex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" dependencies = [ "libc", "signal-hook-registry", @@ -1864,18 +1871,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "0.6.14" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smawk" @@ -1885,11 +1883,10 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "socket2" -version = "0.3.19" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ - "cfg-if 1.0.0", "libc", "winapi 0.3.9", ] @@ -1920,18 +1917,18 @@ checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" dependencies = [ "heck", "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] [[package]] name = "syn" -version = "1.0.74" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "unicode-xid 0.2.2", ] @@ -2042,21 +2039,21 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] @@ -2081,9 +2078,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "unicode-linebreak" @@ -2102,9 +2099,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -2325,7 +2322,7 @@ dependencies = [ "atty", "bstr", "clap", - "memchr 2.4.0", + "memchr 2.4.1", "uucore", "uucore_procs", ] @@ -2450,7 +2447,7 @@ dependencies = [ "paste", "quickcheck", "rand 0.7.3", - "smallvec 0.6.14", + "smallvec", "uucore", "uucore_procs", ] @@ -2503,7 +2500,7 @@ dependencies = [ "hex", "libc", "md5", - "memchr 2.4.0", + "memchr 2.4.1", "regex", "regex-syntax", "sha1", @@ -2518,7 +2515,7 @@ name = "uu_head" version = "0.0.8" dependencies = [ "clap", - "memchr 2.4.0", + "memchr 2.4.1", "uucore", "uucore_procs", ] @@ -2722,7 +2719,7 @@ dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.4.0", + "memchr 2.4.1", "regex", "regex-syntax", "uucore", @@ -2840,7 +2837,7 @@ dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.4.0", + "memchr 2.4.1", "regex", "regex-syntax", "uucore", @@ -2971,7 +2968,7 @@ dependencies = [ "ctrlc", "fnv", "itertools 0.10.1", - "memchr 2.4.0", + "memchr 2.4.1", "ouroboros", "rand 0.7.3", "rayon", @@ -3046,7 +3043,7 @@ name = "uu_tac" version = "0.0.8" dependencies = [ "clap", - "memchr 2.4.0", + "memchr 2.4.1", "memmap2", "regex", "uucore", @@ -3290,7 +3287,7 @@ name = "uucore_procs" version = "0.0.7" dependencies = [ "proc-macro2", - "quote 1.0.9", + "quote 1.0.10", "syn", ] @@ -3413,6 +3410,6 @@ dependencies = [ [[package]] name = "z85" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781" +checksum = "af896e93db81340b74b65f74276a99b210c086f3d34ed0abf433182a462af856" diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 2d2fa236b..1142560d9 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -15,13 +15,13 @@ edition = "2018" num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs [dependencies] +clap = { version = "2.33", features = ["wrap_help"] } coz = { version = "0.1.3", optional = true } num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" rand = { version = "0.7", features = ["small_rng"] } -smallvec = { version = "0.6.14, < 1.0" } +smallvec = "1.7" uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" } -clap = { version = "2.33", features = ["wrap_help"] } [dev-dependencies] paste = "0.1.18" From bed45602a71e02b2447ed4374c75c38742697599 Mon Sep 17 00:00:00 2001 From: nicoo Date: Wed, 10 Nov 2021 15:26:36 +0100 Subject: [PATCH 12/12] factor/Cargo.toml: Document feature pending a MinRustV bump --- src/uu/factor/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 1142560d9..f79afeecf 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -19,7 +19,7 @@ clap = { version = "2.33", features = ["wrap_help"] } coz = { version = "0.1.3", optional = true } num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" rand = { version = "0.7", features = ["small_rng"] } -smallvec = "1.7" +smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later. uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" }