uucore: format: Make Formatter a generic

Using an associated type in Formatter trait was quite nice, but, in
a follow-up change, we'd like to pass a _reference_ to the Float
Formatter, while just passing i64/u64 as a value to the Int
formatters. Associated type doesn't allow for that, so we turn
it into a generic instead.

This makes Format<> a bit more complicated though, as we need
to specify both the Formatter, _and_ the type to be formatted.
This commit is contained in:
Nicolas Boichat 2025-03-05 20:35:53 +01:00 committed by Sylvestre Ledru
parent 2103646ff7
commit 69164688ad
4 changed files with 21 additions and 24 deletions

View file

@ -12,7 +12,7 @@ use crate::csplit_error::CsplitError;
/// format.
pub struct SplitName {
prefix: Vec<u8>,
format: Format<UnsignedInt>,
format: Format<UnsignedInt, u64>,
}
impl SplitName {
@ -52,7 +52,7 @@ impl SplitName {
None => format!("%0{n_digits}u"),
};
let format = match Format::<UnsignedInt>::parse(format_string) {
let format = match Format::<UnsignedInt, u64>::parse(format_string) {
Ok(format) => Ok(format),
Err(FormatError::TooManySpecs(_)) => Err(CsplitError::SuffixFormatTooManyPercents),
Err(_) => Err(CsplitError::SuffixFormatIncorrect),

View file

@ -149,7 +149,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let format = options
.format
.map(Format::<num_format::Float>::parse)
.map(Format::<num_format::Float, f64>::parse)
.transpose()?;
let result = print_seq(
@ -258,7 +258,7 @@ fn print_seq(
terminator: &str,
pad: bool,
padding: usize,
format: Option<&Format<num_format::Float>>,
format: Option<&Format<num_format::Float, f64>>,
) -> std::io::Result<()> {
let stdout = stdout().lock();
let mut stdout = BufWriter::new(stdout);

View file

@ -2,6 +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
//! `printf`-style formatting
//!
@ -45,6 +46,7 @@ use std::{
error::Error,
fmt::Display,
io::{stdout, Write},
marker::PhantomData,
ops::ControlFlow,
};
@ -308,20 +310,21 @@ pub fn sprintf<'a>(
Ok(writer)
}
/// A parsed format for a single float value
/// A parsed format for a single numerical value of type T
///
/// This is used by `seq`. It can be constructed with [`Format::parse`]
/// This is used by `seq` and `csplit`. 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> {
pub struct Format<F: Formatter<T>, T> {
prefix: Vec<u8>,
suffix: Vec<u8>,
formatter: F,
_marker: PhantomData<T>,
}
impl<F: Formatter> Format<F> {
impl<F: Formatter<T>, T> Format<F, T> {
pub fn parse(format_string: impl AsRef<[u8]>) -> Result<Self, FormatError> {
let mut iter = parse_spec_only(format_string.as_ref());
@ -362,10 +365,11 @@ impl<F: Formatter> Format<F> {
prefix,
suffix,
formatter,
_marker: PhantomData,
})
}
pub fn fmt(&self, mut w: impl Write, f: F::Input) -> std::io::Result<()> {
pub fn fmt(&self, mut w: impl Write, f: T) -> std::io::Result<()> {
w.write_all(&self.prefix)?;
self.formatter.fmt(&mut w, f)?;
w.write_all(&self.suffix)?;

View file

@ -13,9 +13,8 @@ use super::{
FormatError,
};
pub trait Formatter {
type Input;
fn fmt(&self, writer: impl Write, x: Self::Input) -> std::io::Result<()>;
pub trait Formatter<T> {
fn fmt(&self, writer: impl Write, x: T) -> std::io::Result<()>;
fn try_from_spec(s: Spec) -> Result<Self, FormatError>
where
Self: Sized;
@ -75,10 +74,8 @@ pub struct SignedInt {
pub alignment: NumberAlignment,
}
impl Formatter for SignedInt {
type Input = i64;
fn fmt(&self, writer: impl Write, x: Self::Input) -> std::io::Result<()> {
impl Formatter<i64> for SignedInt {
fn fmt(&self, writer: impl Write, x: i64) -> std::io::Result<()> {
let s = if self.precision > 0 {
format!("{:0>width$}", x.abs(), width = self.precision)
} else {
@ -129,10 +126,8 @@ pub struct UnsignedInt {
pub alignment: NumberAlignment,
}
impl Formatter for UnsignedInt {
type Input = u64;
fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> {
impl Formatter<u64> for UnsignedInt {
fn fmt(&self, mut writer: impl Write, x: u64) -> std::io::Result<()> {
let mut s = match self.variant {
UnsignedIntVariant::Decimal => format!("{x}"),
UnsignedIntVariant::Octal(_) => format!("{x:o}"),
@ -236,10 +231,8 @@ impl Default for Float {
}
}
impl Formatter for Float {
type Input = f64;
fn fmt(&self, writer: impl Write, f: Self::Input) -> std::io::Result<()> {
impl Formatter<f64> for Float {
fn fmt(&self, writer: impl Write, f: f64) -> std::io::Result<()> {
let x = f.abs();
let s = if x.is_finite() {
match self.variant {