Skip to content

Commit 29a7dde

Browse files
committed
feat(wc): add Python implementation with full flag support
- Implement wc/wc.py: Python version of wc utility - Support -l/--lines, -w/--words, -c/--bytes flags - Read from stdin when no files provided - Process multiple files with totals row - Calculate byte counts using raw file bytes for accuracy - Format output with right-justified columns (4-char width) - Handle file not found errors with proper exit codes - Match behavior of JavaScript implementation - Add argparse for robust CLI argument parsing
1 parent 9963708 commit 29a7dde

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

  • implement-shell-tools/wc

implement-shell-tools/wc/wc.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import argparse
2+
import sys
3+
import os
4+
5+
6+
def calculate_stats(content, display_name, original_bytes=None):
7+
lines = content.count("\n")
8+
words = len(content.split())
9+
# If we have the raw bytes, use that length. Otherwise, encode to get byte length.
10+
byte_count = (
11+
original_bytes if original_bytes is not None else len(content.encode("utf-8"))
12+
)
13+
14+
return {
15+
"lineCount": lines,
16+
"wordCount": words,
17+
"byteCount": byte_count,
18+
"displayName": display_name,
19+
}
20+
21+
22+
def print_formatted_report(stats, args, should_show_all_stats):
23+
output_columns = []
24+
25+
def format_col(count):
26+
return str(count).rjust(4)
27+
28+
if should_show_all_stats:
29+
output_columns.append(format_col(stats["lineCount"]))
30+
output_columns.append(format_col(stats["wordCount"]))
31+
output_columns.append(format_col(stats["byteCount"]))
32+
else:
33+
if args.lines:
34+
output_columns.append(format_col(stats["lineCount"]))
35+
if args.words:
36+
output_columns.append(format_col(stats["wordCount"]))
37+
if args.bytes:
38+
output_columns.append(format_col(stats["byteCount"]))
39+
40+
# Use a single space between the numbers and the name
41+
print(f"{''.join(output_columns)} {stats['displayName']}")
42+
43+
44+
def main():
45+
parser = argparse.ArgumentParser(description="A simple Python implementation of wc")
46+
parser.add_argument("files", nargs="*", help="Files to process")
47+
parser.add_argument(
48+
"-l", "--lines", action="store_true", help="print the newline counts"
49+
)
50+
parser.add_argument(
51+
"-w", "--words", action="store_true", help="print the word counts"
52+
)
53+
parser.add_argument(
54+
"-c", "--bytes", action="store_true", help="print the byte counts"
55+
)
56+
57+
args = parser.parse_args()
58+
should_show_all_stats = not (args.lines or args.words or args.bytes)
59+
all_file_stats = []
60+
exit_code = 0
61+
62+
# NEW: Handle Standard Input if no files are provided
63+
if not args.files:
64+
stdin_content = sys.stdin.read()
65+
stats = calculate_stats(stdin_content, "")
66+
print_formatted_report(stats, args, should_show_all_stats)
67+
return
68+
69+
# Process files
70+
for file_path in args.files:
71+
try:
72+
with open(file_path, "rb") as f:
73+
raw_bytes = f.read()
74+
content = raw_bytes.decode("utf-8", errors="ignore")
75+
stats = calculate_stats(content, file_path, len(raw_bytes))
76+
77+
all_file_stats.append(stats)
78+
print_formatted_report(stats, args, should_show_all_stats)
79+
except Exception:
80+
print(f"wc: {file_path}: No such file or directory", file=sys.stderr)
81+
exit_code = 1
82+
83+
# Print total if more than one file
84+
if len(all_file_stats) > 1:
85+
grand_totals = {
86+
"lineCount": sum(s["lineCount"] for s in all_file_stats),
87+
"wordCount": sum(s["wordCount"] for s in all_file_stats),
88+
"byteCount": sum(s["byteCount"] for s in all_file_stats),
89+
"displayName": "total",
90+
}
91+
print_formatted_report(grand_totals, args, should_show_all_stats)
92+
93+
if exit_code != 0:
94+
sys.exit(exit_code)
95+
96+
97+
if __name__ == "__main__":
98+
main()

0 commit comments

Comments
 (0)