Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
.venv
*.class
54 changes: 54 additions & 0 deletions implement-shell-tools/cat/cat.py
Original file line number Diff line number Diff line change
@@ -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()
58 changes: 58 additions & 0 deletions implement-shell-tools/ls/ls.py
Original file line number Diff line number Diff line change
@@ -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()
98 changes: 98 additions & 0 deletions implement-shell-tools/wc/wc.py
Original file line number Diff line number Diff line change
@@ -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()
Loading