-
-
Notifications
You must be signed in to change notification settings - Fork 105
London | 26-SDC-July | Raihan Sharif | Sprint 4 | implement shell tools python #649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
38c96e9
0591f3f
6020eef
108fef7
0c5aeb2
9361dde
3617742
b28fa81
4f62b37
2ad95a6
36c79a6
34e629a
2d9a740
ef611c6
575f0bf
87d8179
4995bfc
0e5e2ee
1ea4d3e
085a1ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import argparse | ||
| import sys | ||
|
|
||
| parser = argparse.ArgumentParser( | ||
| prog="a simple cat implementation", | ||
| description="cat command line tool with the -n and -b flags" | ||
| ) | ||
|
|
||
| parser.add_argument("-n", action="store_true", help="number all output lines") | ||
| parser.add_argument("-b", action="store_true", help="number non-empty output lines") | ||
| parser.add_argument("paths", nargs="+", help="file path or paths", ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| # cat returns different error messages depending on the reason the path could be read | ||
| def read_file(path): | ||
| """Returns (content, error_message). error_message is None on success""" | ||
| try: | ||
| with open(path, "r", encoding="utf-8") as f: | ||
| return f.read(), None | ||
| except FileNotFoundError: | ||
| return None, f"cat: {path}: No such file or directory" | ||
| except IsADirectoryError: | ||
| return None, f"cat: {path}: Is a directory" | ||
| except PermissionError: | ||
| return None, f"cat: {path}: Permission denied" | ||
|
|
||
|
|
||
| # -b (number the non-empty lines) takes priority over -n (number all lines) | ||
| # if both are present | ||
| def format_lines(lines, number_all=False, number_nonempty=False): | ||
| """Returns a list of formatted output lines""" | ||
| output = [] | ||
|
|
||
| if number_nonempty: | ||
| line_num = 0 | ||
| for line in lines: | ||
| if line == "": | ||
| output.append("") | ||
| else: | ||
| line_num += 1 | ||
| # {line_num:6} right justied number, length of at least 6 | ||
| # {some_str:6} left justifed string, length fo at least 6 | ||
| output.append(f"{line_num:6}\t{line}") | ||
| elif number_all: | ||
| for i, line in enumerate(lines, start=1): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. format_lines numbers with enumerate(..., start=1) and is called fresh for each file in cat_file. Run cat -n
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, the numbering starts from 1 for each file in the cat input python cat: You asked this in the nodeJS version of cat as well. Are you hinting at something, and I'm just not getting it? |
||
| output.append(f"{i:6}\t{line}") | ||
| else: | ||
| output = lines | ||
|
|
||
| return output | ||
|
|
||
|
|
||
| # TODO: runner function to call read_file, and feed it into formatLines, then print | ||
| def cat_file(path, number_all=False, number_nonempty=False): | ||
| """ | ||
| Calls read_file -> format_lines -> prints formatted line. | ||
| Returns True if file read successfully, else returns False | ||
|
|
||
| If failed to read file, prints error to stderr | ||
| """ | ||
| content, error = read_file(path) | ||
| if (error): | ||
| print(error, file=sys.stderr) | ||
| return False | ||
|
|
||
| # splitlines automatically trims trailing empty lines | ||
| lines = content.splitlines() | ||
| for line in format_lines(lines, number_all, number_nonempty): | ||
| print(line) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| def main(): | ||
| # cat exits with error code 1 if any file read fails | ||
| file_error = False | ||
|
|
||
| for path in args.paths: | ||
| line_num = 1 | ||
| is_success = cat_file(path, args.n, args.b) | ||
|
|
||
| if not is_success: | ||
| file_error = True | ||
|
|
||
| # if at any point, file reading failed file error is set to True, | ||
| # and program exist with code 1 after all tasks completed | ||
| sys.exit(1 if file_error else 0) | ||
|
|
||
| # ensures that main only runs when this file/module is directly executed | ||
| # not when it is imported, for example, for automated tests | ||
| if __name__ == "__main__": | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import argparse | ||
| import sys | ||
| import os | ||
|
|
||
| parser = argparse.ArgumentParser( | ||
| prog="a simple version of ls", | ||
| description="ls command line tool which can accept 0 or more arguements" \ | ||
| "and take -a and -1 flags") | ||
|
|
||
| parser.add_argument("-a", action="store_true", help="show all files, including dot files") | ||
|
|
||
| # can't store as an attribute of Namespace object, because 1 is not a valid python identifier | ||
| # but can store under the name given in the dest argument. When working with this | ||
| # parser, look for "opt_one", not "1". | ||
| parser.add_argument("-1", dest="opt_one", action="store_true", help="show one file/directory name per line") | ||
|
|
||
| # takes 0 more arguments, if none are given, sets "." as default value | ||
| parser.add_argument("paths", nargs="*", help="file/directory path(s) to display", default=".") | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
|
|
||
| def get_dir_entries(path, aFlag=args.a): | ||
| # warning: listdir() prints current directory by default | ||
| entries = os.listdir(path) | ||
| entries = [".", ".."] + entries | ||
| entries.sort() | ||
|
|
||
| if (not args.a): | ||
| entries = [entry for entry in entries if not entry.startswith(".")] | ||
|
|
||
| return entries | ||
|
|
||
|
|
||
| def print_entries(entries, onePerLineFlag = args.opt_one): | ||
| if (onePerLineFlag): | ||
| for entry in entries: | ||
| print(entry) | ||
| elif (len(entries) > 0): | ||
| for i in range(len(entries)-1): | ||
| print(f"{entries[i]}\t", end="") | ||
| print(f"{entries[-1]}") | ||
|
|
||
|
|
||
| def main(): | ||
| # file and directory paths are processed separately | ||
| # file_args = [arg for arg in args.paths if os.path.isfile(arg)] | ||
| # dir_args = [arg for arg in args.paths if os.path.isdir(arg)] | ||
|
|
||
| file_args = [] | ||
| dir_args = [] | ||
| invalid_args = [] | ||
|
|
||
| # this is a simplication, it groups all errors under "invalid file" | ||
| # real ls would have different messages things like permission denied | ||
| # also bad because it makes two syscalls | ||
| for arg in args.paths: | ||
| if (os.path.isfile(arg)): | ||
| file_args.append(arg) | ||
| elif (os.path.isdir(arg)): | ||
| dir_args.append(arg) | ||
| else: | ||
| invalid_args.append(arg) | ||
|
|
||
| for arg in invalid_args: | ||
| print(f"ls: {arg}: No such file or directory", file=sys.stderr) | ||
|
|
||
| if (len(file_args) > 0): | ||
| print_entries(file_args) | ||
|
|
||
| for index, path in enumerate(dir_args, start=0): | ||
| if (len(args.paths) > 1): | ||
| if (index > 0 or len(file_args) > 0): | ||
| print("") | ||
| print(f"{path}:") | ||
| print_entries(get_dir_entries(path)) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import argparse | ||
| import sys | ||
| import os | ||
|
|
||
| # TODO: decompose into functions to make it more modular and reusable | ||
|
|
||
| parser = argparse.ArgumentParser( | ||
| prog="a simple version of wc. Takes in one or more files.", | ||
| description="ls command line tool which can accept -l -w -c cflags") | ||
|
|
||
| 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 count") | ||
|
|
||
| parser.add_argument("paths", nargs="*", help="file(s) for which to show data") | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| totals = {"l": 0, "w": 0, "c": 0} | ||
|
|
||
| # if no flags then set all flags to true, same as in real wc | ||
| if (not args.l and not args.w and not args.c): | ||
| args.l = args.w = args.c = True | ||
|
|
||
| for path in args.paths: | ||
| if (not os.path.exists(path)): | ||
| print(f"wc: {path}: open: No such file or directory", file=sys.stderr) | ||
| elif (os.path.isdir(path)): | ||
| print(f"wc: {path}: read: Is a directory", file=sys.stderr) | ||
| elif (os.path.isfile(path)): | ||
| output_str = "" | ||
| with open(path, "r", encoding="utf-8") as file: | ||
| content = file.read() | ||
|
|
||
| lines = content.split('\n') | ||
|
|
||
| if (args.l): | ||
| #if (len(lines)) > 0: | ||
| # lines[-1].strip() | ||
|
|
||
| line_count = content.count('\n') | ||
| totals["l"] += line_count | ||
| output_str += f"{line_count:8}" | ||
|
|
||
| if (args.w): | ||
| word_count = len(content.split()) | ||
| totals["w"] += word_count | ||
| output_str += f"{word_count:8}" | ||
|
|
||
| if (args.c): | ||
| bytes = os.path.getsize(path) | ||
| totals["c"] += bytes | ||
| output_str += f"{bytes:8}" | ||
|
|
||
| output_str += f" {path}" | ||
| print(output_str) | ||
|
|
||
| if (len(args.paths) > 1): | ||
| res = {key : val for key, val in totals.items() | ||
| if val != 0} | ||
| total_str = "" | ||
| for v in res.values(): | ||
| total_str += f"{v:8}" | ||
|
|
||
| total_str += " total" | ||
| print(total_str) | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
Uh oh!
There was an error while loading. Please reload this page.