From 245e96c439b9b32c446046df6a8e399b868e1bd4 Mon Sep 17 00:00:00 2001 From: arbelonson-source <269032023+arbelonson-source@users.noreply.github.com> Date: Mon, 31 Aug 2026 09:17:09 +0300 Subject: [PATCH] ptx: match GNU's error for a bad --gap-size/--width value --gap-size and --width validated their values with clap's own value_parser!(u64).range(1..), so: - a negative value passed as its own argument (`-g -5`, not the attached `-g-5`) was rejected as an unrecognized flag instead of being read as the option's value (still ultimately invalid, since GNU rejects negative values here too, but with the wrong message); - an invalid value -- unparseable, zero, negative, or overflowing -- produced clap's own generic wording instead of GNU's uniform 'invalid gap width: ...'/'invalid line width: ...', which (unlike nl's --number-width fixed in #14314) does not distinguish an overflow from any other kind of invalid value. AI-assisted-by: Claude Opus 5, via Claude Code --- src/uu/ptx/locales/en-US.ftl | 1 + src/uu/ptx/locales/fr-FR.ftl | 1 + src/uu/ptx/src/ptx.rs | 27 ++++++++++++++++++++------- tests/by-util/test_ptx.rs | 27 +++++++++++++++++++++++++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/uu/ptx/locales/en-US.ftl b/src/uu/ptx/locales/en-US.ftl index c9d991c9bf9..e03a1768c26 100644 --- a/src/uu/ptx/locales/en-US.ftl +++ b/src/uu/ptx/locales/en-US.ftl @@ -31,3 +31,4 @@ ptx-error-write-failed = write failed ptx-error-extra-operand = extra operand { $operand } ptx-error-empty-regexp = A regular expression cannot match a length zero string ptx-error-invalid-regexp = Invalid regexp: { $error } +ptx-error-invalid-number = invalid { $kind }: '{ $value }' diff --git a/src/uu/ptx/locales/fr-FR.ftl b/src/uu/ptx/locales/fr-FR.ftl index 743694e227c..a9a0430158c 100644 --- a/src/uu/ptx/locales/fr-FR.ftl +++ b/src/uu/ptx/locales/fr-FR.ftl @@ -30,3 +30,4 @@ ptx-error-write-failed = échec de l'écriture ptx-error-extra-operand = opérande supplémentaire { $operand } ptx-error-empty-regexp = Une expression régulière ne peut pas correspondre à une chaîne de longueur zéro ptx-error-invalid-regexp = Expression régulière invalide : { $error } +ptx-error-invalid-number = { $kind } invalide : '{ $value }' diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 759ddd2fdf5..598ca8a11d9 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -14,7 +14,7 @@ use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Read, Write, stdin, stdout}; use std::path::Path; -use clap::{Arg, ArgAction, Command, value_parser}; +use clap::{Arg, ArgAction, Command}; use regex::Regex; use rustc_hash::FxHashSet; use uucore::display::Quotable; @@ -200,6 +200,19 @@ struct WordRef { char_start: usize, } +/// Parses `--gap-size`/`--width`'s value the way GNU does: a plain positive +/// integer, with any other string -- unparseable, zero, or overflowing -- +/// reported the same way as any other. +fn parse_ptx_number(value: &str, kind: &'static str) -> UResult { + match value.parse::() { + Ok(n) if n > 0 => Ok(n), + _ => Err(USimpleError::new( + 1, + translate!("ptx-error-invalid-number", "kind" => kind, "value" => value.to_owned()), + )), + } +} + fn get_config(matches: &mut clap::ArgMatches) -> UResult { let mut config = Config::default(); let err_msg = "parsing options failed"; @@ -245,13 +258,13 @@ fn get_config(matches: &mut clap::ArgMatches) -> UResult { .expect(err_msg) .clone_into(&mut config.trunc_str); } - if matches.contains_id(options::WIDTH) { - config.line_width = *matches.get_one::(options::WIDTH).unwrap() as usize; + if let Some(value) = matches.get_one::(options::WIDTH) { + config.line_width = parse_ptx_number(value, "line width")? as usize; } else if matches.get_flag(options::TYPESET_MODE) { config.line_width = 100; } - if matches.contains_id(options::GAP_SIZE) { - config.gap_size = *matches.get_one::(options::GAP_SIZE).unwrap() as usize; + if let Some(value) = matches.get_one::(options::GAP_SIZE) { + config.gap_size = parse_ptx_number(value, "gap width")? as usize; } if let Some(format) = matches.get_one::(options::FORMAT) { config.format = match format.as_str() { @@ -1041,7 +1054,7 @@ pub fn uu_app() -> Command { Arg::new(options::GAP_SIZE) .short('g') .long(options::GAP_SIZE) - .value_parser(value_parser!(u64).range(1..)) + .allow_hyphen_values(true) .help(translate!("ptx-help-gap-size")) .value_name("NUMBER"), ) @@ -1082,7 +1095,7 @@ pub fn uu_app() -> Command { Arg::new(options::WIDTH) .short('w') .long(options::WIDTH) - .value_parser(value_parser!(u64).range(1..)) + .allow_hyphen_values(true) .help(translate!("ptx-help-width")) .value_name("NUMBER"), ) diff --git a/tests/by-util/test_ptx.rs b/tests/by-util/test_ptx.rs index d53916dbe00..19cd7701a77 100644 --- a/tests/by-util/test_ptx.rs +++ b/tests/by-util/test_ptx.rs @@ -9,8 +9,31 @@ use uutests::new_ucmd; #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails_with_code(1); - new_ucmd!().arg("-g").arg("0").fails_with_code(1); // clap provided message - new_ucmd!().arg("-w").arg("0").fails_with_code(1); // clap provided message + new_ucmd!() + .arg("-g") + .arg("0") + .fails_with_code(1) + .stderr_is("ptx: invalid gap width: '0'\n"); + new_ucmd!() + .arg("-w") + .arg("0") + .fails_with_code(1) + .stderr_is("ptx: invalid line width: '0'\n"); +} + +#[test] +fn test_gap_size_negative_as_separate_arg() { + // A negative value passed as its own argument (not attached with + // `-g-5`/`=`) must not be mistaken for a new, unrecognized flag; GNU + // still rejects the negative value itself, just with its own wording. + new_ucmd!() + .args(&["-g", "-5"]) + .fails_with_code(1) + .stderr_is("ptx: invalid gap width: '-5'\n"); + new_ucmd!() + .args(&["--width", "-5"]) + .fails_with_code(1) + .stderr_is("ptx: invalid line width: '-5'\n"); } #[test] fn test_reference_format_for_stdin() {