Skip to content
Open
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
**/.venv

51 changes: 51 additions & 0 deletions implement-shell-tools/cat/cat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import argparse

parser = argparse.ArgumentParser(
prog="py-cat",
description="A Python implementation of the Unix cat command",
)

parser.add_argument("-n", "--number", action="store_true", help="Number all output lines")
parser.add_argument("-b", "--numberNonBlank", action="store_true", help="Numbers only non-empty lines. Overrides -n option")
parser.add_argument("path", nargs="+", help="The file path to process")

args = parser.parse_args()

def number_all(lines):
numbered = []
for i, line in enumerate(lines):
numbered.append(f"{i + 1:>6}\t{line}")
return numbered

def number_non_blank(lines):
numbered = []
counter = 1
for line in lines:
if line == "":
numbered.append(line)
else:
numbered.append(f"{counter:>6}\t{line}")
counter += 1
return numbered

# Read and concatenate file contents
content = ""

for path in args.path:
with open(path, "r") as f:
content += f.read()

if content.endswith("\n"):
content = content[:-1]

# Split content into lines
lines = content.split("\n")

# Output logic

if args.numberNonBlank:
print("\n".join(number_non_blank(lines)))
elif args.number:
print("\n".join(number_all(lines)))
else:
print("\n".join(lines))
25 changes: 25 additions & 0 deletions implement-shell-tools/ls/ls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import argparse
import os

parser = argparse.ArgumentParser(
prog="py-ls",
description="A Python implementation of the Unix ls command",
)

parser.add_argument("-1", dest="_1", action="store_true", help="List one file per line")
parser.add_argument("-a", "--all", action="store_true", help="Include entries that begin with a dot (.)")
parser.add_argument("path", nargs="?", default=".", help="Directory to list")

args = parser.parse_args()

files = os.listdir(args.path)

if args.all:
files = [".", ".."] + files
else:
files = [file for file in files if not file.startswith(".")]

if args._1:
print("\n".join(files))
else:
print(" ".join(files))
89 changes: 89 additions & 0 deletions implement-shell-tools/wc/wc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import argparse
import os

parser = argparse.ArgumentParser(
prog="py-wc",
description="A Python implementation of the Unix wc command",
)

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")
parser.add_argument("path", nargs="+", help="The file path to process")

args = parser.parse_args()
file_paths = args.path
lines_flag, words_flag, bytes_flag = args.lines, args.words, args.bytes

def format_output(details_list, totals=None, flags=None):
lines_flag, words_flag, bytes_flag = flags
show_all = not (lines_flag or words_flag or bytes_flag)
output_lines = []

# Per-file output
for d in details_list:
line = ""

if show_all or lines_flag:
line += f"{d['line_count']:>3} "

if show_all or words_flag:
line += f"{d['word_count']:>3} "

if show_all or bytes_flag:
line += f"{d['file_size']:>3} "

line += d["file_path"]
output_lines.append(line)

# Totals (only if more than one file)
if totals and len(details_list) > 1:
total_line = ""

if show_all or lines_flag:
total_line += f"{totals['line_count']:>3} "

if show_all or words_flag:
total_line += f"{totals['word_count']:>3} "

if show_all or bytes_flag:
total_line += f"{totals['file_size']:>3} "

total_line += "total"
output_lines.append(total_line)

return "\n".join(output_lines)


# Collect file details
file_details_list = []
line_count_total = 0
word_count_total = 0
file_size_total = 0

for file_path in file_paths:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()

details = {
"line_count": content.count("\n"),
"word_count": len(content.split()),
"file_size": os.path.getsize(file_path),
"file_path": file_path,
}

line_count_total += details["line_count"]
word_count_total += details["word_count"]
file_size_total += details["file_size"]

file_details_list.append(details)

totals_details = {
"line_count": line_count_total,
"word_count": word_count_total,
"file_size": file_size_total,
}

# Final output
flags = (lines_flag, words_flag, bytes_flag)
print(format_output(file_details_list, totals_details, flags))