Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 51 additions & 15 deletions crates/bashkit/src/builtins/wc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ 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]
Expand All @@ -102,7 +116,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 {
Expand All @@ -127,7 +143,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) => {
Expand All @@ -145,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())));
output.push_str(&format_counts(
&totals,
&flags,
Some(&"total".to_string()),
true,
));
output.push('\n');
}
}
Expand Down Expand Up @@ -178,32 +199,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<usize> = 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<String> = values.iter().map(|v| format!("{:>7}", v)).collect();
parts.join(" ")
} else {
let parts: Vec<String> = 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)]
Expand Down
63 changes: 22 additions & 41 deletions crates/bashkit/tests/spec_cases/bash/wc.test.sh
Original file line number Diff line number Diff line change
@@ -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
Loading