fix(numfmt): format output on error messages

This commit is contained in:
FidelSch
2026-03-04 09:28:11 +01:00
committed by Daniel Hofstetter
parent 30434cb789
commit dd6b88b264
3 changed files with 29 additions and 3 deletions
+15 -1
View File
@@ -452,6 +452,20 @@ fn format_string(
))
}
/// Encodes a byte slice as a string, representing non-UTF-8 bytes as octal escapes.
/// Used to safely format invalid input in error messages.
pub(crate) fn escape_line(line: &[u8]) -> String {
line.iter()
.map(|&b| {
if b.is_ascii_graphic() || b.is_ascii_whitespace() {
(b as char).to_string()
} else {
format!("\\{b:03o}")
}
})
.collect()
}
fn split_bytes<'a>(input: &'a [u8], delim: &'a [u8]) -> impl Iterator<Item = &'a [u8]> {
let mut remainder = Some(input);
std::iter::from_fn(move || {
@@ -488,7 +502,7 @@ pub fn write_formatted_with_delimiter<W: std::io::Write>(
if field_selected {
// Field must be valid UTF-8 for numeric conversion
let field_str = std::str::from_utf8(field)
.map_err(|_| translate!("numfmt-error-invalid-number", "input" => String::from_utf8_lossy(field).into_owned().quote()))?
.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();
+4 -2
View File
@@ -4,7 +4,7 @@
// file that was distributed with this source code.
use crate::errors::NumfmtError;
use crate::format::{write_formatted_with_delimiter, write_formatted_with_whitespace};
use crate::format::{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, HEADER, HEADER_DEFAULT, INVALID, InvalidModes, NUMBER,
@@ -99,7 +99,9 @@ fn write_line<W: std::io::Write>(
// Whitespace mode requires valid UTF-8
match std::str::from_utf8(&line) {
Ok(s) => write_formatted_with_whitespace(writer, s, options, eol),
Err(_) => Err(translate!("numfmt-error-invalid-input")),
Err(_) => Err(
translate!("numfmt-error-invalid-number", "input" => escape_line(&line).quote()),
),
}
};
+10
View File
@@ -1272,3 +1272,13 @@ fn test_null_byte_input_multiline() {
.succeeds()
.stdout_is("1000\n3000");
}
#[test]
fn test_invalid_utf8_input() {
// 0xFF is invalid UTF-8
new_ucmd!()
.pipe_in([b'1', b'0', b'\n', b'\xFF'])
.fails_with_code(2)
.stdout_is("10\n")
.stderr_is("numfmt: invalid number: '\\377'\n");
}