From c54ba92924ab5262854af30fd2a612db8a418d8c Mon Sep 17 00:00:00 2001 From: iswat Date: Sat, 11 Apr 2026 16:12:07 +0100 Subject: [PATCH 1/3] Update .gitignore to exclude Python and Java files - Add .venv to ignore Python virtual environments - Add *.class to ignore Java compiled class files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3629e64..be3563007 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +.venv +*.class \ No newline at end of file From 99637085992941b56438fba69561345adf371f8c Mon Sep 17 00:00:00 2001 From: iswat Date: Sat, 18 Apr 2026 10:35:43 +0100 Subject: [PATCH 2/3] feat(ls): add Python implementation with color support for directories - Implement ls/ls.py: Python version of ls utility - Add -1/--one-per-line flag for single column output - Add -a/--all flag to show hidden files (dotfiles) - Color directories in blue using ANSI escape codes - Sort entries case-insensitively for better readability - Support custom directory path argument (default to current directory) - Add error handling with proper exit codes - Match behavior of JavaScript implementation --- implement-shell-tools/cat/cat.py | 54 +++++++++++++++++++++++++++++ implement-shell-tools/ls/ls.py | 58 ++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 implement-shell-tools/cat/cat.py create mode 100644 implement-shell-tools/ls/ls.py diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 000000000..6c904d1aa --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,54 @@ +import sys +import argparse + +def read_and_output_files(): + # 1. Setup Argument Parser (Equivalent to 'commander') + parser = argparse.ArgumentParser(description="Python implementation of a basic cat-like utility") + parser.add_argument("-n", "--number", action="store_true", help="number all output lines") + parser.add_argument("-b", "--number-nonblank", action="store_true", help="number only non-empty lines") + parser.add_argument("files", nargs="+", help="files to read") + + args = parser.parse_args() + + try: + # 2. Read all file contents (Equivalent to Promise.all / fs.readFile) + file_contents = [] + for file_path in args.files: + with open(file_path, "r", encoding="utf-8") as f: + file_contents.append(f.read()) + + concatenated_content = "".join(file_contents) + + # 3. Process Logic + if args.number: + # -n logic: number all lines + lines = concatenated_content.split("\n") + output = [] + for index, line in enumerate(lines, start=1): + # rjust(6) is equivalent to padStart(6) + output.append(f"{str(index).rjust(6)} {line}") + sys.stdout.write("\n".join(output)) + + elif args.number_nonblank: + # -b logic: number only non-empty lines + lines = concatenated_content.split("\n") + output = [] + nonblank_line_number = 0 + for line in lines: + if line.strip() == "": + output.append(line) + else: + nonblank_line_number += 1 + output.append(f"{str(nonblank_line_number).rjust(6)} {line}") + sys.stdout.write("\n".join(output)) + + else: + # No flags: standard output + sys.stdout.write(concatenated_content) + + except Exception as err: + print(f"Error reading multiple files: {err}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + read_and_output_files() diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 000000000..967635a4c --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,58 @@ +import os +import sys +import argparse + +def run_ls_command(): + parser = argparse.ArgumentParser() + parser.add_argument("-1", "--one-per-line", action="store_true", dest="one") + parser.add_argument("-a", "--all", action="store_true") + parser.add_argument("path", nargs="?", default=".") + args = parser.parse_args() + + # ANSI Color Codes + BLUE = '\033[34m' + RESET = '\033[0m' + + try: + # 1. Get and sort entries + directory_entries = os.listdir(args.path) + directory_entries.sort(key=str.lower) + + # 2. Filter out dotfiles unless -a is used + visible_entries = [] + if args.all: + visible_entries = directory_entries + else: + for name in directory_entries: + if not name.startswith("."): + visible_entries.append(name) + + # 3. Apply colors to folders + colored_entries = [] + for name in visible_entries: + # We must join the path to the name to check if it's a folder correctly + full_path = os.path.join(args.path, name) + + if os.path.isdir(full_path): + # Wrap the name in Blue color codes + colored_entries.append(f"{BLUE}{name}{RESET}") + else: + # Keep regular file name as is + colored_entries.append(name) + + # 4. Build output string + output_string = "" + if args.one: + output_string = "\n".join(colored_entries) + "\n" + else: + output_string = " ".join(colored_entries) + "\n" + + if colored_entries: + sys.stdout.write(output_string) + + except Exception as err: + print(f"Error: {err}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + run_ls_command() \ No newline at end of file From 29a7ddeda1dc114187e0a6042bd899ef85590082 Mon Sep 17 00:00:00 2001 From: iswat Date: Sun, 19 Apr 2026 11:49:50 +0100 Subject: [PATCH 3/3] 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 --- implement-shell-tools/wc/wc.py | 98 ++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 000000000..ea09e9c4d --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,98 @@ +import argparse +import sys +import os + + +def calculate_stats(content, display_name, original_bytes=None): + lines = content.count("\n") + words = len(content.split()) + # If we have the raw bytes, use that length. Otherwise, encode to get byte length. + byte_count = ( + original_bytes if original_bytes is not None else len(content.encode("utf-8")) + ) + + return { + "lineCount": lines, + "wordCount": words, + "byteCount": byte_count, + "displayName": display_name, + } + + +def print_formatted_report(stats, args, should_show_all_stats): + output_columns = [] + + def format_col(count): + return str(count).rjust(4) + + if should_show_all_stats: + output_columns.append(format_col(stats["lineCount"])) + output_columns.append(format_col(stats["wordCount"])) + output_columns.append(format_col(stats["byteCount"])) + else: + if args.lines: + output_columns.append(format_col(stats["lineCount"])) + if args.words: + output_columns.append(format_col(stats["wordCount"])) + if args.bytes: + output_columns.append(format_col(stats["byteCount"])) + + # Use a single space between the numbers and the name + print(f"{''.join(output_columns)} {stats['displayName']}") + + +def main(): + parser = argparse.ArgumentParser(description="A simple Python implementation of wc") + parser.add_argument("files", nargs="*", help="Files to process") + parser.add_argument( + "-l", "--lines", action="store_true", help="print the newline counts" + ) + parser.add_argument( + "-w", "--words", action="store_true", help="print the word counts" + ) + parser.add_argument( + "-c", "--bytes", action="store_true", help="print the byte counts" + ) + + args = parser.parse_args() + should_show_all_stats = not (args.lines or args.words or args.bytes) + all_file_stats = [] + exit_code = 0 + + # NEW: Handle Standard Input if no files are provided + if not args.files: + stdin_content = sys.stdin.read() + stats = calculate_stats(stdin_content, "") + print_formatted_report(stats, args, should_show_all_stats) + return + + # Process files + for file_path in args.files: + try: + with open(file_path, "rb") as f: + raw_bytes = f.read() + content = raw_bytes.decode("utf-8", errors="ignore") + stats = calculate_stats(content, file_path, len(raw_bytes)) + + all_file_stats.append(stats) + print_formatted_report(stats, args, should_show_all_stats) + except Exception: + print(f"wc: {file_path}: No such file or directory", file=sys.stderr) + exit_code = 1 + + # Print total if more than one file + if len(all_file_stats) > 1: + grand_totals = { + "lineCount": sum(s["lineCount"] for s in all_file_stats), + "wordCount": sum(s["wordCount"] for s in all_file_stats), + "byteCount": sum(s["byteCount"] for s in all_file_stats), + "displayName": "total", + } + print_formatted_report(grand_totals, args, should_show_all_stats) + + if exit_code != 0: + sys.exit(exit_code) + + +if __name__ == "__main__": + main()