diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 000000000..1d17dae13 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 000000000..66ca32785 --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -0,0 +1,65 @@ +import argparse +import os +from pathlib import Path + +# ===== Argument Handling ===== +parser = argparse.ArgumentParser( + prog="cat", + description="Prints file content", +) + +parser.add_argument("-b", action="store_true", help="Numbers lines that aren't empty") +parser.add_argument("-n", action="store_true", help="Numbers all lines") +parser.add_argument("file_names", nargs="*", help="Files for which to display content") + +args = parser.parse_args() + +# ===== cat Procedure ===== +def cat(args): + cwd = os.getcwd() + all_files_contents = read_files(args.file_names, cwd) + execute_flags(all_files_contents) + print_lines(all_files_contents) + +# ===== Extracting Data from Arguments ===== +def read_files(file_names, cwd): + all_files_contents = [] + + for file_name in file_names: + file_content = read_file(file_name, cwd) + all_files_contents.append(file_content.splitlines()) + return all_files_contents + +def read_file(file_name, cwd): + file_path = Path(cwd) / file_name + with open(file_path, "r", encoding="utf-8") as f: + return f.read().rstrip() + +# ===== Flag Handling ===== +def execute_flags(all_files_contents): + if args.b: + for file_content in all_files_contents: + line_number = 1 + for line_idx, line in enumerate(file_content): + if line != "": + file_content[line_idx] = f"{line_number:>6}\t{line}" + line_number += 1 + + elif args.n: + for file_idx, file_content in enumerate(all_files_contents): + all_files_contents[file_idx] = [ + f"{line_idx:>6}\t{line}" + for line_idx, line in enumerate(file_content, start=1) + ] + +def make_list(output_string): + return output_string.replace("\t", "\n").replace("\n\n", "\n") + +# ===== Print Output ===== +def print_lines(all_files_contents): + for file_content in all_files_contents: + for line in file_content: + print(line) + +# ===== Run cat ===== +cat(parser.parse_args()) diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 000000000..ff2f28582 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -0,0 +1,101 @@ +import argparse +import os +from pathlib import Path + +# Argument Handling +parser = argparse.ArgumentParser( + prog="ls", + description="Print line, word, and byte counts for each file.", +) + +parser.add_argument("-1", action="store_true", help="Show output on separate lines") +parser.add_argument("-a", action="store_true", help="Show hidden files") +parser.add_argument("file_system_items", nargs="*", help="Files or folders to display") + +args = parser.parse_args() + +# ===== ls Procedure ===== +def ls(args): + flag_status = {"print_in_list": False, "show_all": False} + execute_flags(flag_status) + + cwd = os.getcwd() + fs_items = check_args_length(args.file_system_items) + dir_args = get_dir_args(cwd, fs_items) + file_args = get_file_args(cwd, fs_items, flag_status) + + print_output(populate_output(cwd, fs_items, dir_args, file_args, flag_status), flag_status) + +# ===== Flag Handling ===== +def execute_flags(flag_status): + if getattr(args, "1"): + flag_status["print_in_list"] = True + if args.a: + flag_status["show_all"] = True + +def make_list(output_string): + return output_string.replace("\t", "\n").replace("\n\n", "\n") + +# Extracting Data from Arguments +def check_args_length(fs_items): + if len(fs_items) == 0: + fs_items.append(".") + return fs_items + +def get_dir_args(cwd, fs_items): + return [ + p + for p in fs_items + if (Path(cwd) / p).is_dir() + ] + +def get_file_args(cwd, fs_items, flag_status): + file_args = [ + p + for p in fs_items + if not (Path(cwd) / p).is_dir() + ] + if not flag_status["show_all"]: + file_args = remove_dot_files(file_args) + return file_args + +# Populating and Outputting Data +def populate_output(cwd, fs_items, dir_args, file_args, flag_status): + output_string = "" + + if len(fs_items) == 1: + for file in file_args: + output_string += file + " " + for dir_item in dir_args: + output_string += dir_output(dir_item, cwd, flag_status) + else: + for file in file_args: + output_string += file + "\t" + for dir_item in dir_args: + output_string += f"\n\n{dir_item}:\n" + dir_output(dir_item, cwd, flag_status) + return output_string + +def dir_output(dir_item, cwd, flag_status): + target_path = Path(cwd) / dir_item + contents = os.listdir(target_path) + output_str = "" + if flag_status["show_all"]: + output_str += ".\t..\t" + else: + contents = remove_dot_files(contents) + + for item in contents: + output_str += item + "\t" + return output_str + +def remove_dot_files(file_list): + return [item for item in file_list if not item.startswith(".")] + +def print_output(output_string, flag_status): + output = output_string + if flag_status["print_in_list"]: + output = make_list(output) + print(output.rstrip()) + +# ===== Run ls ===== +ls(parser.parse_args()) \ No newline at end of file diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 000000000..81bfc4027 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -0,0 +1,101 @@ +import argparse +import os +from pathlib import Path + + +# Argument Handling +parser = argparse.ArgumentParser( + prog="wc", + description="Print line, word, and byte counts for each file.", +) + +parser.add_argument("-l", action="store_true", help="Show line count") +parser.add_argument("-w", action="store_true", help="Show word count") +parser.add_argument("-c", action="store_true", help="Show byte size") +parser.add_argument("files", nargs="*", help="File names to process") + +args = parser.parse_args() + +# Global Variables +metrics = ["line_count", "word_count", "byte_size"] +displayed_metrics = [] + +# ===== wc Procedure ===== +def wc(args): + cwd = os.getcwd() + file_names = args.files + + execute_flags() + all_files_data = add_totals(extract_files_data(file_names, cwd)) + print_output(all_files_data) + +# ===== Flag Handling ===== +def execute_flags(): + if args.w: + displayed_metrics.append("word_count") + if args.l: + displayed_metrics.append("line_count") + if args.c: + displayed_metrics.append("byte_size") + +# ===== Extracting Files Data ===== +def extract_files_data(file_names, cwd): + all_files_data = [] + + for file_name in file_names: + file_data = {} + file_data["name"] = file_name + file_data["text"] = read_file(file_name, cwd) + file_data["line_count"] = calculate_line_count(file_data["text"]) + file_data["word_count"] = calculate_word_count(file_data["text"]) + file_data["byte_size"] = read_byte_size(file_name, cwd) + + all_files_data.append(file_data) + + return all_files_data + +def read_file(file_name, cwd): + file_path = Path(cwd) / file_name + return file_path.read_text(encoding="utf-8").rstrip() + +def calculate_line_count(text): + if not text: + return 0 + return len(text.splitlines()) + +def calculate_word_count(text): + return len(text.split()) + +def read_byte_size(file_name, cwd): + file_path = os.path.join(cwd, file_name) + return os.path.getsize(file_path) + +def add_totals(all_files_data): + if len(all_files_data) <= 1: + return all_files_data + + totals_data = {"name": "total"} + + for metric in metrics: + metric_sum = 0 + for file in all_files_data: + metric_sum += file[metric] + totals_data[metric] = metric_sum + + all_files_data.append(totals_data) + return all_files_data + +# ===== Outputting Data ===== +def print_output(output_data): + for file in output_data: + output_string = "" + + for metric in metrics: + if not displayed_metrics or metric in displayed_metrics: + output_string += str(file[metric]).rjust(8) + + output_string += f" {file['name']}" + print(output_string) + +# ===== Run wc ===== +wc(parser.parse_args()) \ No newline at end of file