mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Merge pull request #5128 from tertsdiepraam/printf-rewrite
`printf` rewrite (with a lot of `seq` changes)
This commit is contained in:
@@ -18,7 +18,7 @@ path = "src/dd.rs"
|
||||
clap = { workspace = true }
|
||||
gcd = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
uucore = { workspace = true, features = ["memo", "quoting-style"] }
|
||||
uucore = { workspace = true, features = ["format", "quoting-style"] }
|
||||
|
||||
[target.'cfg(any(target_os = "linux"))'.dependencies]
|
||||
nix = { workspace = true, features = ["fs"] }
|
||||
|
||||
@@ -13,8 +13,10 @@ use std::io::Write;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use uucore::error::UResult;
|
||||
use uucore::memo::sprintf;
|
||||
use uucore::{
|
||||
error::UResult,
|
||||
format::num_format::{FloatVariant, Formatter},
|
||||
};
|
||||
|
||||
use crate::numbers::{to_magnitude_and_suffix, SuffixType};
|
||||
|
||||
@@ -152,7 +154,14 @@ impl ProgUpdate {
|
||||
let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") };
|
||||
|
||||
// The duration should be formatted as in `printf %g`.
|
||||
let duration_str = sprintf("%g", &[duration.to_string()])?;
|
||||
let mut duration_str = Vec::new();
|
||||
uucore::format::num_format::Float {
|
||||
variant: FloatVariant::Shortest,
|
||||
..Default::default()
|
||||
}
|
||||
.fmt(&mut duration_str, duration)?;
|
||||
// We assume that printf will output valid UTF-8
|
||||
let duration_str = std::str::from_utf8(&duration_str).unwrap();
|
||||
|
||||
// If the number of bytes written is sufficiently large, then
|
||||
// print a more concise representation of the number, like
|
||||
|
||||
@@ -16,7 +16,7 @@ path = "src/printf.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
uucore = { workspace = true, features = ["memo", "quoting-style"] }
|
||||
uucore = { workspace = true, features = ["format", "quoting-style"] }
|
||||
|
||||
[[bin]]
|
||||
name = "printf"
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
// spell-checker:ignore (change!) each's
|
||||
// spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr
|
||||
|
||||
use std::io::stdout;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use uucore::error::{UResult, UUsageError};
|
||||
use uucore::memo::printf;
|
||||
use uucore::format::{parse_spec_and_escape, FormatArgument};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage};
|
||||
|
||||
const VERSION: &str = "version";
|
||||
@@ -30,12 +33,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let format_string = matches
|
||||
.get_one::<String>(options::FORMATSTRING)
|
||||
.ok_or_else(|| UUsageError::new(1, "missing operand"))?;
|
||||
let values: Vec<String> = match matches.get_many::<String>(options::ARGUMENT) {
|
||||
Some(s) => s.map(|s| s.to_string()).collect(),
|
||||
|
||||
let values: Vec<_> = match matches.get_many::<String>(options::ARGUMENT) {
|
||||
Some(s) => s.map(|s| FormatArgument::Unparsed(s.to_string())).collect(),
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
printf(format_string, &values[..])?;
|
||||
let mut args = values.iter().peekable();
|
||||
for item in parse_spec_and_escape(format_string.as_ref()) {
|
||||
match item?.write(stdout(), &mut args)? {
|
||||
ControlFlow::Continue(()) => {}
|
||||
ControlFlow::Break(()) => return Ok(()),
|
||||
};
|
||||
}
|
||||
|
||||
while args.peek().is_some() {
|
||||
for item in parse_spec_and_escape(format_string.as_ref()) {
|
||||
match item?.write(stdout(), &mut args)? {
|
||||
ControlFlow::Continue(()) => {}
|
||||
ControlFlow::Break(()) => return Ok(()),
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ bigdecimal = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
num-bigint = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
uucore = { workspace = true, features = ["memo", "quoting-style"] }
|
||||
uucore = { workspace = true, features = ["format", "quoting-style"] }
|
||||
|
||||
[[bin]]
|
||||
name = "seq"
|
||||
|
||||
@@ -25,13 +25,8 @@ 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.
|
||||
@@ -72,53 +67,14 @@ pub enum ExtendedBigDecimal {
|
||||
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 {
|
||||
Self::BigDecimal(x) => ExtendedBigInt::BigInt(ceil(x)),
|
||||
other => From::from(other),
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub fn zero() -> Self {
|
||||
Self::BigDecimal(0.into())
|
||||
}
|
||||
|
||||
/// The largest integer less than or equal to this number.
|
||||
pub fn floor(self) -> ExtendedBigInt {
|
||||
match self {
|
||||
Self::BigDecimal(x) => ExtendedBigInt::BigInt(floor(x)),
|
||||
other => From::from(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtendedBigInt> for ExtendedBigDecimal {
|
||||
fn from(big_int: ExtendedBigInt) -> Self {
|
||||
match big_int {
|
||||
ExtendedBigInt::BigInt(n) => Self::BigDecimal(BigDecimal::from(n)),
|
||||
ExtendedBigInt::Infinity => Self::Infinity,
|
||||
ExtendedBigInt::MinusInfinity => Self::MinusInfinity,
|
||||
ExtendedBigInt::MinusZero => Self::MinusZero,
|
||||
ExtendedBigInt::Nan => Self::Nan,
|
||||
}
|
||||
pub fn one() -> Self {
|
||||
Self::BigDecimal(1.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// 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.
|
||||
Self::BigInt(BigInt::one())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtendedBigDecimal> 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 => Self::Infinity,
|
||||
ExtendedBigDecimal::MinusInfinity => Self::MinusInfinity,
|
||||
ExtendedBigDecimal::MinusZero => Self::MinusZero,
|
||||
ExtendedBigDecimal::Nan => Self::Nan,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExtendedBigInt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::BigInt(n) => n.fmt(f),
|
||||
Self::Infinity => f32::INFINITY.fmt(f),
|
||||
Self::MinusInfinity => f32::NEG_INFINITY.fmt(f),
|
||||
Self::MinusZero => "-0".fmt(f),
|
||||
Self::Nan => "nan".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Zero for ExtendedBigInt {
|
||||
fn zero() -> Self {
|
||||
Self::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<Ordering> {
|
||||
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::MinusZero), "-0");
|
||||
assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf");
|
||||
assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf");
|
||||
assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan");
|
||||
}
|
||||
}
|
||||
@@ -2,80 +2,10 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// 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 [`str::parse`].
|
||||
// spell-checker:ignore extendedbigdecimal
|
||||
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 {
|
||||
Self::Int(n) => n.is_zero(),
|
||||
Self::Float(x) => x.is_zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this number into an `ExtendedBigDecimal`.
|
||||
pub fn into_extended_big_decimal(self) -> ExtendedBigDecimal {
|
||||
match self {
|
||||
Self::Int(n) => ExtendedBigDecimal::from(n),
|
||||
Self::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.
|
||||
Self::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`.
|
||||
Self::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.
|
||||
Self::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.
|
||||
///
|
||||
@@ -87,13 +17,17 @@ impl Number {
|
||||
/// You can get an instance of this struct by calling [`str::parse`].
|
||||
#[derive(Debug)]
|
||||
pub struct PreciseNumber {
|
||||
pub number: Number,
|
||||
pub number: ExtendedBigDecimal,
|
||||
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) -> Self {
|
||||
pub fn new(
|
||||
number: ExtendedBigDecimal,
|
||||
num_integral_digits: usize,
|
||||
num_fractional_digits: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
number,
|
||||
num_integral_digits,
|
||||
@@ -106,7 +40,7 @@ impl PreciseNumber {
|
||||
// We would like to implement `num_traits::One`, but it requires
|
||||
// a multiplication implementation, and we don't want to
|
||||
// implement that here.
|
||||
Self::new(Number::one(), 1, 0)
|
||||
Self::new(ExtendedBigDecimal::one(), 1, 0)
|
||||
}
|
||||
|
||||
/// Decide whether this number is zero (either positive or negative).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore extendedbigdecimal extendedbigint bigdecimal numberparse
|
||||
// spell-checker:ignore extendedbigdecimal bigdecimal numberparse
|
||||
//! Parsing numbers for use in `seq`.
|
||||
//!
|
||||
//! This module provides an implementation of [`FromStr`] for the
|
||||
@@ -16,8 +16,6 @@ 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.
|
||||
@@ -29,8 +27,8 @@ pub enum ParseNumberError {
|
||||
}
|
||||
|
||||
/// 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()
|
||||
fn is_minus_zero_int(s: &str, n: &BigDecimal) -> bool {
|
||||
s.starts_with('-') && n == &BigDecimal::zero()
|
||||
}
|
||||
|
||||
/// Decide whether a given string and its parsed `BigDecimal` is negative zero.
|
||||
@@ -53,19 +51,19 @@ fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool {
|
||||
/// assert_eq!(actual, expected);
|
||||
/// ```
|
||||
fn parse_no_decimal_no_exponent(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
||||
match s.parse::<BigInt>() {
|
||||
match s.parse::<BigDecimal>() {
|
||||
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),
|
||||
ExtendedBigDecimal::MinusZero,
|
||||
s.len(),
|
||||
0,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(n)),
|
||||
ExtendedBigDecimal::BigDecimal(n),
|
||||
s.len(),
|
||||
0,
|
||||
))
|
||||
@@ -79,7 +77,7 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result<PreciseNumber, ParseNumberErr
|
||||
"nan" | "-nan" => return Err(ParseNumberError::Nan),
|
||||
_ => return Err(ParseNumberError::Float),
|
||||
};
|
||||
Ok(PreciseNumber::new(Number::Float(float_val), 0, 0))
|
||||
Ok(PreciseNumber::new(float_val, 0, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,13 +123,13 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result<PreciseNumber, ParseNu
|
||||
if exponent < 0 {
|
||||
if is_minus_zero_float(s, &x) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::MinusZero),
|
||||
ExtendedBigDecimal::MinusZero,
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
|
||||
ExtendedBigDecimal::BigDecimal(x),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
@@ -169,13 +167,13 @@ fn parse_decimal_no_exponent(s: &str, i: usize) -> Result<PreciseNumber, ParseNu
|
||||
let num_fractional_digits = s.len() - (i + 1);
|
||||
if is_minus_zero_float(s, &x) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::MinusZero),
|
||||
ExtendedBigDecimal::MinusZero,
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
|
||||
ExtendedBigDecimal::BigDecimal(x),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
@@ -239,7 +237,7 @@ fn parse_decimal_and_exponent(
|
||||
if num_digits_between_decimal_point_and_e <= exponent {
|
||||
if is_minus_zero_float(s, &val) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::MinusZero),
|
||||
ExtendedBigDecimal::MinusZero,
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
@@ -251,23 +249,23 @@ fn parse_decimal_and_exponent(
|
||||
);
|
||||
let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat();
|
||||
let n = expanded
|
||||
.parse::<BigInt>()
|
||||
.parse::<BigDecimal>()
|
||||
.map_err(|_| ParseNumberError::Float)?;
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(n)),
|
||||
ExtendedBigDecimal::BigDecimal(n),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
}
|
||||
} else if is_minus_zero_float(s, &val) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::MinusZero),
|
||||
ExtendedBigDecimal::MinusZero,
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(val)),
|
||||
ExtendedBigDecimal::BigDecimal(val),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
@@ -303,20 +301,17 @@ fn parse_hexadecimal(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
||||
}
|
||||
|
||||
let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?;
|
||||
let num = BigDecimal::from(num);
|
||||
|
||||
match (is_neg, num == BigInt::zero()) {
|
||||
(true, true) => Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::MinusZero),
|
||||
2,
|
||||
0,
|
||||
)),
|
||||
match (is_neg, num == BigDecimal::zero()) {
|
||||
(true, true) => Ok(PreciseNumber::new(ExtendedBigDecimal::MinusZero, 2, 0)),
|
||||
(true, false) => Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(-num)),
|
||||
ExtendedBigDecimal::BigDecimal(-num),
|
||||
0,
|
||||
0,
|
||||
)),
|
||||
(false, _) => Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(num)),
|
||||
ExtendedBigDecimal::BigDecimal(num),
|
||||
0,
|
||||
0,
|
||||
)),
|
||||
@@ -364,19 +359,14 @@ impl FromStr for PreciseNumber {
|
||||
|
||||
#[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 {
|
||||
fn parse(s: &str) -> ExtendedBigDecimal {
|
||||
s.parse::<PreciseNumber>().unwrap().number
|
||||
}
|
||||
|
||||
@@ -392,40 +382,37 @@ mod tests {
|
||||
|
||||
#[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));
|
||||
assert_eq!(parse("-0e0"), ExtendedBigDecimal::MinusZero);
|
||||
assert_eq!(parse("-0e-0"), ExtendedBigDecimal::MinusZero);
|
||||
assert_eq!(parse("-0e1"), ExtendedBigDecimal::MinusZero);
|
||||
assert_eq!(parse("-0e+1"), ExtendedBigDecimal::MinusZero);
|
||||
assert_eq!(parse("-0.0e1"), ExtendedBigDecimal::MinusZero);
|
||||
assert_eq!(parse("-0x0"), ExtendedBigDecimal::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)
|
||||
);
|
||||
assert_eq!(parse("-0.0"), ExtendedBigDecimal::MinusZero);
|
||||
assert_eq!(parse("-0e-1"), ExtendedBigDecimal::MinusZero);
|
||||
assert_eq!(parse("-0.0e-1"), 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("0"), ExtendedBigDecimal::zero());
|
||||
assert_eq!(parse("0.1e1"), ExtendedBigDecimal::one());
|
||||
assert_eq!(
|
||||
parse("1.0e1"),
|
||||
Number::Int(ExtendedBigInt::BigInt("10".parse::<BigInt>().unwrap()))
|
||||
ExtendedBigDecimal::BigDecimal("10".parse::<BigDecimal>().unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_hexadecimal_big_int() {
|
||||
assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero()));
|
||||
assert_eq!(parse("0x0"), ExtendedBigDecimal::zero());
|
||||
assert_eq!(
|
||||
parse("0x10"),
|
||||
Number::Int(ExtendedBigInt::BigInt("16".parse::<BigInt>().unwrap()))
|
||||
ExtendedBigDecimal::BigDecimal("16".parse::<BigDecimal>().unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -433,56 +420,34 @@ mod tests {
|
||||
fn test_parse_big_decimal() {
|
||||
assert_eq!(
|
||||
parse("0.0"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"0.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
ExtendedBigDecimal::BigDecimal("0.0".parse::<BigDecimal>().unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parse(".0"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"0.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
ExtendedBigDecimal::BigDecimal("0.0".parse::<BigDecimal>().unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parse("1.0"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"1.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
ExtendedBigDecimal::BigDecimal("1.0".parse::<BigDecimal>().unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parse("10e-1"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"1.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
ExtendedBigDecimal::BigDecimal("1.0".parse::<BigDecimal>().unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
parse("-1e-3"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"-0.001".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
ExtendedBigDecimal::BigDecimal("-0.001".parse::<BigDecimal>().unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_inf() {
|
||||
assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity));
|
||||
assert_eq!(
|
||||
parse("infinity"),
|
||||
Number::Float(ExtendedBigDecimal::Infinity)
|
||||
);
|
||||
assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity));
|
||||
assert_eq!(
|
||||
parse("+infinity"),
|
||||
Number::Float(ExtendedBigDecimal::Infinity)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("-inf"),
|
||||
Number::Float(ExtendedBigDecimal::MinusInfinity)
|
||||
);
|
||||
assert_eq!(
|
||||
parse("-infinity"),
|
||||
Number::Float(ExtendedBigDecimal::MinusInfinity)
|
||||
);
|
||||
assert_eq!(parse("inf"), ExtendedBigDecimal::Infinity);
|
||||
assert_eq!(parse("infinity"), ExtendedBigDecimal::Infinity);
|
||||
assert_eq!(parse("+inf"), ExtendedBigDecimal::Infinity);
|
||||
assert_eq!(parse("+infinity"), ExtendedBigDecimal::Infinity);
|
||||
assert_eq!(parse("-inf"), ExtendedBigDecimal::MinusInfinity);
|
||||
assert_eq!(parse("-infinity"), ExtendedBigDecimal::MinusInfinity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+33
-128
@@ -2,28 +2,22 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse
|
||||
// spell-checker:ignore (ToDO) extendedbigdecimal numberparse
|
||||
use std::io::{stdout, ErrorKind, Write};
|
||||
use std::process::exit;
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use num_traits::Zero;
|
||||
use num_traits::{ToPrimitive, Zero};
|
||||
|
||||
use uucore::error::FromIo;
|
||||
use uucore::error::UResult;
|
||||
use uucore::memo::printf;
|
||||
use uucore::show;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::format::{num_format, Format};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
mod error;
|
||||
mod extendedbigdecimal;
|
||||
mod extendedbigint;
|
||||
mod number;
|
||||
mod numberparse;
|
||||
use crate::error::SeqError;
|
||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||
use crate::extendedbigint::ExtendedBigInt;
|
||||
use crate::number::Number;
|
||||
use crate::number::PreciseNumber;
|
||||
|
||||
const ABOUT: &str = help_about!("seq.md");
|
||||
@@ -44,11 +38,6 @@ struct SeqOptions<'a> {
|
||||
format: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// A range of integers.
|
||||
///
|
||||
/// The elements are (first, increment, last).
|
||||
type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt);
|
||||
|
||||
/// A range of floats.
|
||||
///
|
||||
/// The elements are (first, increment, last).
|
||||
@@ -119,32 +108,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
.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(
|
||||
(first, increment, last),
|
||||
&options.separator,
|
||||
&options.terminator,
|
||||
options.equal_width,
|
||||
padding,
|
||||
options.format,
|
||||
)
|
||||
let format = match options.format {
|
||||
Some(f) => {
|
||||
let f = Format::<num_format::Float>::parse(f)?;
|
||||
Some(f)
|
||||
}
|
||||
(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,
|
||||
options.equal_width,
|
||||
padding,
|
||||
options.format,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let result = print_seq(
|
||||
(first.number, increment.number, last.number),
|
||||
largest_dec,
|
||||
&options.separator,
|
||||
&options.terminator,
|
||||
options.equal_width,
|
||||
padding,
|
||||
&format,
|
||||
);
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()),
|
||||
@@ -216,28 +195,6 @@ fn write_value_float(
|
||||
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,
|
||||
) -> std::io::Result<()> {
|
||||
let value_as_str = if pad {
|
||||
if *value == ExtendedBigInt::MinusZero {
|
||||
format!("{value:0<width$}")
|
||||
} else {
|
||||
format!("{value:>0width$}")
|
||||
}
|
||||
} 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: RangeFloat,
|
||||
@@ -246,13 +203,17 @@ fn print_seq(
|
||||
terminator: &str,
|
||||
pad: bool,
|
||||
padding: usize,
|
||||
format: Option<&str>,
|
||||
format: &Option<Format<num_format::Float>>,
|
||||
) -> std::io::Result<()> {
|
||||
let stdout = stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let (first, increment, last) = range;
|
||||
let mut value = first;
|
||||
let padding = if pad { padding + 1 + largest_dec } else { 0 };
|
||||
let padding = if pad {
|
||||
padding + if largest_dec > 0 { largest_dec + 1 } else { 0 }
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let mut is_first_iteration = true;
|
||||
while !done_printing(&value, &increment, &last) {
|
||||
if !is_first_iteration {
|
||||
@@ -270,13 +231,16 @@ fn print_seq(
|
||||
// it as a string and ultimately writing to `stdout`. We
|
||||
// shouldn't have to do so much converting back and forth via
|
||||
// strings.
|
||||
match format {
|
||||
match &format {
|
||||
Some(f) => {
|
||||
let s = format!("{value}");
|
||||
if let Err(x) = printf(f, &[s]) {
|
||||
show!(x);
|
||||
exit(1);
|
||||
}
|
||||
let float = match &value {
|
||||
ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(),
|
||||
ExtendedBigDecimal::Infinity => f64::INFINITY,
|
||||
ExtendedBigDecimal::MinusInfinity => f64::NEG_INFINITY,
|
||||
ExtendedBigDecimal::MinusZero => -0.0,
|
||||
ExtendedBigDecimal::Nan => f64::NAN,
|
||||
};
|
||||
f.fmt(&mut stdout, float)?;
|
||||
}
|
||||
None => write_value_float(&mut stdout, &value, padding, largest_dec)?,
|
||||
}
|
||||
@@ -290,62 +254,3 @@ fn print_seq(
|
||||
stdout.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Print an integer sequence.
|
||||
///
|
||||
/// This function prints a sequence of integers defined by `range`,
|
||||
/// which defines the first integer, last integer, and increment of the
|
||||
/// range. The `separator` is inserted between each integer and
|
||||
/// `terminator` is inserted at the end.
|
||||
///
|
||||
/// The `pad` parameter indicates whether to pad numbers to the width
|
||||
/// given in `padding`.
|
||||
///
|
||||
/// If `is_first_minus_zero` is `true`, then the `first` parameter is
|
||||
/// printed as if it were negative zero, even though no such number
|
||||
/// exists as an integer (negative zero only exists for floating point
|
||||
/// numbers). Only set this to `true` if `first` is actually zero.
|
||||
fn print_seq_integers(
|
||||
range: RangeInt,
|
||||
separator: &str,
|
||||
terminator: &str,
|
||||
pad: bool,
|
||||
padding: usize,
|
||||
format: Option<&str>,
|
||||
) -> std::io::Result<()> {
|
||||
let stdout = stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let (first, increment, last) = range;
|
||||
let mut value = first;
|
||||
let mut is_first_iteration = true;
|
||||
while !done_printing(&value, &increment, &last) {
|
||||
if !is_first_iteration {
|
||||
write!(stdout, "{separator}")?;
|
||||
}
|
||||
// If there was an argument `-f FORMAT`, then use that format
|
||||
// template instead of the default formatting strategy.
|
||||
//
|
||||
// The `printf()` function takes in the template and
|
||||
// the current value and writes the result to `stdout`.
|
||||
//
|
||||
// TODO See similar comment about formatting in `print_seq()`.
|
||||
match format {
|
||||
Some(f) => {
|
||||
let s = format!("{value}");
|
||||
if let Err(x) = printf(f, &[s]) {
|
||||
show!(x);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
None => write_value_int(&mut stdout, &value, padding, pad)?,
|
||||
}
|
||||
// TODO Implement augmenting addition.
|
||||
value = value + increment.clone();
|
||||
is_first_iteration = false;
|
||||
}
|
||||
|
||||
if !is_first_iteration {
|
||||
write!(stdout, "{terminator}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ entries = ["libc"]
|
||||
fs = ["dunce", "libc", "winapi-util", "windows-sys"]
|
||||
fsext = ["libc", "time", "windows-sys"]
|
||||
lines = []
|
||||
memo = ["itertools"]
|
||||
format = ["itertools"]
|
||||
mode = ["libc"]
|
||||
perms = ["libc", "walkdir"]
|
||||
pipes = []
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
pub mod backup_control;
|
||||
#[cfg(feature = "encoding")]
|
||||
pub mod encoding;
|
||||
#[cfg(feature = "format")]
|
||||
pub mod format;
|
||||
#[cfg(feature = "fs")]
|
||||
pub mod fs;
|
||||
#[cfg(feature = "fsext")]
|
||||
pub mod fsext;
|
||||
#[cfg(feature = "lines")]
|
||||
pub mod lines;
|
||||
#[cfg(feature = "memo")]
|
||||
pub mod memo;
|
||||
#[cfg(feature = "quoting-style")]
|
||||
pub mod quoting_style;
|
||||
#[cfg(feature = "ranges")]
|
||||
@@ -24,8 +24,6 @@ pub mod ranges;
|
||||
pub mod ringbuffer;
|
||||
#[cfg(feature = "sum")]
|
||||
pub mod sum;
|
||||
#[cfg(feature = "memo")]
|
||||
mod tokenize;
|
||||
#[cfg(feature = "update-control")]
|
||||
pub mod update_control;
|
||||
#[cfg(feature = "version-cmp")]
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use os_display::Quotable;
|
||||
|
||||
use crate::{error::set_exit_code, show_warning};
|
||||
|
||||
/// An argument for formatting
|
||||
///
|
||||
/// Each of these variants is only accepted by their respective directives. For
|
||||
/// example, [`FormatArgument::Char`] requires a `%c` directive.
|
||||
///
|
||||
/// The [`FormatArgument::Unparsed`] variant contains a string that can be
|
||||
/// parsed into other types. This is used by the `printf` utility.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FormatArgument {
|
||||
Char(char),
|
||||
String(String),
|
||||
UnsignedInt(u64),
|
||||
SignedInt(i64),
|
||||
Float(f64),
|
||||
/// Special argument that gets coerced into the other variants
|
||||
Unparsed(String),
|
||||
}
|
||||
|
||||
pub trait ArgumentIter<'a>: Iterator<Item = &'a FormatArgument> {
|
||||
fn get_char(&mut self) -> char;
|
||||
fn get_i64(&mut self) -> i64;
|
||||
fn get_u64(&mut self) -> u64;
|
||||
fn get_f64(&mut self) -> f64;
|
||||
fn get_str(&mut self) -> &'a str;
|
||||
}
|
||||
|
||||
impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||
fn get_char(&mut self) -> char {
|
||||
let Some(next) = self.next() else {
|
||||
return '\0';
|
||||
};
|
||||
match next {
|
||||
FormatArgument::Char(c) => *c,
|
||||
FormatArgument::Unparsed(s) => {
|
||||
let mut chars = s.chars();
|
||||
let Some(c) = chars.next() else {
|
||||
return '\0';
|
||||
};
|
||||
let None = chars.next() else {
|
||||
return '\0';
|
||||
};
|
||||
c
|
||||
}
|
||||
_ => '\0',
|
||||
}
|
||||
}
|
||||
|
||||
fn get_u64(&mut self) -> u64 {
|
||||
let Some(next) = self.next() else {
|
||||
return 0;
|
||||
};
|
||||
match next {
|
||||
FormatArgument::UnsignedInt(n) => *n,
|
||||
FormatArgument::Unparsed(s) => {
|
||||
let opt = if let Some(s) = s.strip_prefix("0x") {
|
||||
u64::from_str_radix(s, 16).ok()
|
||||
} else if let Some(s) = s.strip_prefix('0') {
|
||||
u64::from_str_radix(s, 8).ok()
|
||||
} else if let Some(s) = s.strip_prefix('\'') {
|
||||
s.chars().next().map(|c| c as u64)
|
||||
} else {
|
||||
s.parse().ok()
|
||||
};
|
||||
match opt {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
show_warning!("{}: expected a numeric value", s.quote());
|
||||
set_exit_code(1);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_i64(&mut self) -> i64 {
|
||||
let Some(next) = self.next() else {
|
||||
return 0;
|
||||
};
|
||||
match next {
|
||||
FormatArgument::SignedInt(n) => *n,
|
||||
FormatArgument::Unparsed(s) => {
|
||||
// For hex, we parse `u64` because we do not allow another
|
||||
// minus sign. We might need to do more precise parsing here.
|
||||
let opt = if let Some(s) = s.strip_prefix("-0x") {
|
||||
u64::from_str_radix(s, 16).ok().map(|x| -(x as i64))
|
||||
} else if let Some(s) = s.strip_prefix("0x") {
|
||||
u64::from_str_radix(s, 16).ok().map(|x| x as i64)
|
||||
} else if s.starts_with("-0") || s.starts_with('0') {
|
||||
i64::from_str_radix(s, 8).ok()
|
||||
} else if let Some(s) = s.strip_prefix('\'') {
|
||||
s.chars().next().map(|x| x as i64)
|
||||
} else {
|
||||
s.parse().ok()
|
||||
};
|
||||
match opt {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
show_warning!("{}: expected a numeric value", s.quote());
|
||||
set_exit_code(1);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_f64(&mut self) -> f64 {
|
||||
let Some(next) = self.next() else {
|
||||
return 0.0;
|
||||
};
|
||||
match next {
|
||||
FormatArgument::Float(n) => *n,
|
||||
FormatArgument::Unparsed(s) => {
|
||||
let opt = if s.starts_with("0x") || s.starts_with("-0x") {
|
||||
unimplemented!("Hexadecimal floats are unimplemented!")
|
||||
} else if let Some(s) = s.strip_prefix('\'') {
|
||||
s.chars().next().map(|x| x as u64 as f64)
|
||||
} else {
|
||||
s.parse().ok()
|
||||
};
|
||||
match opt {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
show_warning!("{}: expected a numeric value", s.quote());
|
||||
set_exit_code(1);
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_str(&mut self) -> &'a str {
|
||||
match self.next() {
|
||||
Some(FormatArgument::Unparsed(s) | FormatArgument::String(s)) => s,
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Parsing of escape sequences
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EscapedChar {
|
||||
/// A single byte
|
||||
Byte(u8),
|
||||
/// A unicode character
|
||||
Char(char),
|
||||
/// A character prefixed with a backslash (i.e. an invalid escape sequence)
|
||||
Backslash(u8),
|
||||
/// Specifies that the string should stop (`\c`)
|
||||
End,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
enum Base {
|
||||
Oct = 8,
|
||||
Hex = 16,
|
||||
}
|
||||
|
||||
impl Base {
|
||||
fn max_digits(&self) -> u8 {
|
||||
match self {
|
||||
Self::Oct => 3,
|
||||
Self::Hex => 2,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_digit(&self, c: u8) -> Option<u8> {
|
||||
match self {
|
||||
Self::Oct => {
|
||||
if matches!(c, b'0'..=b'7') {
|
||||
Some(c - b'0')
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Self::Hex => match c {
|
||||
b'0'..=b'9' => Some(c - b'0'),
|
||||
b'A'..=b'F' => Some(c - b'A' + 10),
|
||||
b'a'..=b'f' => Some(c - b'a' + 10),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences
|
||||
fn parse_code(input: &mut &[u8], base: Base) -> Option<u8> {
|
||||
// All arithmetic on `ret` needs to be wrapping, because octal input can
|
||||
// take 3 digits, which is 9 bits, and therefore more than what fits in a
|
||||
// `u8`. GNU just seems to wrap these values.
|
||||
// Note that if we instead make `ret` a `u32` and use `char::from_u32` will
|
||||
// yield incorrect results because it will interpret values larger than
|
||||
// `u8::MAX` as unicode.
|
||||
let [c, rest @ ..] = input else { return None };
|
||||
let mut ret = base.convert_digit(*c)?;
|
||||
*input = rest;
|
||||
|
||||
for _ in 1..base.max_digits() {
|
||||
let [c, rest @ ..] = input else { break };
|
||||
let Some(n) = base.convert_digit(*c) else {
|
||||
break;
|
||||
};
|
||||
ret = ret.wrapping_mul(base as u8).wrapping_add(n);
|
||||
*input = rest;
|
||||
}
|
||||
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
// spell-checker:disable-next
|
||||
/// Parse `\uHHHH` and `\UHHHHHHHH`
|
||||
// TODO: This should print warnings and possibly halt execution when it fails to parse
|
||||
// TODO: If the character cannot be converted to u32, the input should be printed.
|
||||
fn parse_unicode(input: &mut &[u8], digits: u8) -> Option<char> {
|
||||
let (c, rest) = input.split_first()?;
|
||||
let mut ret = Base::Hex.convert_digit(*c)? as u32;
|
||||
*input = rest;
|
||||
|
||||
for _ in 1..digits {
|
||||
let (c, rest) = input.split_first()?;
|
||||
let n = Base::Hex.convert_digit(*c)?;
|
||||
ret = ret.wrapping_mul(Base::Hex as u32).wrapping_add(n as u32);
|
||||
*input = rest;
|
||||
}
|
||||
|
||||
char::from_u32(ret)
|
||||
}
|
||||
|
||||
pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar {
|
||||
if let [c, new_rest @ ..] = rest {
|
||||
// This is for the \NNN syntax for octal sequences.
|
||||
// Note that '0' is intentionally omitted because that
|
||||
// would be the \0NNN syntax.
|
||||
if let b'1'..=b'7' = c {
|
||||
if let Some(parsed) = parse_code(rest, Base::Oct) {
|
||||
return EscapedChar::Byte(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
*rest = new_rest;
|
||||
match c {
|
||||
b'\\' => EscapedChar::Byte(b'\\'),
|
||||
b'a' => EscapedChar::Byte(b'\x07'),
|
||||
b'b' => EscapedChar::Byte(b'\x08'),
|
||||
b'c' => EscapedChar::End,
|
||||
b'e' => EscapedChar::Byte(b'\x1b'),
|
||||
b'f' => EscapedChar::Byte(b'\x0c'),
|
||||
b'n' => EscapedChar::Byte(b'\n'),
|
||||
b'r' => EscapedChar::Byte(b'\r'),
|
||||
b't' => EscapedChar::Byte(b'\t'),
|
||||
b'v' => EscapedChar::Byte(b'\x0b'),
|
||||
b'x' => {
|
||||
if let Some(c) = parse_code(rest, Base::Hex) {
|
||||
EscapedChar::Byte(c)
|
||||
} else {
|
||||
EscapedChar::Backslash(b'x')
|
||||
}
|
||||
}
|
||||
b'0' => EscapedChar::Byte(parse_code(rest, Base::Oct).unwrap_or(b'\0')),
|
||||
b'u' => EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0')),
|
||||
b'U' => EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0')),
|
||||
c => EscapedChar::Backslash(*c),
|
||||
}
|
||||
} else {
|
||||
EscapedChar::Byte(b'\\')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! `printf`-style formatting
|
||||
//!
|
||||
//! Rust has excellent formatting capabilities, but the coreutils require very
|
||||
//! specific formatting that needs to work exactly like the GNU utilities.
|
||||
//! Naturally, the GNU behavior is based on the C `printf` functionality.
|
||||
//!
|
||||
//! Additionally, we need support for escape sequences for the `printf` utility.
|
||||
//!
|
||||
//! The [`printf`] and [`sprintf`] functions closely match the behavior of the
|
||||
//! corresponding C functions: the former renders a formatted string
|
||||
//! to stdout, the latter renders to a new [`String`] object.
|
||||
//!
|
||||
//! There are three kinds of parsing that we might want to do:
|
||||
//!
|
||||
//! 1. Parse only `printf` directives (for e.g. `seq`, `dd`)
|
||||
//! 2. Parse only escape sequences (for e.g. `echo`)
|
||||
//! 3. Parse both `printf` specifiers and escape sequences (for e.g. `printf`)
|
||||
//!
|
||||
//! This module aims to combine all three use cases. An iterator parsing each
|
||||
//! of these cases is provided by [`parse_escape_only`], [`parse_spec_only`]
|
||||
//! and [`parse_spec_and_escape`], respectively.
|
||||
//!
|
||||
//! There is a special [`Format`] type, which can be used to parse a format
|
||||
//! string containing exactly one directive and does not use any `*` in that
|
||||
//! directive. This format can be printed in a type-safe manner without failing
|
||||
//! (modulo IO errors).
|
||||
|
||||
mod argument;
|
||||
mod escape;
|
||||
pub mod num_format;
|
||||
mod spec;
|
||||
|
||||
pub use argument::*;
|
||||
use spec::Spec;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
io::{stdout, Write},
|
||||
ops::ControlFlow,
|
||||
};
|
||||
|
||||
use crate::error::UError;
|
||||
|
||||
use self::{
|
||||
escape::{parse_escape_code, EscapedChar},
|
||||
num_format::Formatter,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FormatError {
|
||||
SpecError(Vec<u8>),
|
||||
IoError(std::io::Error),
|
||||
NoMoreArguments,
|
||||
InvalidArgument(FormatArgument),
|
||||
TooManySpecs,
|
||||
NeedAtLeastOneSpec,
|
||||
WrongSpecType,
|
||||
}
|
||||
|
||||
impl Error for FormatError {}
|
||||
impl UError for FormatError {}
|
||||
|
||||
impl From<std::io::Error> for FormatError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FormatError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::SpecError(s) => write!(
|
||||
f,
|
||||
"%{}: invalid conversion specification",
|
||||
String::from_utf8_lossy(s)
|
||||
),
|
||||
// TODO: The next two should print the spec as well
|
||||
Self::TooManySpecs => write!(f, "format has too many % directives"),
|
||||
Self::NeedAtLeastOneSpec => write!(f, "format has no % directive"),
|
||||
// TODO: Error message below needs some work
|
||||
Self::WrongSpecType => write!(f, "wrong % directive type was given"),
|
||||
Self::IoError(_) => write!(f, "io error"),
|
||||
Self::NoMoreArguments => write!(f, "no more arguments"),
|
||||
Self::InvalidArgument(_) => write!(f, "invalid argument"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single item to format
|
||||
pub enum FormatItem<C: FormatChar> {
|
||||
/// A format specifier
|
||||
Spec(Spec),
|
||||
/// A single character
|
||||
Char(C),
|
||||
}
|
||||
|
||||
pub trait FormatChar {
|
||||
fn write(&self, writer: impl Write) -> std::io::Result<ControlFlow<()>>;
|
||||
}
|
||||
|
||||
impl FormatChar for u8 {
|
||||
fn write(&self, mut writer: impl Write) -> std::io::Result<ControlFlow<()>> {
|
||||
writer.write_all(&[*self])?;
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatChar for EscapedChar {
|
||||
fn write(&self, mut writer: impl Write) -> std::io::Result<ControlFlow<()>> {
|
||||
match self {
|
||||
Self::Byte(c) => {
|
||||
writer.write_all(&[*c])?;
|
||||
}
|
||||
Self::Char(c) => {
|
||||
write!(writer, "{c}")?;
|
||||
}
|
||||
Self::Backslash(c) => {
|
||||
writer.write_all(&[b'\\', *c])?;
|
||||
}
|
||||
Self::End => return Ok(ControlFlow::Break(())),
|
||||
}
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: FormatChar> FormatItem<C> {
|
||||
pub fn write<'a>(
|
||||
&self,
|
||||
writer: impl Write,
|
||||
args: &mut impl Iterator<Item = &'a FormatArgument>,
|
||||
) -> Result<ControlFlow<()>, FormatError> {
|
||||
match self {
|
||||
Self::Spec(spec) => spec.write(writer, args)?,
|
||||
Self::Char(c) => return c.write(writer).map_err(FormatError::IoError),
|
||||
};
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a format string containing % directives and escape sequences
|
||||
pub fn parse_spec_and_escape(
|
||||
fmt: &[u8],
|
||||
) -> impl Iterator<Item = Result<FormatItem<EscapedChar>, FormatError>> + '_ {
|
||||
let mut current = fmt;
|
||||
std::iter::from_fn(move || match current {
|
||||
[] => None,
|
||||
[b'%', b'%', rest @ ..] => {
|
||||
current = rest;
|
||||
Some(Ok(FormatItem::Char(EscapedChar::Byte(b'%'))))
|
||||
}
|
||||
[b'%', rest @ ..] => {
|
||||
current = rest;
|
||||
let spec = match Spec::parse(&mut current) {
|
||||
Ok(spec) => spec,
|
||||
Err(slice) => return Some(Err(FormatError::SpecError(slice.to_vec()))),
|
||||
};
|
||||
Some(Ok(FormatItem::Spec(spec)))
|
||||
}
|
||||
[b'\\', rest @ ..] => {
|
||||
current = rest;
|
||||
Some(Ok(FormatItem::Char(parse_escape_code(&mut current))))
|
||||
}
|
||||
[c, rest @ ..] => {
|
||||
current = rest;
|
||||
Some(Ok(FormatItem::Char(EscapedChar::Byte(*c))))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a format string containing % directives
|
||||
pub fn parse_spec_only(
|
||||
fmt: &[u8],
|
||||
) -> impl Iterator<Item = Result<FormatItem<u8>, FormatError>> + '_ {
|
||||
let mut current = fmt;
|
||||
std::iter::from_fn(move || match current {
|
||||
[] => None,
|
||||
[b'%', b'%', rest @ ..] => {
|
||||
current = rest;
|
||||
Some(Ok(FormatItem::Char(b'%')))
|
||||
}
|
||||
[b'%', rest @ ..] => {
|
||||
current = rest;
|
||||
let spec = match Spec::parse(&mut current) {
|
||||
Ok(spec) => spec,
|
||||
Err(slice) => return Some(Err(FormatError::SpecError(slice.to_vec()))),
|
||||
};
|
||||
Some(Ok(FormatItem::Spec(spec)))
|
||||
}
|
||||
[c, rest @ ..] => {
|
||||
current = rest;
|
||||
Some(Ok(FormatItem::Char(*c)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a format string containing escape sequences
|
||||
pub fn parse_escape_only(fmt: &[u8]) -> impl Iterator<Item = EscapedChar> + '_ {
|
||||
let mut current = fmt;
|
||||
std::iter::from_fn(move || match current {
|
||||
[] => None,
|
||||
[b'\\', rest @ ..] => {
|
||||
current = rest;
|
||||
Some(parse_escape_code(&mut current))
|
||||
}
|
||||
[c, rest @ ..] => {
|
||||
current = rest;
|
||||
Some(EscapedChar::Byte(*c))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a formatted string to stdout.
|
||||
///
|
||||
/// `format_string` contains the template and `args` contains the
|
||||
/// arguments to render into the template.
|
||||
///
|
||||
/// See also [`sprintf`], which creates a new formatted [`String`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use uucore::format::{printf, FormatArgument};
|
||||
///
|
||||
/// printf("hello %s", &[FormatArgument::String("world".into())]).unwrap();
|
||||
/// // prints "hello world"
|
||||
/// ```
|
||||
pub fn printf<'a>(
|
||||
format_string: impl AsRef<[u8]>,
|
||||
arguments: impl IntoIterator<Item = &'a FormatArgument>,
|
||||
) -> Result<(), FormatError> {
|
||||
printf_writer(stdout(), format_string, arguments)
|
||||
}
|
||||
|
||||
fn printf_writer<'a>(
|
||||
mut writer: impl Write,
|
||||
format_string: impl AsRef<[u8]>,
|
||||
args: impl IntoIterator<Item = &'a FormatArgument>,
|
||||
) -> Result<(), FormatError> {
|
||||
let mut args = args.into_iter();
|
||||
for item in parse_spec_only(format_string.as_ref()) {
|
||||
item?.write(&mut writer, &mut args)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new formatted string.
|
||||
///
|
||||
/// `format_string` contains the template and `args` contains the
|
||||
/// arguments to render into the template.
|
||||
///
|
||||
/// See also [`printf`], which prints to stdout.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use uucore::format::{sprintf, FormatArgument};
|
||||
///
|
||||
/// let s = sprintf("hello %s", &[FormatArgument::String("world".into())]).unwrap();
|
||||
/// let s = std::str::from_utf8(&s).unwrap();
|
||||
/// assert_eq!(s, "hello world");
|
||||
/// ```
|
||||
pub fn sprintf<'a>(
|
||||
format_string: impl AsRef<[u8]>,
|
||||
arguments: impl IntoIterator<Item = &'a FormatArgument>,
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
let mut writer = Vec::new();
|
||||
printf_writer(&mut writer, format_string, arguments)?;
|
||||
Ok(writer)
|
||||
}
|
||||
|
||||
/// A parsed format for a single float value
|
||||
///
|
||||
/// This is used by `seq`. It can be constructed with [`Format::parse`]
|
||||
/// and can write a value with [`Format::fmt`].
|
||||
///
|
||||
/// It can only accept a single specification without any asterisk parameters.
|
||||
/// If it does get more specifications, it will return an error.
|
||||
pub struct Format<F: Formatter> {
|
||||
prefix: Vec<u8>,
|
||||
suffix: Vec<u8>,
|
||||
formatter: F,
|
||||
}
|
||||
|
||||
impl<F: Formatter> Format<F> {
|
||||
pub fn parse(format_string: impl AsRef<[u8]>) -> Result<Self, FormatError> {
|
||||
let mut iter = parse_spec_only(format_string.as_ref());
|
||||
|
||||
let mut prefix = Vec::new();
|
||||
let mut spec = None;
|
||||
for item in &mut iter {
|
||||
match item? {
|
||||
FormatItem::Spec(s) => {
|
||||
spec = Some(s);
|
||||
break;
|
||||
}
|
||||
FormatItem::Char(c) => prefix.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
let Some(spec) = spec else {
|
||||
return Err(FormatError::NeedAtLeastOneSpec);
|
||||
};
|
||||
|
||||
let formatter = F::try_from_spec(spec)?;
|
||||
|
||||
let mut suffix = Vec::new();
|
||||
for item in &mut iter {
|
||||
match item? {
|
||||
FormatItem::Spec(_) => {
|
||||
return Err(FormatError::TooManySpecs);
|
||||
}
|
||||
FormatItem::Char(c) => suffix.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
prefix,
|
||||
suffix,
|
||||
formatter,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fmt(&self, mut w: impl Write, f: F::Input) -> std::io::Result<()> {
|
||||
w.write_all(&self.prefix)?;
|
||||
self.formatter.fmt(&mut w, f)?;
|
||||
w.write_all(&self.suffix)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,462 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (vars) intmax ptrdiff
|
||||
|
||||
use crate::quoting_style::{escape_name, QuotingStyle};
|
||||
|
||||
use super::{
|
||||
num_format::{
|
||||
self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix,
|
||||
UnsignedIntVariant,
|
||||
},
|
||||
parse_escape_only, ArgumentIter, FormatChar, FormatError,
|
||||
};
|
||||
use std::{fmt::Display, io::Write, ops::ControlFlow};
|
||||
|
||||
/// A parsed specification for formatting a value
|
||||
///
|
||||
/// This might require more than one argument to resolve width or precision
|
||||
/// values that are given as `*`.
|
||||
#[derive(Debug)]
|
||||
pub enum Spec {
|
||||
Char {
|
||||
width: Option<CanAsterisk<usize>>,
|
||||
align_left: bool,
|
||||
},
|
||||
String {
|
||||
precision: Option<CanAsterisk<usize>>,
|
||||
width: Option<CanAsterisk<usize>>,
|
||||
align_left: bool,
|
||||
},
|
||||
EscapedString,
|
||||
QuotedString,
|
||||
SignedInt {
|
||||
width: Option<CanAsterisk<usize>>,
|
||||
precision: Option<CanAsterisk<usize>>,
|
||||
positive_sign: PositiveSign,
|
||||
alignment: NumberAlignment,
|
||||
},
|
||||
UnsignedInt {
|
||||
variant: UnsignedIntVariant,
|
||||
width: Option<CanAsterisk<usize>>,
|
||||
precision: Option<CanAsterisk<usize>>,
|
||||
alignment: NumberAlignment,
|
||||
},
|
||||
Float {
|
||||
variant: FloatVariant,
|
||||
case: Case,
|
||||
force_decimal: ForceDecimal,
|
||||
width: Option<CanAsterisk<usize>>,
|
||||
positive_sign: PositiveSign,
|
||||
alignment: NumberAlignment,
|
||||
precision: Option<CanAsterisk<usize>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Precision and width specified might use an asterisk to indicate that they are
|
||||
/// determined by an argument.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum CanAsterisk<T> {
|
||||
Fixed(T),
|
||||
Asterisk,
|
||||
}
|
||||
|
||||
/// Size of the expected type (ignored)
|
||||
///
|
||||
/// We ignore this parameter entirely, but we do parse it.
|
||||
/// It could be used in the future if the need arises.
|
||||
enum Length {
|
||||
/// signed/unsigned char ("hh")
|
||||
Char,
|
||||
/// signed/unsigned short int ("h")
|
||||
Short,
|
||||
/// signed/unsigned long int ("l")
|
||||
Long,
|
||||
/// signed/unsigned long long int ("ll")
|
||||
LongLong,
|
||||
/// intmax_t ("j")
|
||||
IntMaxT,
|
||||
/// size_t ("z")
|
||||
SizeT,
|
||||
/// ptrdiff_t ("t")
|
||||
PtfDiffT,
|
||||
/// long double ("L")
|
||||
LongDouble,
|
||||
}
|
||||
|
||||
impl Spec {
|
||||
pub fn parse<'a>(rest: &mut &'a [u8]) -> Result<Self, &'a [u8]> {
|
||||
// Based on the C++ reference, the spec format looks like:
|
||||
//
|
||||
// %[flags][width][.precision][length]specifier
|
||||
//
|
||||
// However, we have already parsed the '%'.
|
||||
let mut index = 0;
|
||||
let start = *rest;
|
||||
|
||||
let mut minus = false;
|
||||
let mut plus = false;
|
||||
let mut space = false;
|
||||
let mut hash = false;
|
||||
let mut zero = false;
|
||||
|
||||
while let Some(x) = rest.get(index) {
|
||||
match x {
|
||||
b'-' => minus = true,
|
||||
b'+' => plus = true,
|
||||
b' ' => space = true,
|
||||
b'#' => hash = true,
|
||||
b'0' => zero = true,
|
||||
_ => break,
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
let alignment = match (minus, zero) {
|
||||
(true, _) => NumberAlignment::Left,
|
||||
(false, true) => NumberAlignment::RightZero,
|
||||
(false, false) => NumberAlignment::RightSpace,
|
||||
};
|
||||
|
||||
let positive_sign = match (plus, space) {
|
||||
(true, _) => PositiveSign::Plus,
|
||||
(false, true) => PositiveSign::Space,
|
||||
(false, false) => PositiveSign::None,
|
||||
};
|
||||
|
||||
let width = eat_asterisk_or_number(rest, &mut index);
|
||||
|
||||
let precision = if let Some(b'.') = rest.get(index) {
|
||||
index += 1;
|
||||
Some(eat_asterisk_or_number(rest, &mut index).unwrap_or(CanAsterisk::Fixed(0)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// We ignore the length. It's not really relevant to printf
|
||||
let _ = Self::parse_length(rest, &mut index);
|
||||
|
||||
let Some(type_spec) = rest.get(index) else {
|
||||
return Err(&start[..index]);
|
||||
};
|
||||
index += 1;
|
||||
*rest = &start[index..];
|
||||
|
||||
Ok(match type_spec {
|
||||
// GNU accepts minus, plus and space even though they are not used
|
||||
b'c' => {
|
||||
if hash || precision.is_some() {
|
||||
return Err(&start[..index]);
|
||||
}
|
||||
Self::Char {
|
||||
width,
|
||||
align_left: minus,
|
||||
}
|
||||
}
|
||||
b's' => {
|
||||
if hash {
|
||||
return Err(&start[..index]);
|
||||
}
|
||||
Self::String {
|
||||
precision,
|
||||
width,
|
||||
align_left: minus,
|
||||
}
|
||||
}
|
||||
b'b' => {
|
||||
if hash || minus || plus || space || width.is_some() || precision.is_some() {
|
||||
return Err(&start[..index]);
|
||||
}
|
||||
Self::EscapedString
|
||||
}
|
||||
b'q' => {
|
||||
if hash || minus || plus || space || width.is_some() || precision.is_some() {
|
||||
return Err(&start[..index]);
|
||||
}
|
||||
Self::QuotedString
|
||||
}
|
||||
b'd' | b'i' => {
|
||||
if hash {
|
||||
return Err(&start[..index]);
|
||||
}
|
||||
Self::SignedInt {
|
||||
width,
|
||||
precision,
|
||||
alignment,
|
||||
positive_sign,
|
||||
}
|
||||
}
|
||||
c @ (b'u' | b'o' | b'x' | b'X') => {
|
||||
// Normal unsigned integer cannot have a prefix
|
||||
if *c == b'u' && hash {
|
||||
return Err(&start[..index]);
|
||||
}
|
||||
let prefix = match hash {
|
||||
false => Prefix::No,
|
||||
true => Prefix::Yes,
|
||||
};
|
||||
let variant = match c {
|
||||
b'u' => UnsignedIntVariant::Decimal,
|
||||
b'o' => UnsignedIntVariant::Octal(prefix),
|
||||
b'x' => UnsignedIntVariant::Hexadecimal(Case::Lowercase, prefix),
|
||||
b'X' => UnsignedIntVariant::Hexadecimal(Case::Uppercase, prefix),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Self::UnsignedInt {
|
||||
variant,
|
||||
precision,
|
||||
width,
|
||||
alignment,
|
||||
}
|
||||
}
|
||||
c @ (b'f' | b'F' | b'e' | b'E' | b'g' | b'G' | b'a' | b'A') => Self::Float {
|
||||
width,
|
||||
precision,
|
||||
variant: match c {
|
||||
b'f' | b'F' => FloatVariant::Decimal,
|
||||
b'e' | b'E' => FloatVariant::Scientific,
|
||||
b'g' | b'G' => FloatVariant::Shortest,
|
||||
b'a' | b'A' => FloatVariant::Hexadecimal,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
force_decimal: match hash {
|
||||
false => ForceDecimal::No,
|
||||
true => ForceDecimal::Yes,
|
||||
},
|
||||
case: match c.is_ascii_uppercase() {
|
||||
false => Case::Lowercase,
|
||||
true => Case::Uppercase,
|
||||
},
|
||||
alignment,
|
||||
positive_sign,
|
||||
},
|
||||
_ => return Err(&start[..index]),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_length(rest: &mut &[u8], index: &mut usize) -> Option<Length> {
|
||||
// Parse 0..N length options, keep the last one
|
||||
// Even though it is just ignored. We might want to use it later and we
|
||||
// should parse those characters.
|
||||
//
|
||||
// TODO: This needs to be configurable: `seq` accepts only one length
|
||||
// param
|
||||
let mut length = None;
|
||||
loop {
|
||||
let new_length = rest.get(*index).and_then(|c| {
|
||||
Some(match c {
|
||||
b'h' => {
|
||||
if let Some(b'h') = rest.get(*index + 1) {
|
||||
*index += 1;
|
||||
Length::Char
|
||||
} else {
|
||||
Length::Short
|
||||
}
|
||||
}
|
||||
b'l' => {
|
||||
if let Some(b'l') = rest.get(*index + 1) {
|
||||
*index += 1;
|
||||
Length::Long
|
||||
} else {
|
||||
Length::LongLong
|
||||
}
|
||||
}
|
||||
b'j' => Length::IntMaxT,
|
||||
b'z' => Length::SizeT,
|
||||
b't' => Length::PtfDiffT,
|
||||
b'L' => Length::LongDouble,
|
||||
_ => return None,
|
||||
})
|
||||
});
|
||||
if new_length.is_some() {
|
||||
*index += 1;
|
||||
length = new_length;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
length
|
||||
}
|
||||
|
||||
pub fn write<'a>(
|
||||
&self,
|
||||
mut writer: impl Write,
|
||||
mut args: impl ArgumentIter<'a>,
|
||||
) -> Result<(), FormatError> {
|
||||
match self {
|
||||
Self::Char { width, align_left } => {
|
||||
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
|
||||
write_padded(writer, args.get_char(), width, false, *align_left)
|
||||
}
|
||||
Self::String {
|
||||
width,
|
||||
align_left,
|
||||
precision,
|
||||
} => {
|
||||
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
|
||||
|
||||
// GNU does do this truncation on a byte level, see for instance:
|
||||
// printf "%.1s" 🙃
|
||||
// > �
|
||||
// For now, we let printf panic when we truncate within a code point.
|
||||
// TODO: We need to not use Rust's formatting for aligning the output,
|
||||
// so that we can just write bytes to stdout without panicking.
|
||||
let precision = resolve_asterisk(*precision, &mut args)?;
|
||||
let s = args.get_str();
|
||||
let truncated = match precision {
|
||||
Some(p) if p < s.len() => &s[..p],
|
||||
_ => s,
|
||||
};
|
||||
write_padded(writer, truncated, width, false, *align_left)
|
||||
}
|
||||
Self::EscapedString => {
|
||||
let s = args.get_str();
|
||||
let mut parsed = Vec::new();
|
||||
for c in parse_escape_only(s.as_bytes()) {
|
||||
match c.write(&mut parsed)? {
|
||||
ControlFlow::Continue(()) => {}
|
||||
ControlFlow::Break(()) => {
|
||||
// TODO: This should break the _entire execution_ of printf
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
writer.write_all(&parsed).map_err(FormatError::IoError)
|
||||
}
|
||||
Self::QuotedString => {
|
||||
let s = args.get_str();
|
||||
writer
|
||||
.write_all(
|
||||
escape_name(
|
||||
s.as_ref(),
|
||||
&QuotingStyle::Shell {
|
||||
escape: true,
|
||||
always_quote: false,
|
||||
show_control: false,
|
||||
},
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
Self::SignedInt {
|
||||
width,
|
||||
precision,
|
||||
positive_sign,
|
||||
alignment,
|
||||
} => {
|
||||
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
|
||||
let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(0);
|
||||
let i = args.get_i64();
|
||||
|
||||
num_format::SignedInt {
|
||||
width,
|
||||
precision,
|
||||
positive_sign: *positive_sign,
|
||||
alignment: *alignment,
|
||||
}
|
||||
.fmt(writer, i)
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
Self::UnsignedInt {
|
||||
variant,
|
||||
width,
|
||||
precision,
|
||||
alignment,
|
||||
} => {
|
||||
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
|
||||
let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(0);
|
||||
let i = args.get_u64();
|
||||
|
||||
num_format::UnsignedInt {
|
||||
variant: *variant,
|
||||
precision,
|
||||
width,
|
||||
alignment: *alignment,
|
||||
}
|
||||
.fmt(writer, i)
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
Self::Float {
|
||||
variant,
|
||||
case,
|
||||
force_decimal,
|
||||
width,
|
||||
positive_sign,
|
||||
alignment,
|
||||
precision,
|
||||
} => {
|
||||
let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0);
|
||||
let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(6);
|
||||
let f = args.get_f64();
|
||||
|
||||
num_format::Float {
|
||||
width,
|
||||
precision,
|
||||
variant: *variant,
|
||||
case: *case,
|
||||
force_decimal: *force_decimal,
|
||||
positive_sign: *positive_sign,
|
||||
alignment: *alignment,
|
||||
}
|
||||
.fmt(writer, f)
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_asterisk<'a>(
|
||||
option: Option<CanAsterisk<usize>>,
|
||||
mut args: impl ArgumentIter<'a>,
|
||||
) -> Result<Option<usize>, FormatError> {
|
||||
Ok(match option {
|
||||
None => None,
|
||||
Some(CanAsterisk::Asterisk) => Some(usize::try_from(args.get_u64()).ok().unwrap_or(0)),
|
||||
Some(CanAsterisk::Fixed(w)) => Some(w),
|
||||
})
|
||||
}
|
||||
|
||||
fn write_padded(
|
||||
mut writer: impl Write,
|
||||
text: impl Display,
|
||||
width: usize,
|
||||
pad_zero: bool,
|
||||
left: bool,
|
||||
) -> Result<(), FormatError> {
|
||||
match (left, pad_zero) {
|
||||
(false, false) => write!(writer, "{text: >width$}"),
|
||||
(false, true) => write!(writer, "{text:0>width$}"),
|
||||
// 0 is ignored if we pad left.
|
||||
(true, _) => write!(writer, "{text: <width$}"),
|
||||
}
|
||||
.map_err(FormatError::IoError)
|
||||
}
|
||||
|
||||
fn eat_asterisk_or_number(rest: &mut &[u8], index: &mut usize) -> Option<CanAsterisk<usize>> {
|
||||
if let Some(b'*') = rest.get(*index) {
|
||||
*index += 1;
|
||||
Some(CanAsterisk::Asterisk)
|
||||
} else {
|
||||
eat_number(rest, index).map(CanAsterisk::Fixed)
|
||||
}
|
||||
}
|
||||
|
||||
fn eat_number(rest: &mut &[u8], index: &mut usize) -> Option<usize> {
|
||||
match rest[*index..].iter().position(|b| !b.is_ascii_digit()) {
|
||||
None | Some(0) => None,
|
||||
Some(i) => {
|
||||
// TODO: This might need to handle errors better
|
||||
// For example in case of overflow.
|
||||
let parsed = std::str::from_utf8(&rest[*index..(*index + i)])
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
*index += i;
|
||||
Some(parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
//! Main entry point for our implementation of printf.
|
||||
//!
|
||||
//! The [`printf`] and [`sprintf`] closely match the behavior of the
|
||||
//! corresponding C functions: the former renders a formatted string
|
||||
//! to stdout, the latter renders to a new [`String`] object.
|
||||
use crate::display::Quotable;
|
||||
use crate::error::{UResult, USimpleError};
|
||||
use crate::features::tokenize::sub::SubParser;
|
||||
use crate::features::tokenize::token::Token;
|
||||
use crate::features::tokenize::unescaped_text::UnescapedText;
|
||||
use crate::show_warning;
|
||||
use itertools::put_back_n;
|
||||
use std::io::{stdout, Cursor, Write};
|
||||
use std::iter::Peekable;
|
||||
use std::slice::Iter;
|
||||
|
||||
/// Memo runner of printf
|
||||
/// Takes a format string and arguments
|
||||
/// 1. tokenize format string into tokens, consuming
|
||||
/// any subst. arguments along the way.
|
||||
/// 2. feeds remaining arguments into function
|
||||
/// that prints tokens.
|
||||
struct Memo {
|
||||
tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
fn warn_excess_args(first_arg: &str) {
|
||||
show_warning!(
|
||||
"ignoring excess arguments, starting with {}",
|
||||
first_arg.quote()
|
||||
);
|
||||
}
|
||||
|
||||
impl Memo {
|
||||
fn new<W>(
|
||||
writer: &mut W,
|
||||
pf_string: &str,
|
||||
pf_args_it: &mut Peekable<Iter<String>>,
|
||||
) -> UResult<Self>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let mut pm = Self { tokens: Vec::new() };
|
||||
let mut it = put_back_n(pf_string.chars());
|
||||
let mut has_sub = false;
|
||||
loop {
|
||||
if let Some(x) = UnescapedText::from_it_core(writer, &mut it, false) {
|
||||
pm.tokens.push(x);
|
||||
}
|
||||
if let Some(x) = SubParser::from_it(writer, &mut it, pf_args_it)? {
|
||||
if !has_sub {
|
||||
has_sub = true;
|
||||
}
|
||||
pm.tokens.push(x);
|
||||
}
|
||||
if let Some(x) = it.next() {
|
||||
it.put_back(x);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !has_sub {
|
||||
let mut drain = false;
|
||||
if let Some(first_arg) = pf_args_it.peek() {
|
||||
warn_excess_args(first_arg);
|
||||
drain = true;
|
||||
}
|
||||
if drain {
|
||||
loop {
|
||||
// drain remaining args;
|
||||
if pf_args_it.next().is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(pm)
|
||||
}
|
||||
fn apply<W>(&self, writer: &mut W, pf_args_it: &mut Peekable<Iter<String>>)
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
for tkn in &self.tokens {
|
||||
tkn.write(writer, pf_args_it);
|
||||
}
|
||||
}
|
||||
fn run_all<W>(writer: &mut W, pf_string: &str, pf_args: &[String]) -> UResult<()>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let mut arg_it = pf_args.iter().peekable();
|
||||
let pm = Self::new(writer, pf_string, &mut arg_it)?;
|
||||
loop {
|
||||
if arg_it.peek().is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
pm.apply(writer, &mut arg_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a formatted string to stdout.
|
||||
///
|
||||
/// `format_string` contains the template and `args` contains the
|
||||
/// arguments to render into the template.
|
||||
///
|
||||
/// See also [`sprintf`], which creates a new formatted [`String`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use uucore::memo::printf;
|
||||
///
|
||||
/// printf("hello %s", &["world".to_string()]).unwrap();
|
||||
/// // prints "hello world"
|
||||
/// ```
|
||||
pub fn printf(format_string: &str, args: &[String]) -> UResult<()> {
|
||||
let mut writer = stdout();
|
||||
Memo::run_all(&mut writer, format_string, args)
|
||||
}
|
||||
|
||||
/// Create a new formatted string.
|
||||
///
|
||||
/// `format_string` contains the template and `args` contains the
|
||||
/// arguments to render into the template.
|
||||
///
|
||||
/// See also [`printf`], which prints to stdout.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use uucore::memo::sprintf;
|
||||
///
|
||||
/// let s = sprintf("hello %s", &["world".to_string()]).unwrap();
|
||||
/// assert_eq!(s, "hello world".to_string());
|
||||
/// ```
|
||||
pub fn sprintf(format_string: &str, args: &[String]) -> UResult<String> {
|
||||
let mut writer = Cursor::new(vec![]);
|
||||
Memo::run_all(&mut writer, format_string, args)?;
|
||||
let buf = writer.into_inner();
|
||||
match String::from_utf8(buf) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(USimpleError::new(
|
||||
1,
|
||||
format!("failed to parse formatted string as UTF-8: {e}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::memo::sprintf;
|
||||
|
||||
#[test]
|
||||
fn test_sprintf_smoke() {
|
||||
assert_eq!(sprintf("", &[]).unwrap(), "".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sprintf_no_args() {
|
||||
assert_eq!(
|
||||
sprintf("hello world", &[]).unwrap(),
|
||||
"hello world".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sprintf_string() {
|
||||
assert_eq!(
|
||||
sprintf("hello %s", &["world".to_string()]).unwrap(),
|
||||
"hello world".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
#[allow(clippy::module_inception)]
|
||||
mod num_format;
|
||||
pub mod sub;
|
||||
pub mod token;
|
||||
pub mod unescaped_text;
|
||||
@@ -1,30 +0,0 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
|
||||
|
||||
//! Primitives used by Sub Tokenizer
|
||||
//! and num_format modules
|
||||
#[derive(Clone)]
|
||||
pub enum FieldType {
|
||||
Strf,
|
||||
Floatf,
|
||||
CninetyNineHexFloatf,
|
||||
Scif,
|
||||
Decf,
|
||||
Intf,
|
||||
Charf,
|
||||
}
|
||||
|
||||
// a Sub Tokens' fields are stored
|
||||
// as a single object so they can be more simply
|
||||
// passed by ref to num_format in a Sub method
|
||||
#[derive(Clone)]
|
||||
pub struct FormatField<'a> {
|
||||
pub min_width: Option<isize>,
|
||||
pub second_field: Option<u32>,
|
||||
pub field_char: &'a char,
|
||||
pub field_type: &'a FieldType,
|
||||
pub orig: &'a String,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user