From 4b1163890f88d068c70dd82a1888c84c3bf9b10b Mon Sep 17 00:00:00 2001 From: Misakait Date: Sun, 5 Oct 2025 21:54:11 +0800 Subject: [PATCH] fix(ptx): Align text wrapping behavior with GNU in traditional mode and add related test In traditional mode (-G) with references enabled, `uutils/ptx` failed to wrap long lines in the same way as the GNU `ptx` reference implementation. This was due to the layout algorithm operating on an incorrectly large line width, as the space for the reference column was not being subtracted from the total width budget. This commit implements the correct line width adjustment by subtracting the reference width. This aligns the wrapping behavior and makes the output identical to GNU `ptx` for the tested cases. --- src/uu/ptx/src/ptx.rs | 42 +++++++++++++++++++++++++++++++++++---- tests/by-util/test_ptx.rs | 24 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 111d74d39..a0fe9a5e8 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -224,7 +224,7 @@ fn get_config(matches: &clap::ArgMatches) -> UResult { } config.auto_ref = matches.get_flag(options::AUTO_REFERENCE); config.input_ref = matches.get_flag(options::REFERENCES); - config.right_ref &= matches.get_flag(options::RIGHT_SIDE_REFS); + config.right_ref = matches.get_flag(options::RIGHT_SIDE_REFS); config.ignore_case = matches.get_flag(options::IGNORE_CASE); if matches.contains_id(options::MACRO_NAME) { config.macro_name = matches @@ -661,7 +661,7 @@ fn prepare_line_chunks( } fn write_traditional_output( - config: &Config, + config: &mut Config, file_map: &FileMap, words: &BTreeSet, output_filename: &OsStr, @@ -677,6 +677,15 @@ fn write_traditional_output( let context_reg = Regex::new(&config.context_regex).unwrap(); + if !config.right_ref { + let max_ref_len = if config.auto_ref { + get_auto_max_reference_len(words) + } else { + 0 + }; + config.line_width -= max_ref_len; + } + for word_ref in words { let file_map_value: &FileContent = file_map .get(&word_ref.filename) @@ -722,6 +731,31 @@ fn write_traditional_output( Ok(()) } +fn get_auto_max_reference_len(words: &BTreeSet) -> usize { + //Get the maximum length of the reference field + let line_num = words + .iter() + .map(|w| { + if w.local_line_nr == 0 { + 1 + } else { + (w.local_line_nr as f64).log10() as usize + 1 + } + }) + .max() + .unwrap_or(0); + + let filename_len = words + .iter() + .filter(|w| w.filename != "-") + .map(|w| w.filename.maybe_quote().to_string().len()) + .max() + .unwrap_or(0); + + // +1 for the colon + line_num + filename_len + 1 +} + mod options { pub mod format { pub static ROFF: &str = "roff"; @@ -749,7 +783,7 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; - let config = get_config(&matches)?; + let mut config = get_config(&matches)?; let input_files; let output_file: OsString; @@ -783,7 +817,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let word_filter = WordFilter::new(&matches, &config)?; let file_map = read_input(&input_files).map_err_context(String::new)?; let word_set = create_word_set(&config, &word_filter, &file_map); - write_traditional_output(&config, &file_map, &word_set, &output_file) + write_traditional_output(&mut config, &file_map, &word_set, &output_file) } pub fn uu_app() -> Command { diff --git a/tests/by-util/test_ptx.rs b/tests/by-util/test_ptx.rs index 917cd047a..3ff36a1c6 100644 --- a/tests/by-util/test_ptx.rs +++ b/tests/by-util/test_ptx.rs @@ -57,6 +57,30 @@ fn test_truncation_no_extra_space_in_after() { .stdout_contains(".xx \"\" \"Rust\" \"is/\" \"\""); } +#[test] +fn gnu_ext_disabled_reference_calculation() { + let input = "Hello World Rust is good language"; + let expected_output = concat!( + r#".xx "language" "" "Hello World Rust is good" "" ":1""#, + "\n", + r#".xx "" "Hello World" "Rust is good language" "" ":1""#, + "\n", + r#".xx "" "Hello" "World Rust is good language" "" ":1""#, + "\n", + r#".xx "" "Hello World Rust is" "good language" "" ":1""#, + "\n", + r#".xx "" "Hello World Rust" "is good language" "" ":1""#, + "\n", + r#".xx "" "Hello World Rust is good" "language" "" ":1""#, + "\n", + ); + new_ucmd!() + .args(&["-G", "-A"]) + .pipe_in(input) + .succeeds() + .stdout_only(expected_output); +} + #[test] fn gnu_ext_disabled_rightward_no_ref() { new_ucmd!()