diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 438048781..06016f794 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -95,7 +95,8 @@ impl FmtOptions { (w, g) } (Some(&w), None) => { - let g = (w * DEFAULT_GOAL_TO_WIDTH_RATIO / 100).min(w - 3); + // Only allow a goal of zero if the width is set to be zero + let g = (w * DEFAULT_GOAL_TO_WIDTH_RATIO / 100).max(if w == 0 { 0 } else { 1 }); (w, g) } (None, Some(&g)) => { @@ -104,6 +105,7 @@ impl FmtOptions { } (None, None) => (75, 70), }; + debug_assert!(width >= goal, "GOAL {goal} should not be greater than WIDTH {width} when given {width_opt:?} and {goal_opt:?}."); if width > MAX_WIDTH { return Err(USimpleError::new( @@ -331,7 +333,7 @@ pub fn uu_app() -> Command { Arg::new(options::GOAL) .short('g') .long("goal") - .help("Goal width, default of 93% of WIDTH. Must be less than WIDTH.") + .help("Goal width, default of 93% of WIDTH. Must be less than or equal to WIDTH.") .value_name("GOAL") .value_parser(clap::value_parser!(usize)), ) diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index 7393589d0..b8c592d3f 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -6,7 +6,7 @@ // spell-checker:ignore (ToDO) INFTY MULT accum breakwords linebreak linebreaking linebreaks linelen maxlength minlength nchars ostream overlen parasplit plass posn powf punct signum slen sstart tabwidth tlen underlen winfo wlen wordlen use std::io::{BufWriter, Stdout, Write}; -use std::{cmp, i64, mem}; +use std::{cmp, mem}; use crate::parasplit::{ParaWords, Paragraph, WordInfo}; use crate::FmtOptions; @@ -238,8 +238,8 @@ fn find_kp_breakpoints<'a, T: Iterator>>( let mut active_breaks = vec![0]; let mut next_active_breaks = vec![]; - let stretch = (args.opts.width - args.opts.goal) as isize; - let minlength = args.opts.goal - stretch as usize; + let stretch = args.opts.width - args.opts.goal; + let minlength = args.opts.goal - stretch; let mut new_linebreaks = vec![]; let mut is_sentence_start = false; let mut least_demerits = 0; @@ -300,7 +300,7 @@ fn find_kp_breakpoints<'a, T: Iterator>>( compute_demerits( args.opts.goal as isize - tlen as isize, stretch, - w.word_nchars as isize, + w.word_nchars, active.prev_rat, ) }; @@ -393,7 +393,7 @@ const DR_MULT: f32 = 600.0; // DL_MULT is penalty multiplier for short words at end of line const DL_MULT: f32 = 300.0; -fn compute_demerits(delta_len: isize, stretch: isize, wlen: isize, prev_rat: f32) -> (i64, f32) { +fn compute_demerits(delta_len: isize, stretch: usize, wlen: usize, prev_rat: f32) -> (i64, f32) { // how much stretch are we using? let ratio = if delta_len == 0 { 0.0f32 @@ -419,7 +419,7 @@ fn compute_demerits(delta_len: isize, stretch: isize, wlen: isize, prev_rat: f32 }; // we penalize lines that have very different ratios from previous lines - let bad_delta_r = (DR_MULT * (((ratio - prev_rat) / 2.0).powi(3)).abs()) as i64; + let bad_delta_r = (DR_MULT * ((ratio - prev_rat) / 2.0).powi(3).abs()) as i64; let demerits = i64::pow(1 + bad_linelen + bad_wordlen + bad_delta_r, 2); @@ -440,8 +440,8 @@ fn restart_active_breaks<'a>( } else { // choose the lesser evil: breaking too early, or breaking too late let wlen = w.word_nchars + args.compute_width(w, active.length, active.fresh); - let underlen = (min - active.length) as isize; - let overlen = ((wlen + slen + active.length) - args.opts.width) as isize; + let underlen = min as isize - active.length as isize; + let overlen = (wlen + slen + active.length) as isize - args.opts.width as isize; if overlen > underlen { // break early, put this word on the next line (true, args.indent_len + w.word_nchars) diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 4fd059080..411ab322c 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -33,7 +33,19 @@ fn test_fmt_width() { new_ucmd!() .args(&["one-word-per-line.txt", param, "10"]) .succeeds() - .stdout_is("this is\na file\nwith one\nword per\nline\n"); + .stdout_is("this is a\nfile with\none word\nper line\n"); + } +} + +#[test] +fn test_small_width() { + for width in ["0", "1", "2", "3"] { + for param in ["-w", "--width"] { + new_ucmd!() + .args(&[param, width, "one-word-per-line.txt"]) + .succeeds() + .stdout_is("this\nis\na\nfile\nwith\none\nword\nper\nline\n"); + } } }