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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
node_modules

# Ignore Python virtual environments
implement-cowsay/.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 @@
#!/usr/bin/env python3

import sys

def cat_file(file, options, line_number):
try:
with open(file, 'r', encoding='utf-8') as f:
lines = f.readlines()

for line in lines:
if options['number_non_blank'] and line.strip():
print(f"{line_number:6}\t{line}", end='')
line_number += 1
elif options['number_all']:
print(f"{line_number:6}\t{line}", end='')
line_number += 1
else:
print(line, end='')

return line_number
except FileNotFoundError:
print(f"cat: {file}: No such file or directory", file=sys.stderr)
sys.exit(1)

def main():
args = sys.argv[1:]
options = {
'number_all': False,
'number_non_blank': False,
}

files = []

for arg in args:
if arg == '-n':
options['number_all'] = True
elif arg == '-b':
options['number_non_blank'] = True
else:
files.append(arg)

if not files:
print("Usage: cat [-n | -b] <file>...", file=sys.stderr)
sys.exit(1)

line_number = 1
for file in files:
line_number = cat_file(file, options, line_number)

if __name__ == "__main__":
main()
65 changes: 65 additions & 0 deletions implement-shell-tools/ls/ls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3

import locale
import os
import sys


def parse_args(args):
one_per_line = False
show_all = False
paths = []

for arg in args:
if arg.startswith("-") and arg != "-":
for flag in arg[1:]:
if flag == "1":
one_per_line = True
elif flag == "a":
show_all = True
else:
print(f"ls: invalid option -- '{flag}'", file=sys.stderr)
sys.exit(1)
else:
paths.append(arg)

if not one_per_line:
print("Usage: ls.py -1 [-a] [path]", file=sys.stderr)
sys.exit(1)

if len(paths) > 1:
print("Usage: ls.py -1 [-a] [path]", file=sys.stderr)
sys.exit(1)

return show_all, (paths[0] if paths else ".")


def list_entries(path, show_all):
try:
entries = os.listdir(path)
except FileNotFoundError:
print(f"ls: cannot access '{path}': No such file or directory", file=sys.stderr)
sys.exit(1)
except NotADirectoryError:
print(os.path.basename(path))
return

if show_all:
entries = [".", ".."] + entries
else:
entries = [name for name in entries if not name.startswith(".")]

locale.setlocale(locale.LC_COLLATE, "")
entries = sorted(entries, key=locale.strxfrm)

for entry in entries:
print(entry)


def main():
show_all, path = parse_args(sys.argv[1:])
list_entries(path, show_all)


if __name__ == "__main__":
main()
107 changes: 107 additions & 0 deletions implement-shell-tools/wc/wc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3

import sys


def count_file(file):
try:
with open(file, 'r', encoding='utf-8') as f:
data = f.read()

lines = data.count('\n')
words = len(data.split())
bytes_count = len(data.encode('utf-8'))

return lines, words, bytes_count
except FileNotFoundError:
print(f"wc: {file}: No such file or directory", file=sys.stderr)
sys.exit(1)


def selected_keys(options):
if not any(options.values()):
return ['lines', 'words', 'bytes']

keys = []
if options['lines']:
keys.append('lines')
if options['words']:
keys.append('words')
if options['bytes']:
keys.append('bytes')
return keys


def values_for_keys(counts, keys):
lines, words, bytes_count = counts
mapping = {
'lines': lines,
'words': words,
'bytes': bytes_count,
}
return [mapping[key] for key in keys]


def print_rows(rows, keys):
align_columns = len(keys) > 1 or len(rows) > 1

if not align_columns:
values, name = rows[0]
print(f"{values[0]} {name}")
return

widths = []
for index in range(len(keys)):
max_len = max(len(str(values[index])) for values, _ in rows)
widths.append(max(3, max_len))

for values, name in rows:
formatted_values = " ".join(
f"{value:>{width}}" for value, width in zip(values, widths)
)
print(f"{formatted_values} {name}")

def main():
args = sys.argv[1:]
options = {
'lines': False,
'words': False,
'bytes': False,
}

files = []

for arg in args:
if arg == '-l':
options['lines'] = True
elif arg == '-w':
options['words'] = True
elif arg == '-c':
options['bytes'] = True
else:
files.append(arg)

if not files:
print("Usage: wc [-l | -w | -c] <file>...", file=sys.stderr)
sys.exit(1)

total_lines = 0
total_words = 0
total_bytes = 0
keys = selected_keys(options)
rows = []

for file in files:
lines, words, bytes_count = count_file(file)
total_lines += lines
total_words += words
total_bytes += bytes_count
rows.append((values_for_keys((lines, words, bytes_count), keys), file))

if len(files) > 1:
rows.append((values_for_keys((total_lines, total_words, total_bytes), keys), 'total'))

print_rows(rows, keys)

if __name__ == "__main__":
main()
Loading