diff --git a/.gitignore b/.gitignore index 3c3629e6..3e5a9836 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules +**/.venv + diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 00000000..59087bf6 --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -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)) \ No newline at end of file diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 00000000..f8e0f910 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -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)) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 00000000..063978aa --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -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))