diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index f24726009..1d9b7247d 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -13,8 +13,8 @@ use std::io::Write; use std::sync::mpsc; use std::time::Duration; -use uucore::error::UResult; use uucore::format::sprintf; +use uucore::{error::UResult, format::FormatArgument}; use crate::numbers::{to_magnitude_and_suffix, SuffixType}; @@ -152,7 +152,9 @@ 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()])?; + // TODO: remove unwrap and make FormatError implement UError + let duration_str = sprintf("%g", &[FormatArgument::Float(duration)])?; + 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 diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 36b4c3453..6e270ec26 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -8,7 +8,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use uucore::error::{UResult, UUsageError}; -use uucore::format::printf; +use uucore::format::{printf, FormatArgument}; use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; @@ -30,12 +30,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format_string = matches .get_one::(options::FORMATSTRING) .ok_or_else(|| UUsageError::new(1, "missing operand"))?; - let values: Vec = match matches.get_many::(options::ARGUMENT) { - Some(s) => s.map(|s| s.to_string()).collect(), + let values: Vec<_> = match matches.get_many::(options::ARGUMENT) { + Some(s) => s.map(|s| FormatArgument::Unparsed(s.to_string())).collect(), None => vec![], }; - printf(format_string, &values[..])?; + printf(format_string, &values)?; Ok(()) } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index f93ced926..217e90428 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -9,7 +9,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use num_traits::Zero; use uucore::error::UResult; -use uucore::format::printf; +use uucore::format::{printf, FormatArgument}; use uucore::{format_usage, help_about, help_usage}; mod error; @@ -144,8 +144,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; match result { Ok(_) => Ok(()), - Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), - Err(e) => Err(e.map_err_context(|| "write error".into())), + _ => todo!(), + // Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), + // Err(e) => Err(e.map_err_context(|| "write error".into())), } } @@ -270,7 +271,7 @@ fn print_seq( match format { Some(f) => { let s = format!("{value}"); - printf(f, &[s])?; + printf(f, &[FormatArgument::String(s)])?; } None => write_value_float(&mut stdout, &value, padding, largest_dec)?, } @@ -326,7 +327,7 @@ fn print_seq_integers( match format { Some(f) => { let s = format!("{value}"); - printf(f, &[s])?; + printf(f, &[FormatArgument::String(s)])?; } None => write_value_int(&mut stdout, &value, padding, pad)?, } diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 133050954..1d0d43782 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -8,16 +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 = "format")] -pub mod format; -#[cfg(feature = "memo")] -pub mod memo; #[cfg(feature = "quoting-style")] pub mod quoting_style; #[cfg(feature = "ranges")] @@ -26,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")] diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index abd92011c..ebb1cc360 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -3,7 +3,7 @@ //! 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. -//! +//! //! In addition to the [`printf`] and [`sprintf`] functions, we expose the //! [`Format`] struct, which represents a parsed format string. This reduces //! the need for parsing a format string multiple times and assures that no @@ -14,8 +14,15 @@ mod spec; use spec::Spec; -use std::io::{stdout, Write}; +use std::{ + error::Error, + fmt::Display, + io::{stdout, Write}, +}; +use crate::error::UError; + +#[derive(Debug)] pub enum FormatError { SpecError, IoError(std::io::Error), @@ -23,6 +30,21 @@ pub enum FormatError { InvalidArgument(FormatArgument), } +impl Error for FormatError {} +impl UError for FormatError {} + +impl Display for FormatError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO: Be more precise about these + match self { + FormatError::SpecError => write!(f, "invalid spec"), + FormatError::IoError(_) => write!(f, "io error"), + FormatError::NoMoreArguments => write!(f, "no more arguments"), + FormatError::InvalidArgument(_) => write!(f, "invalid argument"), + } + } +} + /// A single item to format enum FormatItem { /// A format specifier @@ -30,21 +52,28 @@ enum FormatItem { /// Some plain text Text(Vec), /// A single character - /// + /// /// Added in addition to `Text` as an optimization. Char(u8), } +#[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), } impl FormatItem { - fn write<'a>(&self, mut writer: impl Write, args: &mut impl Iterator) -> Result<(), FormatError> { + fn write<'a>( + &self, + mut writer: impl Write, + args: &mut impl Iterator, + ) -> Result<(), FormatError> { match self { FormatItem::Spec(spec) => spec.write(writer, args), FormatItem::Text(bytes) => writer.write_all(bytes).map_err(FormatError::IoError), @@ -110,13 +139,20 @@ fn parse_iter(fmt: &[u8]) -> impl Iterator) -> Result<(), FormatError> { +pub fn printf<'a>( + format_string: impl AsRef<[u8]>, + arguments: impl IntoIterator, +) -> Result<(), FormatError> { printf_writer(stdout(), format_string, arguments) } -fn printf_writer(mut writer: impl Write, format_string: &[u8], args: impl IntoIterator) -> Result<(), FormatError> { +fn printf_writer<'a>( + mut writer: impl Write, + format_string: impl AsRef<[u8]>, + args: impl IntoIterator, +) -> Result<(), FormatError> { let mut args = args.into_iter(); - for item in parse_iter(format_string) { + for item in parse_iter(format_string.as_ref()) { item?.write(&mut writer, &mut args)?; } Ok(()) @@ -137,7 +173,10 @@ fn printf_writer(mut writer: impl Write, format_string: &[u8], args: impl IntoIt /// let s = sprintf("hello %s", &["world".to_string()]).unwrap(); /// assert_eq!(s, "hello world".to_string()); /// ``` -pub fn sprintf(format_string: &[u8], arguments: impl IntoIterator) -> Result, FormatError> { +pub fn sprintf<'a>( + format_string: impl AsRef<[u8]>, + arguments: impl IntoIterator, +) -> Result, FormatError> { let mut writer = Vec::new(); printf_writer(&mut writer, format_string, arguments)?; Ok(writer) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index d1786c3d3..e66cad32d 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -257,7 +257,7 @@ impl Spec { pub fn write<'a>( &self, mut writer: impl Write, - mut args: impl Iterator, + mut args: impl Iterator, ) -> Result<(), FormatError> { match self { &Spec::Char { width, align_left } => { @@ -265,7 +265,7 @@ impl Spec { let arg = next_arg(&mut args)?; match arg { FormatArgument::Char(c) => write_padded(writer, c, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg)), + _ => Err(FormatError::InvalidArgument(arg.clone())), } } &Spec::String { width, align_left } => { @@ -273,7 +273,7 @@ impl Spec { let arg = next_arg(&mut args)?; match arg { FormatArgument::String(s) => write_padded(writer, s, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg)), + _ => Err(FormatError::InvalidArgument(arg.clone())), } } &Spec::SignedInt { @@ -285,10 +285,10 @@ impl Spec { let arg = next_arg(&mut args)?; let FormatArgument::SignedInt(i) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; - if i >= 0 { + if *i >= 0 { match positive_sign { PositiveSign::None => Ok(()), PositiveSign::Plus => write!(writer, "+"), @@ -313,7 +313,7 @@ impl Spec { let arg = next_arg(args)?; let FormatArgument::SignedInt(i) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; let s = match variant { @@ -355,7 +355,7 @@ impl Spec { let arg = next_arg(args)?; let FormatArgument::Float(f) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; if f.is_sign_positive() { @@ -369,16 +369,16 @@ impl Spec { let s = match variant { FloatVariant::Decimal => { - format_float_decimal(f, precision, case, force_decimal) + format_float_decimal(*f, precision, case, force_decimal) } FloatVariant::Scientific => { - format_float_scientific(f, precision, case, force_decimal) + format_float_scientific(*f, precision, case, force_decimal) } FloatVariant::Shortest => { - format_float_shortest(f, precision, case, force_decimal) + format_float_shortest(*f, precision, case, force_decimal) } FloatVariant::Hexadecimal => { - format_float_hexadecimal(f, precision, case, force_decimal) + format_float_hexadecimal(*f, precision, case, force_decimal) } }; @@ -490,7 +490,7 @@ fn format_float_hexadecimal( let mut s = match (precision, force_decimal) { (0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"), (0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"), - _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}") + _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}"), }; if case == Case::Uppercase { @@ -500,29 +500,29 @@ fn format_float_hexadecimal( return s; } -fn resolve_asterisk( +fn resolve_asterisk<'a>( option: Option>, - args: impl Iterator, + args: impl Iterator, ) -> Result, FormatError> { Ok(match option { None => None, Some(CanAsterisk::Asterisk) => { let arg = next_arg(args)?; match arg { - FormatArgument::UnsignedInt(u) => match usize::try_from(u) { + FormatArgument::UnsignedInt(u) => match usize::try_from(*u) { Ok(u) => Some(u), - Err(_) => return Err(FormatError::InvalidArgument(arg)), + Err(_) => return Err(FormatError::InvalidArgument(arg.clone())), }, - _ => return Err(FormatError::InvalidArgument(arg)), + _ => return Err(FormatError::InvalidArgument(arg.clone())), } } Some(CanAsterisk::Fixed(w)) => Some(w), }) } -fn next_arg( - mut arguments: impl Iterator, -) -> Result { +fn next_arg<'a>( + mut arguments: impl Iterator, +) -> Result<&'a FormatArgument, FormatError> { arguments.next().ok_or(FormatError::NoMoreArguments) } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 0540275ee..af8668ef0 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -37,16 +37,14 @@ pub use crate::parser::shortcut_value_parser; pub use crate::features::backup_control; #[cfg(feature = "encoding")] pub use crate::features::encoding; +#[cfg(feature = "format")] +pub use crate::features::format; #[cfg(feature = "fs")] pub use crate::features::fs; #[cfg(feature = "fsext")] pub use crate::features::fsext; #[cfg(feature = "lines")] pub use crate::features::lines; -#[cfg(feature = "format")] -pub use crate::features::format; -#[cfg(feature = "memo")] -pub use crate::features::memo; #[cfg(feature = "quoting-style")] pub use crate::features::quoting_style; #[cfg(feature = "ranges")]