-
-
Notifications
You must be signed in to change notification settings - Fork 42
London | SDC-Nov-2025 | Ikenna Agulobi | Sprint 4 | Implement shell tools python #301
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
1bce56a
4f141b3
7a738ae
da1d1cc
0a84822
4471a05
ebd5f71
62b3a4b
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,63 @@ | ||
| import argparse | ||
|
|
||
| def parse_args(): | ||
| parser = argparse.ArgumentParser( | ||
| prog ="display-file-content", | ||
| description = "Implement cat command with -n and -b flag support", | ||
| ) | ||
|
|
||
| parser.add_argument("-n", "--number-all-lines", | ||
| action="store_true", | ||
| help="Number every line in the file" | ||
| ) | ||
|
|
||
| parser.add_argument("-b", "--number-non-empty-lines", | ||
| action="store_true", | ||
| help="Number non empty lines in the file" | ||
| ) | ||
|
|
||
| parser.add_argument("paths", nargs="+", help="File paths to process") | ||
|
|
||
| args = parser.parse_args() | ||
| return args | ||
|
|
||
|
|
||
| def print_line(line, line_number = None): | ||
| if line_number is None: | ||
| print(line, end="") | ||
| else: | ||
| print(f"{line_number} {line}", end="") | ||
|
|
||
| def process_line(line, args, line_number): | ||
| if args.number_non_empty_lines: | ||
| if line.strip() == "": | ||
| print_line(line) | ||
| else: | ||
| print_line(line, line_number) | ||
| line_number += 1 | ||
|
|
||
| elif args.number_all_lines: | ||
| print_line(line, line_number) | ||
| line_number +=1 | ||
|
|
||
| else: | ||
| print_line(line) | ||
|
|
||
| return line_number | ||
|
|
||
| def process_file(filepath, args, line_number): | ||
| """Read a file and print its lines. Returns updated line_number.""" | ||
| with open(filepath, "r", encoding="utf-8") as f: | ||
| for line in f: | ||
| line_number = process_line(line, args, line_number) | ||
| return line_number | ||
|
|
||
| def main(): | ||
| args = parse_args() | ||
| line_number = 1 | ||
|
|
||
| for filepath in args.paths: | ||
| line_number = process_file(filepath, args, line_number) | ||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| #!/usr/bin/env python3 | ||
|
|
||
| import argparse | ||
| import os | ||
| import sys | ||
|
|
||
|
|
||
| # Set up the argument parser | ||
| def parse_args(): | ||
| parser = argparse.ArgumentParser( | ||
| prog="list_files_in_directory", | ||
| description="Implement ls commands to list files in directory" | ||
| ) | ||
|
|
||
| parser.add_argument( | ||
| "-1","--file-per-line", | ||
| action="store_true", | ||
| dest="file_per_line", | ||
| help="list files one per line" | ||
| ) | ||
|
|
||
| parser.add_argument( | ||
| "-a", | ||
| "--files-and-hidden-ones", | ||
| action="store_true", | ||
| dest="files_and_hidden_ones", | ||
| help="list all files including hidden ones", | ||
| ) | ||
|
|
||
| parser.add_argument( | ||
| "paths", | ||
| nargs="*", | ||
| help="directories to list" | ||
| ) | ||
|
|
||
| return parser.parse_args() | ||
|
|
||
| # if no paths, default to current directory | ||
| def get_paths(args): | ||
| if len(args.paths) == 0: | ||
| return ["."] | ||
| return args.paths | ||
|
|
||
| # list a single directory | ||
| def list_directory(directory_path, show_hidden, file_per_line): | ||
|
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. It is good as is, but as a suggestion - a tiny bit cleaner approach would be to first get the list of directories/files and for list_directory function to just accept this list and print it (filtering whatever is not needed). This would follow 1 method/1 responsibility paradigm (which helps with code readability / reusability). |
||
| try: | ||
| entries = os.listdir(directory_path) | ||
| except OSError as err: | ||
| print(f"ls: cannot access '{directory_path}': {err}", file=sys.stderr) | ||
| return | ||
|
|
||
| if not show_hidden: | ||
| entries = [name for name in entries if not name.startswith(".")] | ||
|
|
||
| if file_per_line: | ||
| for name in entries: | ||
| print(name) | ||
| else: | ||
| print(" ".join(entries)) | ||
|
|
||
| def main(): | ||
| args = parse_args() | ||
| paths = get_paths(args) | ||
|
|
||
| for directory_path in paths: | ||
| list_directory( | ||
| directory_path=directory_path, | ||
| show_hidden=args.files_and_hidden_ones, | ||
| file_per_line=args.file_per_line, | ||
| ) | ||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # Implement `wc` | ||
| # Implement `wc` in python | ||
|
|
||
| You should already be familiar with the `wc` command line tool. | ||
|
|
||
|
|
@@ -15,3 +15,20 @@ It must act the same as `wc` would, if run from the directory containing this RE | |
| Matching any additional behaviours or flags are optional stretch goals. | ||
|
|
||
| We recommend you start off supporting no flags for one file, then add support for multiple files, then add support for the flags. | ||
|
|
||
| *======Command for testing the script======== | ||
|
|
||
| * All sample files | ||
| python wc.py sample-files/* | ||
|
|
||
| * Just lines | ||
| python wc.py -l sample-files/3.txt | ||
|
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. Looks like some outputs are different?
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. Hi @DaryaShirokova - thanks for pointing this out. The issue was caused because I used split("\n"), which overcounts when a file ends with a newline. I've now updated my implementation to use content.count("\n"), and the output now matches wc as expected. |
||
|
|
||
| * Just words | ||
| python wc.py -w sample-files/3.txt | ||
|
|
||
| * Just characters | ||
| python wc.py -c sample-files/3.txt | ||
|
|
||
| * Lines with multiple files (to see totals) | ||
| python wc.py -l sample-files/* | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import argparse | ||
| import sys | ||
| import re | ||
|
|
||
|
|
||
| def main(): | ||
|
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. Optional: you could try to organize the code into multiple methods for readability |
||
| parser= argparse.ArgumentParser( | ||
| prog="wc", | ||
| description ="wc command implementation in python" | ||
| ) | ||
|
|
||
| parser.add_argument("-l", action="store_true", help ="show number of lines only") | ||
| parser.add_argument("-w", action="store_true", help="show number of words only") | ||
| parser.add_argument("-c", action="store_true", help="show number of characters only") | ||
|
|
||
| parser.add_argument("paths", nargs="*", help="file paths to process") | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| if len(args.paths) == 0: | ||
| print("wc: no file specified", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| totals= {"lines": 0, "words": 0, "chars": 0} | ||
|
|
||
| no_flags = not args.l and not args.w and not args.c | ||
| width = 8 | ||
|
|
||
| def format_output(counts, label): | ||
| if no_flags: | ||
| return f"{counts['lines']:>{width}}{counts['words']:>{width}}{counts['chars']:>{width}} {label}" | ||
|
|
||
| parts = [] | ||
| if args.l: | ||
| parts.append(f"{counts['lines']:>{width}}") | ||
| if args.w: | ||
| parts.append(f"{counts['words']:>{width}}") | ||
| if args.c: | ||
| parts.append(f"{counts['chars']:>{width}}") | ||
|
|
||
| return f"{''.join(parts)} {label}" | ||
| had_error = False | ||
|
|
||
| for file_path in args.paths: | ||
| try: | ||
| with open(file_path, "rb") as f: | ||
| data = f.read() | ||
| except OSError as err: | ||
| print(f"wc: cannot read file' {file_path} ': {err}", file=sys.stderr) | ||
| had_error = True | ||
| continue | ||
|
|
||
| content = data.decode("utf-8", errors="replace") | ||
|
|
||
| line_count = content.count("\n") | ||
|
|
||
| words = [w for w in re.split(r"\s+", content) if w] | ||
| word_count = len(words) | ||
| char_count = len(data) | ||
|
|
||
| counts = {"lines": line_count, "words": word_count, "chars":char_count} | ||
|
|
||
| totals["lines"] += line_count | ||
| totals["words"] += word_count | ||
| totals["chars"] +=char_count | ||
|
|
||
| print(format_output(counts, file_path)) | ||
|
|
||
| if len(args.paths) > 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. Maybe there is a way to reuse the same functions (with different args) for printing individual lines and totals |
||
| print(format_output(totals, "total")) | ||
|
|
||
| sys.exit(1 if had_error else 0) | ||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a small discrepancy in how you program handles some cases it seems:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing out the discrepancy. I’ve fixed the line-numbering logic so empty lines are handled correctly, matching cat behaviour. Thank you.