Skip to content

Commit 91ee983

Browse files
chaliyclaude
andauthored
fix(wc): match real bash output padding behavior (#342)
## Summary - Single-value stdin output: no padding (matches real bash) - Multi-value stdin output: 7-char right-aligned fields with space separator - File output: always padded with 7-char fields - Remove 19 `bash_diff` markers from wc.test.sh (keep 1 for locale-dependent unicode) Closes #322 ## Test plan - [x] `cargo test --test spec_tests` passes (all 13 tests including bash_comparison_tests) - [x] `cargo test --all-features` passes - [x] 933/934 comparison tests match real bash (99.9%, only unicode locale diff remains) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 12a428a commit 91ee983

File tree

2 files changed

+73
-56
lines changed

2 files changed

+73
-56
lines changed

crates/bashkit/src/builtins/wc.rs

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ impl WcFlags {
7878
max_line_length,
7979
}
8080
}
81+
82+
/// Number of active count fields
83+
fn active_count(&self) -> usize {
84+
[
85+
self.lines,
86+
self.words,
87+
self.bytes,
88+
self.chars,
89+
self.max_line_length,
90+
]
91+
.iter()
92+
.filter(|&&b| b)
93+
.count()
94+
}
8195
}
8296

8397
#[async_trait]
@@ -102,7 +116,9 @@ impl Builtin for Wc {
102116
// Read from stdin
103117
if let Some(stdin) = ctx.stdin {
104118
let counts = count_text(stdin);
105-
output.push_str(&format_counts(&counts, &flags, None));
119+
// Real bash: no padding for single-value stdin, padded for multiple values
120+
let padded = flags.active_count() > 1;
121+
output.push_str(&format_counts(&counts, &flags, None, padded));
106122
output.push('\n');
107123
}
108124
} else {
@@ -127,7 +143,7 @@ impl Builtin for Wc {
127143
total_max_line = counts.max_line_length;
128144
}
129145

130-
output.push_str(&format_counts(&counts, &flags, Some(file)));
146+
output.push_str(&format_counts(&counts, &flags, Some(file), true));
131147
output.push('\n');
132148
}
133149
Err(e) => {
@@ -145,7 +161,12 @@ impl Builtin for Wc {
145161
chars: total_chars,
146162
max_line_length: total_max_line,
147163
};
148-
output.push_str(&format_counts(&totals, &flags, Some(&"total".to_string())));
164+
output.push_str(&format_counts(
165+
&totals,
166+
&flags,
167+
Some(&"total".to_string()),
168+
true,
169+
));
149170
output.push('\n');
150171
}
151172
}
@@ -178,32 +199,47 @@ fn count_text(text: &str) -> TextCounts {
178199
}
179200
}
180201

