mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
numfmt: fix empty delimiter and whitespace handling
This commit is contained in:
committed by
Sylvestre Ledru
parent
3745fe7989
commit
1e5eb7c942
@@ -144,16 +144,17 @@ fn detailed_error_message(s: &str, unit: Unit) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_suffix(s: &str, unit: Unit) -> Result<(f64, Option<Suffix>)> {
|
||||
if s.is_empty() {
|
||||
fn parse_suffix(s: &str, unit: Unit, max_whitespace: usize) -> Result<(f64, Option<Suffix>)> {
|
||||
let trimmed = s.trim_end();
|
||||
if trimmed.is_empty() {
|
||||
return Err(translate!("numfmt-error-invalid-number-empty"));
|
||||
}
|
||||
|
||||
let with_i = s.ends_with('i');
|
||||
let with_i = trimmed.ends_with('i');
|
||||
if with_i && ![Unit::Auto, Unit::Iec(true)].contains(&unit) {
|
||||
return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote()));
|
||||
}
|
||||
let mut iter = s.chars();
|
||||
let mut iter = trimmed.chars();
|
||||
if with_i {
|
||||
iter.next_back();
|
||||
}
|
||||
@@ -181,7 +182,18 @@ fn parse_suffix(s: &str, unit: Unit) -> Result<(f64, Option<Suffix>)> {
|
||||
Some((_, true)) => 2,
|
||||
};
|
||||
|
||||
let number = s[..s.len() - suffix_len]
|
||||
let number_part = &trimmed[..trimmed.len() - suffix_len];
|
||||
let number_trimmed = number_part.trim_end();
|
||||
|
||||
// Validate whitespace between number and suffix
|
||||
if suffix.is_some() {
|
||||
let whitespace = number_part.len() - number_trimmed.len();
|
||||
if whitespace > max_whitespace {
|
||||
return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote()));
|
||||
}
|
||||
}
|
||||
|
||||
let number = number_trimmed
|
||||
.parse::<f64>()
|
||||
.map_err(|_| translate!("numfmt-error-invalid-number", "input" => s.quote()))?;
|
||||
|
||||
@@ -238,8 +250,8 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: Unit) -> Result<f64> {
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_from(s: &str, opts: &TransformOptions) -> Result<f64> {
|
||||
let (i, suffix) = parse_suffix(s, opts.from)
|
||||
fn transform_from(s: &str, opts: &TransformOptions, max_whitespace: usize) -> Result<f64> {
|
||||
let (i, suffix) = parse_suffix(s, opts.from, max_whitespace)
|
||||
.map_err(|original| detailed_error_message(s, opts.from).unwrap_or(original))?;
|
||||
let i = i * (opts.from_unit as f64);
|
||||
|
||||
@@ -395,7 +407,11 @@ fn format_string(
|
||||
};
|
||||
|
||||
let number = transform_to(
|
||||
transform_from(source_without_suffix, &options.transform)?,
|
||||
transform_from(
|
||||
source_without_suffix,
|
||||
&options.transform,
|
||||
options.max_whitespace,
|
||||
)?,
|
||||
&options.transform,
|
||||
options.round,
|
||||
precision,
|
||||
@@ -438,6 +454,9 @@ fn split_bytes<'a>(input: &'a [u8], delim: &'a [u8]) -> impl Iterator<Item = &'a
|
||||
let mut remainder = Some(input);
|
||||
std::iter::from_fn(move || {
|
||||
let input = remainder.take()?;
|
||||
if delim.is_empty() {
|
||||
return Some(input);
|
||||
}
|
||||
match input.windows(delim.len()).position(|w| w == delim) {
|
||||
Some(pos) => {
|
||||
remainder = Some(&input[pos + delim.len()..]);
|
||||
|
||||
@@ -261,6 +261,15 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
// Max whitespace between number and suffix: length of separator if provided, default one
|
||||
let max_whitespace = if args.contains_id(UNIT_SEPARATOR)
|
||||
&& args.value_source(UNIT_SEPARATOR) == Some(ValueSource::CommandLine)
|
||||
{
|
||||
unit_separator.len()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let invalid = InvalidModes::from_str(args.get_one::<String>(INVALID).unwrap()).unwrap();
|
||||
|
||||
let zero_terminated = args.get_flag(ZERO_TERMINATED);
|
||||
@@ -276,6 +285,7 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
|
||||
round,
|
||||
suffix,
|
||||
unit_separator,
|
||||
max_whitespace,
|
||||
format,
|
||||
invalid,
|
||||
zero_terminated,
|
||||
@@ -502,6 +512,7 @@ mod tests {
|
||||
round: RoundMethod::Nearest,
|
||||
suffix: None,
|
||||
unit_separator: String::new(),
|
||||
max_whitespace: 1,
|
||||
format: FormatOptions::default(),
|
||||
invalid: InvalidModes::Abort,
|
||||
zero_terminated: false,
|
||||
|
||||
@@ -55,6 +55,7 @@ pub struct NumfmtOptions {
|
||||
pub round: RoundMethod,
|
||||
pub suffix: Option<String>,
|
||||
pub unit_separator: String,
|
||||
pub max_whitespace: usize,
|
||||
pub format: FormatOptions,
|
||||
pub invalid: InvalidModes,
|
||||
pub zero_terminated: bool,
|
||||
|
||||
@@ -1201,3 +1201,51 @@ fn test_debug_warnings() {
|
||||
.stdout_is("4.0K\n")
|
||||
.stderr_is("numfmt: --header ignored with command-line input\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_delimiter_success() {
|
||||
for (args, expected) in [
|
||||
// Single space between number and suffix is allowed by default
|
||||
(&["-d", "", "--from=si", "4.0 K"][..], "4000\n"),
|
||||
// Trailing spaces without suffix are allowed
|
||||
(&["-d", "", "--from=si", "4 "], "4\n"),
|
||||
(&["-d", "", "--from=auto", "2 "], "2\n"),
|
||||
(&["-d", "", "--from=auto", "2 "], "2\n"),
|
||||
// Trailing space after suffix is allowed
|
||||
(&["-d", "", "--from=auto", "2K "], "2000\n"),
|
||||
// Explicit --unit-separator=" " allows single space
|
||||
(
|
||||
&["-d", "", "--from=si", "--unit-separator= ", "1 K"],
|
||||
"1000\n",
|
||||
),
|
||||
(
|
||||
&["-d", "", "--from=iec", "--unit-separator= ", "2 M"],
|
||||
"2097152\n",
|
||||
),
|
||||
] {
|
||||
new_ucmd!().args(args).succeeds().stdout_only(expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_delimiter_multi_char_unit_separator() {
|
||||
// Two-space unit separator allows two spaces between number and suffix
|
||||
new_ucmd!()
|
||||
.args(&["-d", "", "--from=si", "--unit-separator= "])
|
||||
.pipe_in("1 K\n2 M\n3 G\n")
|
||||
.succeeds()
|
||||
.stdout_only("1000\n2000000\n3000000000\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_delimiter_whitespace_rejection() {
|
||||
new_ucmd!()
|
||||
.args(&["-d", "", "--from=auto", "2 K"])
|
||||
.fails_with_code(2)
|
||||
.stderr_contains("invalid suffix in input");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-d", "", "--from=si", "--unit-separator=", "1 K"])
|
||||
.fails_with_code(2)
|
||||
.stderr_contains("invalid suffix in input");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user