diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 61c45b2fa8b..a8341353f0f 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -787,12 +787,32 @@ fn split_bytes<'a>(input: &'a [u8], delim: &'a [u8]) -> impl Iterator( +#[derive(Debug)] +pub(crate) enum WriteFormattedError { + Format(String), + Io(std::io::Error), +} + +impl From for WriteFormattedError { + fn from(error: String) -> Self { + Self::Format(error) + } +} + +impl From for WriteFormattedError { + fn from(error: std::io::Error) -> Self { + Self::Io(error) + } +} + +type WriteFormattedResult = std::result::Result; + +pub(crate) fn write_formatted_with_delimiter( writer: &mut W, input: &[u8], options: &NumfmtOptions, eol: Option, -) -> Result<()> { +) -> WriteFormattedResult<()> { let delimiter = options.delimiter.as_deref().unwrap(); for (n, field) in (1..).zip(split_bytes(input, delimiter)) { @@ -800,7 +820,7 @@ pub fn write_formatted_with_delimiter( // add delimiter before second and subsequent fields if n > 1 { - writer.write_all(delimiter).unwrap(); + writer.write_all(delimiter)?; } if field_selected { @@ -809,26 +829,26 @@ pub fn write_formatted_with_delimiter( .map_err(|_| translate!("numfmt-error-invalid-number", "input" => escape_line(field).quote()))? .trim_start(); let formatted = format_string(field_str, options, None)?; - writer.write_all(formatted.as_bytes()).unwrap(); + writer.write_all(formatted.as_bytes())?; } else { // add unselected field without conversion - writer.write_all(field).unwrap(); + writer.write_all(field)?; } } if let Some(eol) = eol { - writer.write_all(&[eol]).unwrap(); + writer.write_all(&[eol])?; } Ok(()) } -pub fn write_formatted_with_whitespace( +pub(crate) fn write_formatted_with_whitespace( writer: &mut W, s: &str, options: &NumfmtOptions, eol: Option, -) -> Result<()> { +) -> WriteFormattedResult<()> { for (n, (prefix, field)) in (1..).zip(WhitespaceSplitter { s: Some(s), options, @@ -840,7 +860,7 @@ pub fn write_formatted_with_whitespace( // add delimiter before second and subsequent fields let prefix = if n > 1 { - writer.write_all(b" ").unwrap(); + writer.write_all(b" ")?; &prefix[1..] } else { prefix @@ -853,23 +873,23 @@ pub fn write_formatted_with_whitespace( }; let formatted = format_string(field, options, implicit_padding)?; - writer.write_all(formatted.as_bytes()).unwrap(); + writer.write_all(formatted.as_bytes())?; } else { // the -z option converts an initial \n into a space let prefix = if options.zero_terminated && prefix.starts_with('\n') { - writer.write_all(b" ").unwrap(); + writer.write_all(b" ")?; &prefix[1..] } else { prefix }; // add unselected field without conversion - writer.write_all(prefix.as_bytes()).unwrap(); - writer.write_all(field.as_bytes()).unwrap(); + writer.write_all(prefix.as_bytes())?; + writer.write_all(field.as_bytes())?; } } if let Some(eol) = eol { - writer.write_all(&[eol]).unwrap(); + writer.write_all(&[eol])?; } Ok(()) diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 080e0200e22..c2a9f65f76b 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -5,7 +5,10 @@ // spell-checker:ignore behavior use crate::errors::NumfmtError; -use crate::format::{escape_line, write_formatted_with_delimiter, write_formatted_with_whitespace}; +use crate::format::{ + WriteFormattedError, escape_line, write_formatted_with_delimiter, + write_formatted_with_whitespace, +}; use crate::options::{ DEBUG, DELIMITER, FIELD, FIELD_DEFAULT, FORMAT, FROM, FROM_DEFAULT, FROM_UNIT, FROM_UNIT_DEFAULT, FormatOptions, GROUPING, HEADER, HEADER_DEFAULT, INVALID, InvalidModes, @@ -19,7 +22,7 @@ use std::io::{BufRead, Write as _, stderr}; use std::str::FromStr; use uucore::display::Quotable; -use uucore::error::UResult; +use uucore::error::{FromIo, UResult}; use uucore::i18n::decimal::locale_grouping_separator; use uucore::parser::parse_size::{IEC_BASES, SI_BASES}; use uucore::parser::shortcut_value_parser::ShortcutValueParser; @@ -46,6 +49,12 @@ fn is_scientific(input: &[u8]) -> bool { false } +fn write_output(writer: &mut W, buf: &[u8]) -> UResult<()> { + writer + .write_all(buf) + .map_err_context(|| "write error".into()) +} + /// Format a single line and write it, handling `--invalid` error modes. /// /// Returns `true` if the line contained invalid input (only possible in @@ -74,22 +83,28 @@ fn format_and_write( match std::str::from_utf8(line) { Ok(s) => { if is_scientific(s.as_bytes()) { - Err(format!( + Err(WriteFormattedError::Format(format!( "invalid suffix in input: '{}'", String::from_utf8_lossy(line) - )) + ))) } else { write_formatted_with_whitespace(dest, s, options, eol) } } - Err(_) => Err(translate!( + Err(_) => Err(WriteFormattedError::Format(translate!( "numfmt-error-invalid-number", "input" => escape_line(line).quote() - )), + ))), } }; - if let Err(msg) = result { + if let Err(error) = result { + let msg = match error { + WriteFormattedError::Format(msg) => msg, + WriteFormattedError::Io(error) => { + return Err(error.map_err_context(|| "write error".into())); + } + }; match options.invalid { InvalidModes::Abort => { return Err(Box::new(NumfmtError::FormattingError(msg))); @@ -103,15 +118,15 @@ fn format_and_write( InvalidModes::Ignore => {} } // On error, echo the original line unchanged. - writer.write_all(input_line)?; + write_output(writer, input_line)?; if let Some(eol) = eol { - writer.write_all(&[eol])?; + write_output(writer, &[eol])?; } return Ok(true); } if buffer_output { - writer.write_all(&buf)?; + write_output(writer, &buf)?; } Ok(false) } @@ -160,9 +175,9 @@ fn handle_buffer(mut input: R, options: &NumfmtOptions) -> UResult