181-
/// Format counts for output
182-
fn format_counts(counts: &TextCounts, flags: &WcFlags, filename: Option<&String>) -> String {
183-
let mut parts = Vec::new();
202+
/// Format counts for output.
203+
/// When `padded` is true, right-align numbers in 8-char fields (used for file output).
204+
/// When `padded` is false, use minimal formatting like real bash stdin output.
205+
fn format_counts(
206+
counts: &TextCounts,
207+
flags: &WcFlags,
208+
filename: Option<&String>,
209+
padded: bool,
210+
) -> String {
211+
let mut values: Vec<usize> = Vec::new();
184212

185213
if flags.lines {
186-
parts.push(format!("{:>8}", counts.lines));
214+
values.push(counts.lines);
187215
}
188216
if flags.words {
189-
parts.push(format!("{:>8}", counts.words));
217+
values.push(counts.words);
190218
}
191219
if flags.bytes {
192-
parts.push(format!("{:>8}", counts.bytes));
220+
values.push(counts.bytes);
193221
}
194222
if flags.chars {
195-
parts.push(format!("{:>8}", counts.chars));
223+
values.push(counts.chars);
196224
}
197225
if flags.max_line_length {
198-
parts.push(format!("{:>8}", counts.max_line_length));
226+
values.push(counts.max_line_length);
199227
}
200228

201-
let mut result = parts.join("");
229+
let result = if padded {
230+
// Real bash uses 7-char wide fields separated by a space
231+
let parts: Vec<String> = values.iter().map(|v| format!("{:>7}", v)).collect();
232+
parts.join(" ")
233+
} else {
234+
let parts: Vec<String> = values.iter().map(|v| v.to_string()).collect();
235+
parts.join(" ")
236+
};
237+
202238
if let Some(name) = filename {
203-
result.push(' ');
204-
result.push_str(name);
239+
format!("{} {}", result, name)
240+
} else {
241+
result
205242
}
206-
result
207243
}
208244

209245
#[cfg(test)]
Lines changed: 22 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,139 @@
11
### wc_lines_only
2-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
32
# Count lines with -l
43
printf 'a\nb\nc\n' | wc -l
54
### expect
6-
3
5+
3
76
### end
87

98
### wc_words_only
10-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
119
# Count words with -w
1210
printf 'one two three four five' | wc -w
1311
### expect
14-
5
12+
5
1513
### end
1614

1715
### wc_bytes_only
18-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
1916
# Count bytes with -c
2017
printf 'hello' | wc -c
2118
### expect
22-
5
19+
5
2320
### end
2421

2522
### wc_empty
26-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
2723
# Empty input
2824
printf '' | wc -l
2925
### expect
30-
0
26+
0
3127
### end
3228

3329
### wc_all_flags
34-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
3530
# All counts (default)
3631
printf 'hello world\n' | wc
3732
### expect
38-
1 2 12
33+
1 2 12
3934
### end
4035

4136
### wc_multiple_lines
42-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
4337
# Multiple lines
4438
printf 'one\ntwo\nthree\n' | wc -l
4539
### expect
46-
3
40+
3
4741
### end
4842

4943
### wc_chars_m_flag
50-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
5144
# Count characters with -m
5245
printf 'hello' | wc -m
5346
### expect
54-
5
47+
5
5548
### end
5649

5750
### wc_lines_words
58-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
5951
# Lines and words combined
6052
printf 'one two\nthree four\n' | wc -lw
6153
### expect
62-
2 4
54+
2 4
6355
### end
6456

6557
### wc_no_newline_at_end
66-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
6758
# Input without trailing newline
6859
printf 'hello world' | wc -w
6960
### expect
70-
2
61+
2
7162
### end
7263

7364
### wc_multiple_spaces
74-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
7565
# Multiple spaces between words
7666
printf 'hello world' | wc -w
7767
### expect
78-
2
68+
2
7969
### end
8070

8171
### wc_tabs_count
82-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
8372
# Tabs in input
8473
printf 'a\tb\tc' | wc -w
8574
### expect
86-
3
75+
3
8776
### end
8877

8978
### wc_single_word
90-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
9179
# Single word
9280
printf 'word' | wc -w
9381
### expect
94-
1
82+
1
9583
### end
9684

9785
### wc_only_whitespace
98-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
9986
# Only whitespace
10087
printf ' \t ' | wc -w
10188
### expect
102-
0
89+
0
10390
### end
10491

10592
### wc_max_line_length
106-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
10793
printf 'short\nlongerline\n' | wc -L
10894
### expect
109-
10
95+
10
11096
### end
11197

11298
### wc_long_flags
113-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
11499
# Long flag --lines
115100
printf 'a\nb\n' | wc --lines
116101
### expect
117-
2
102+
2
118103
### end
119104

120105
### wc_long_words
121-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
122106
# Long flag --words
123107
printf 'one two three' | wc --words
124108
### expect
125-
3
109+
3
126110
### end
127111

128112
### wc_long_bytes
129-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
130113
# Long flag --bytes
131114
printf 'hello' | wc --bytes
132115
### expect
133-
5
116+
5
134117
### end
135118

136119
### wc_bytes_vs_chars
137-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
138120
# Bytes vs chars for ASCII
139121
printf 'hello' | wc -c && printf 'hello' | wc -m
140122
### expect
141-
5
142-
5
123+
5
124+
5
143125
### end
144126

145127
### wc_unicode_chars
146-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
128+
### bash_diff: locale-dependent; real bash wc -m may count bytes in C locale
147129
printf 'héllo' | wc -m
148130
### expect
149-
5
131+
5
150132
### end
151133

152134
### wc_unicode_bytes
153-
### bash_diff: Bashkit wc uses fixed-width padding for stdin, real bash uses no padding
154135
# Unicode byte count
155136
printf 'héllo' | wc -c
156137
### expect
157-
6
138+
6
158139
### end

0 commit comments

Comments
 (0)