mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-02 14:07:46 +00:00
uucore/format: implement single specifier formats
This commit is contained in:
parent
ee0e2c042b
commit
6481d63ea4
4 changed files with 216 additions and 28 deletions
|
@ -3,13 +3,13 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse
|
// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse
|
||||||
use std::io::{stdout, ErrorKind, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use num_traits::Zero;
|
use num_traits::{Zero, ToPrimitive};
|
||||||
|
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
use uucore::format::{printf, FormatArgument};
|
use uucore::format::{printf, FormatArgument, Format, num_format};
|
||||||
use uucore::{format_usage, help_about, help_usage};
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
@ -119,16 +119,31 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let result = match (first.number, increment.number, last.number) {
|
let result = match (first.number, increment.number, last.number) {
|
||||||
(Number::Int(first), Number::Int(increment), last) => {
|
(Number::Int(first), Number::Int(increment), last) => {
|
||||||
let last = last.round_towards(&first);
|
let last = last.round_towards(&first);
|
||||||
|
let format = match options.format {
|
||||||
|
Some(f) => {
|
||||||
|
let f = Format::<num_format::SignedInt>::parse(f)?;
|
||||||
|
Some(f)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
print_seq_integers(
|
print_seq_integers(
|
||||||
(first, increment, last),
|
(first, increment, last),
|
||||||
&options.separator,
|
&options.separator,
|
||||||
&options.terminator,
|
&options.terminator,
|
||||||
options.equal_width,
|
options.equal_width,
|
||||||
padding,
|
padding,
|
||||||
options.format,
|
format,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(first, increment, last) => print_seq(
|
(first, increment, last) => {
|
||||||
|
let format = match options.format {
|
||||||
|
Some(f) => {
|
||||||
|
let f = Format::<num_format::Float>::parse(f)?;
|
||||||
|
Some(f)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
print_seq(
|
||||||
(
|
(
|
||||||
first.into_extended_big_decimal(),
|
first.into_extended_big_decimal(),
|
||||||
increment.into_extended_big_decimal(),
|
increment.into_extended_big_decimal(),
|
||||||
|
@ -139,8 +154,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
&options.terminator,
|
&options.terminator,
|
||||||
options.equal_width,
|
options.equal_width,
|
||||||
padding,
|
padding,
|
||||||
options.format,
|
format,
|
||||||
),
|
)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
@ -244,7 +260,7 @@ fn print_seq(
|
||||||
terminator: &str,
|
terminator: &str,
|
||||||
pad: bool,
|
pad: bool,
|
||||||
padding: usize,
|
padding: usize,
|
||||||
format: Option<&str>,
|
format: Option<Format<num_format::Float>>,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
let stdout = stdout();
|
let stdout = stdout();
|
||||||
let mut stdout = stdout.lock();
|
let mut stdout = stdout.lock();
|
||||||
|
@ -268,10 +284,16 @@ fn print_seq(
|
||||||
// it as a string and ultimately writing to `stdout`. We
|
// it as a string and ultimately writing to `stdout`. We
|
||||||
// shouldn't have to do so much converting back and forth via
|
// shouldn't have to do so much converting back and forth via
|
||||||
// strings.
|
// strings.
|
||||||
match format {
|
match &format {
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
let s = format!("{value}");
|
let float = match &value {
|
||||||
printf(f, &[FormatArgument::String(s)])?;
|
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)?,
|
None => write_value_float(&mut stdout, &value, padding, largest_dec)?,
|
||||||
}
|
}
|
||||||
|
@ -306,7 +328,7 @@ fn print_seq_integers(
|
||||||
terminator: &str,
|
terminator: &str,
|
||||||
pad: bool,
|
pad: bool,
|
||||||
padding: usize,
|
padding: usize,
|
||||||
format: Option<&str>,
|
format: Option<Format<num_format::SignedInt>>,
|
||||||
) -> UResult<()> {
|
) -> UResult<()> {
|
||||||
let stdout = stdout();
|
let stdout = stdout();
|
||||||
let mut stdout = stdout.lock();
|
let mut stdout = stdout.lock();
|
||||||
|
@ -324,10 +346,16 @@ fn print_seq_integers(
|
||||||
// the current value and writes the result to `stdout`.
|
// the current value and writes the result to `stdout`.
|
||||||
//
|
//
|
||||||
// TODO See similar comment about formatting in `print_seq()`.
|
// TODO See similar comment about formatting in `print_seq()`.
|
||||||
match format {
|
match &format {
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
let s = format!("{value}");
|
let int = match &value {
|
||||||
printf(f, &[FormatArgument::String(s)])?;
|
ExtendedBigInt::BigInt(bi) => bi.to_i64().unwrap(),
|
||||||
|
ExtendedBigInt::Infinity => todo!(),
|
||||||
|
ExtendedBigInt::MinusInfinity => todo!(),
|
||||||
|
ExtendedBigInt::MinusZero => todo!(),
|
||||||
|
ExtendedBigInt::Nan => todo!(),
|
||||||
|
};
|
||||||
|
f.fmt(&mut stdout, int)?;
|
||||||
}
|
}
|
||||||
None => write_value_int(&mut stdout, &value, padding, pad)?,
|
None => write_value_int(&mut stdout, &value, padding, pad)?,
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
//! parsing errors occur during writing.
|
//! parsing errors occur during writing.
|
||||||
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
|
// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety
|
||||||
|
|
||||||
mod spec;
|
|
||||||
pub mod num_format;
|
pub mod num_format;
|
||||||
|
mod spec;
|
||||||
|
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -22,6 +22,8 @@ use std::{
|
||||||
|
|
||||||
use crate::error::UError;
|
use crate::error::UError;
|
||||||
|
|
||||||
|
use self::num_format::Formatter;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FormatError {
|
pub enum FormatError {
|
||||||
SpecError,
|
SpecError,
|
||||||
|
@ -33,6 +35,12 @@ pub enum FormatError {
|
||||||
impl Error for FormatError {}
|
impl Error for FormatError {}
|
||||||
impl UError for FormatError {}
|
impl UError for FormatError {}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for FormatError {
|
||||||
|
fn from(value: std::io::Error) -> Self {
|
||||||
|
FormatError::IoError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for FormatError {
|
impl Display for FormatError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
// TODO: Be more precise about these
|
// TODO: Be more precise about these
|
||||||
|
@ -181,3 +189,65 @@ pub fn sprintf<'a>(
|
||||||
printf_writer(&mut writer, format_string, arguments)?;
|
printf_writer(&mut writer, format_string, arguments)?;
|
||||||
Ok(writer)
|
Ok(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A parsed format for a single float value
|
||||||
|
///
|
||||||
|
/// This is used by `seq`. It can be constructed with [`FloatFormat::parse`]
|
||||||
|
/// and can write a value with [`FloatFormat::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_iter(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::Text(t) => prefix.extend_from_slice(&t),
|
||||||
|
FormatItem::Char(c) => prefix.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(spec) = spec else {
|
||||||
|
return Err(FormatError::SpecError);
|
||||||
|
};
|
||||||
|
|
||||||
|
let formatter = F::try_from_spec(spec)?;
|
||||||
|
|
||||||
|
let mut suffix = Vec::new();
|
||||||
|
for item in &mut iter {
|
||||||
|
match item? {
|
||||||
|
FormatItem::Spec(_) => {
|
||||||
|
return Err(FormatError::SpecError);
|
||||||
|
}
|
||||||
|
FormatItem::Text(t) => suffix.extend_from_slice(&t),
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use super::FormatError;
|
use super::{
|
||||||
|
spec::{CanAsterisk, Spec},
|
||||||
|
FormatError,
|
||||||
|
};
|
||||||
|
|
||||||
pub trait Formatter {
|
pub trait Formatter {
|
||||||
type Input;
|
type Input;
|
||||||
fn fmt(&self, writer: impl Write, x: Self::Input) -> Result<(), FormatError>;
|
fn fmt(&self, writer: impl Write, x: Self::Input) -> std::io::Result<()>;
|
||||||
|
fn try_from_spec(s: Spec) -> Result<Self, FormatError>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -64,14 +70,13 @@ pub struct SignedInt {
|
||||||
impl Formatter for SignedInt {
|
impl Formatter for SignedInt {
|
||||||
type Input = i64;
|
type Input = i64;
|
||||||
|
|
||||||
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> {
|
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> {
|
||||||
if x >= 0 {
|
if x >= 0 {
|
||||||
match self.positive_sign {
|
match self.positive_sign {
|
||||||
PositiveSign::None => Ok(()),
|
PositiveSign::None => Ok(()),
|
||||||
PositiveSign::Plus => write!(writer, "+"),
|
PositiveSign::Plus => write!(writer, "+"),
|
||||||
PositiveSign::Space => write!(writer, " "),
|
PositiveSign::Space => write!(writer, " "),
|
||||||
}
|
}?;
|
||||||
.map_err(FormatError::IoError)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.alignment {
|
match self.alignment {
|
||||||
|
@ -79,7 +84,29 @@ impl Formatter for SignedInt {
|
||||||
NumberAlignment::RightSpace => write!(writer, "{x:>width$}", width = self.width),
|
NumberAlignment::RightSpace => write!(writer, "{x:>width$}", width = self.width),
|
||||||
NumberAlignment::RightZero => write!(writer, "{x:0>width$}", width = self.width),
|
NumberAlignment::RightZero => write!(writer, "{x:0>width$}", width = self.width),
|
||||||
}
|
}
|
||||||
.map_err(FormatError::IoError)
|
}
|
||||||
|
|
||||||
|
fn try_from_spec(s: Spec) -> Result<Self, FormatError> {
|
||||||
|
let Spec::SignedInt {
|
||||||
|
width,
|
||||||
|
positive_sign,
|
||||||
|
alignment,
|
||||||
|
} = s
|
||||||
|
else {
|
||||||
|
return Err(FormatError::SpecError);
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = match width {
|
||||||
|
Some(CanAsterisk::Fixed(x)) => x,
|
||||||
|
None => 0,
|
||||||
|
Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
width,
|
||||||
|
positive_sign,
|
||||||
|
alignment,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +119,7 @@ pub struct UnsignedInt {
|
||||||
impl Formatter for UnsignedInt {
|
impl Formatter for UnsignedInt {
|
||||||
type Input = u64;
|
type Input = u64;
|
||||||
|
|
||||||
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> {
|
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> {
|
||||||
let s = match self.variant {
|
let s = match self.variant {
|
||||||
UnsignedIntVariant::Decimal => format!("{x}"),
|
UnsignedIntVariant::Decimal => format!("{x}"),
|
||||||
UnsignedIntVariant::Octal(Prefix::No) => format!("{x:o}"),
|
UnsignedIntVariant::Octal(Prefix::No) => format!("{x:o}"),
|
||||||
|
@ -116,7 +143,29 @@ impl Formatter for UnsignedInt {
|
||||||
NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width),
|
NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width),
|
||||||
NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width),
|
NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width),
|
||||||
}
|
}
|
||||||
.map_err(FormatError::IoError)
|
}
|
||||||
|
|
||||||
|
fn try_from_spec(s: Spec) -> Result<Self, FormatError> {
|
||||||
|
let Spec::UnsignedInt {
|
||||||
|
variant,
|
||||||
|
width,
|
||||||
|
alignment,
|
||||||
|
} = s
|
||||||
|
else {
|
||||||
|
return Err(FormatError::SpecError);
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = match width {
|
||||||
|
Some(CanAsterisk::Fixed(x)) => x,
|
||||||
|
None => 0,
|
||||||
|
Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
width,
|
||||||
|
variant,
|
||||||
|
alignment,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,14 +196,13 @@ impl Default for Float {
|
||||||
impl Formatter for Float {
|
impl Formatter for Float {
|
||||||
type Input = f64;
|
type Input = f64;
|
||||||
|
|
||||||
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> {
|
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> {
|
||||||
if x.is_sign_positive() {
|
if x.is_sign_positive() {
|
||||||
match self.positive_sign {
|
match self.positive_sign {
|
||||||
PositiveSign::None => Ok(()),
|
PositiveSign::None => Ok(()),
|
||||||
PositiveSign::Plus => write!(writer, "+"),
|
PositiveSign::Plus => write!(writer, "+"),
|
||||||
PositiveSign::Space => write!(writer, " "),
|
PositiveSign::Space => write!(writer, " "),
|
||||||
}
|
}?;
|
||||||
.map_err(FormatError::IoError)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let s = match self.variant {
|
let s = match self.variant {
|
||||||
|
@ -177,7 +225,46 @@ impl Formatter for Float {
|
||||||
NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width),
|
NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width),
|
||||||
NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width),
|
NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width),
|
||||||
}
|
}
|
||||||
.map_err(FormatError::IoError)
|
}
|
||||||
|
|
||||||
|
fn try_from_spec(s: Spec) -> Result<Self, FormatError>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let Spec::Float {
|
||||||
|
variant,
|
||||||
|
case,
|
||||||
|
force_decimal,
|
||||||
|
width,
|
||||||
|
positive_sign,
|
||||||
|
alignment,
|
||||||
|
precision,
|
||||||
|
} = s
|
||||||
|
else {
|
||||||
|
return Err(FormatError::SpecError);
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = match width {
|
||||||
|
Some(CanAsterisk::Fixed(x)) => x,
|
||||||
|
None => 0,
|
||||||
|
Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError),
|
||||||
|
};
|
||||||
|
|
||||||
|
let precision = match precision {
|
||||||
|
Some(CanAsterisk::Fixed(x)) => x,
|
||||||
|
None => 0,
|
||||||
|
Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
variant,
|
||||||
|
case,
|
||||||
|
force_decimal,
|
||||||
|
width,
|
||||||
|
positive_sign,
|
||||||
|
alignment,
|
||||||
|
precision,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -252,6 +252,7 @@ impl Spec {
|
||||||
alignment,
|
alignment,
|
||||||
}
|
}
|
||||||
.fmt(writer, *i)
|
.fmt(writer, *i)
|
||||||
|
.map_err(FormatError::IoError)
|
||||||
}
|
}
|
||||||
&Spec::UnsignedInt {
|
&Spec::UnsignedInt {
|
||||||
variant,
|
variant,
|
||||||
|
@ -271,6 +272,7 @@ impl Spec {
|
||||||
alignment,
|
alignment,
|
||||||
}
|
}
|
||||||
.fmt(writer, *i)
|
.fmt(writer, *i)
|
||||||
|
.map_err(FormatError::IoError)
|
||||||
}
|
}
|
||||||
&Spec::Float {
|
&Spec::Float {
|
||||||
variant,
|
variant,
|
||||||
|
@ -299,6 +301,7 @@ impl Spec {
|
||||||
precision,
|
precision,
|
||||||
}
|
}
|
||||||
.fmt(writer, *f)
|
.fmt(writer, *f)
|
||||||
|
.map_err(FormatError::IoError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue