From 0a09b35a19a75ded7671fbc15514daa423c0988b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Feb 2026 03:59:24 +0000 Subject: [PATCH 1/2] fix(wc): match real bash output padding behavior Single-value stdin: no padding. Multi-value stdin: 7-char right-aligned fields with space separator. File output: always padded. Remove all bash_diff markers except locale-dependent unicode chars test. Closes #322 --- crates/bashkit/src/builtins/wc.rs | 55 +++++++++++----- .../bashkit/tests/spec_cases/bash/wc.test.sh | 63 +++++++------------ 2 files changed, 62 insertions(+), 56 deletions(-) diff --git a/crates/bashkit/src/builtins/wc.rs b/crates/bashkit/src/builtins/wc.rs index 481f0f61..0ca9ad23 100644 --- a/crates/bashkit/src/builtins/wc.rs +++ b/crates/bashkit/src/builtins/wc.rs @@ -78,6 +78,14 @@ impl WcFlags { max_line_length, } } + + /// Number of active count fields + fn active_count(&self) -> usize { + [self.lines, self.words, self.bytes, self.chars, self.max_line_length] + .iter() + .filter(|&&b| b) + .count() + } } #[async_trait] @@ -102,7 +110,9 @@ impl Builtin for Wc { // Read from stdin if let Some(stdin) = ctx.stdin { let counts = count_text(stdin); - output.push_str(&format_counts(&counts, &flags, None)); + // Real bash: no padding for single-value stdin, padded for multiple values + let padded = flags.active_count() > 1; + output.push_str(&format_counts(&counts, &flags, None, padded)); output.push('\n'); } } else { @@ -127,7 +137,7 @@ impl Builtin for Wc { total_max_line = counts.max_line_length; } - output.push_str(&format_counts(&counts, &flags, Some(file))); + output.push_str(&format_counts(&counts, &flags, Some(file), true)); output.push('\n'); } Err(e) => { @@ -145,7 +155,7 @@ impl Builtin for Wc { chars: total_chars, max_line_length: total_max_line, }; - output.push_str(&format_counts(&totals, &flags, Some(&"total".to_string()))); + output.push_str(&format_counts(&totals, &flags, Some(&"total".to_string()), true)); output.push('\n'); } } @@ -178,32 +188,47 @@ fn count_text(text: &str) -> TextCounts { } } -/// Format counts for output -fn format_counts(counts: &TextCounts, flags: &WcFlags, filename: Option<&String>) -> String { - let mut parts = Vec::new(); +/// Format counts for output. +/// When `padded` is true, right-align numbers in 8-char fields (used for file output). +/// When `padded` is false, use minimal formatting like real bash stdin output. +fn format_counts( + counts: &TextCounts, + flags: &WcFlags, + filename: Option<&String>, + padded: bool, +) -> String { + let mut values: Vec = Vec::new(); if flags.lines { - parts.push(format!("{:>8}", counts.lines)); + values.push(counts.lines); } if flags.words { - parts.push(format!("{:>8}", counts.words)); + values.push(counts.words); } if flags.bytes { - parts.push(format!("{:>8}", counts.bytes)); + values.push(counts.bytes); } if flags.chars { - parts.push(format!("{:>8}", counts.chars)); + values.push(counts.chars); } if flags.max_line_length { - parts.push(format!("{:>8}", counts.max_line_length)); + values.push(counts.max_line_length); } - let mut result = parts.join(""); + let result = if padded { + // Real bash uses 7-char wide fields separated by a space + let parts: Vec = values.iter().map(|v| format!("{:>7}", v)).collect(); + parts.join(" ") + } else { + let parts: Vec = values.iter().map(|v| v.to_string()).collect(); + parts.join(" ") + }; + if let Some(name) = filename { - result.push(' '); - result.push_str(name); + format!("{} {}", result, name) + } else { + result } - result } #[cfg(test)] diff --git a/crates/bashkit/tests/spec_cases/bash/wc.test.sh b/crates/bashkit/tests/spec_cases/bash/wc.test.sh index d3c5cad9..de9bbf2b 100644 --- a/crates/bashkit/tests/spec_cases/bash/wc.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/wc.test.sh @@ -1,158 +1,139 @@ ### wc_lines_only -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Count lines with -l printf 'a\nb\nc\n' | wc -l ### expect - 3 +3 ### end ### wc_words_only -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Count words with -w printf 'one two three four five' | wc -w ### expect - 5 +5 ### end ### wc_bytes_only -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Count bytes with -c printf 'hello' | wc -c ### expect - 5 +5 ### end ### wc_empty -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Empty input printf '' | wc -l ### expect - 0 +0 ### end ### wc_all_flags -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # All counts (default) printf 'hello world\n' | wc ### expect - 1 2 12 + 1 2 12 ### end ### wc_multiple_lines -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Multiple lines printf 'one\ntwo\nthree\n' | wc -l ### expect - 3 +3 ### end ### wc_chars_m_flag -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Count characters with -m printf 'hello' | wc -m ### expect - 5 +5 ### end ### wc_lines_words -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Lines and words combined printf 'one two\nthree four\n' | wc -lw ### expect - 2 4 + 2 4 ### end ### wc_no_newline_at_end -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Input without trailing newline printf 'hello world' | wc -w ### expect - 2 +2 ### end ### wc_multiple_spaces -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Multiple spaces between words printf 'hello world' | wc -w ### expect - 2 +2 ### end ### wc_tabs_count -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Tabs in input printf 'a\tb\tc' | wc -w ### expect - 3 +3 ### end ### wc_single_word -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Single word printf 'word' | wc -w ### expect - 1 +1 ### end ### wc_only_whitespace -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Only whitespace printf ' \t ' | wc -w ### expect - 0 +0 ### end ### wc_max_line_length -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding printf 'short\nlongerline\n' | wc -L ### expect - 10 +10 ### end ### wc_long_flags -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Long flag --lines printf 'a\nb\n' | wc --lines ### expect - 2 +2 ### end ### wc_long_words -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Long flag --words printf 'one two three' | wc --words ### expect - 3 +3 ### end ### wc_long_bytes -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Long flag --bytes printf 'hello' | wc --bytes ### expect - 5 +5 ### end ### wc_bytes_vs_chars -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Bytes vs chars for ASCII printf 'hello' | wc -c && printf 'hello' | wc -m ### expect - 5 - 5 +5 +5 ### end ### wc_unicode_chars -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding +### bash_diff: locale-dependent; real bash wc -m may count bytes in C locale printf 'héllo' | wc -m ### expect - 5 +5 ### end ### wc_unicode_bytes -### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding # Unicode byte count printf 'héllo' | wc -c ### expect - 6 +6 ### end From f2319f11e3e6801f5e42bf263d620d5c06a9ff7a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Feb 2026 04:07:26 +0000 Subject: [PATCH 2/2] fix: cargo fmt --- crates/bashkit/src/builtins/wc.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/bashkit/src/builtins/wc.rs b/crates/bashkit/src/builtins/wc.rs index 0ca9ad23..49f12269 100644 --- a/crates/bashkit/src/builtins/wc.rs +++ b/crates/bashkit/src/builtins/wc.rs @@ -81,10 +81,16 @@ impl WcFlags { /// Number of active count fields fn active_count(&self) -> usize { - [self.lines, self.words, self.bytes, self.chars, self.max_line_length] - .iter() - .filter(|&&b| b) - .count() + [ + self.lines, + self.words, + self.bytes, + self.chars, + self.max_line_length, + ] + .iter() + .filter(|&&b| b) + .count() } } @@ -155,7 +161,12 @@ impl Builtin for Wc { chars: total_chars, max_line_length: total_max_line, }; - output.push_str(&format_counts(&totals, &flags, Some(&"total".to_string()), true)); + output.push_str(&format_counts( + &totals, + &flags, + Some(&"total".to_string()), + true, + )); output.push('\n'); } }