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
19 changes: 18 additions & 1 deletion implement-shell-tools/cat/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Implement `cat`
# Implement `cat` in python

You should already be familiar with the `cat` command line tool.

Expand All @@ -15,3 +15,20 @@ It must act the same as `cat` would, if run from the directory containing this R
Matching any additional behaviours or flags are optional stretch goals.

We recommend you start off supporting no flags, then add support for `-n`, then add support for `-b`.

*========commands for running the scripts========

* No flags
python cat.py sample-files/1.txt

* Number every line (-n)
python cat.py -n sample-files/1.txt

* Display all files in directory(shell expands *.txt)
python cat.py sample-files/*.txt

* Number every line across multiple files(including empty ones)
python cat.py -n sample-files/*.txt

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:

$ cat -n sample-files/*.txt
     1  Once upon a time...
     2  There was a house made of gingerbread.
     3  It looked delicious.
     4  I was tempted to take a bite of it.
     5  But this seemed like a bad idea...
     6
     7  There's more to come, though...
     
$ python cat.py -n sample-files/*.txt
1 Once upon a time...
2 
3 There was a house made of gingerbread.
4 
5 It looked delicious.
6 I was tempted to take a bite of it.
7 But this seemed like a bad idea...
8 
9 There's more to come, though...
10 


* Number non-empty lines only (-b)
python cat.py -b sample-files/3.txt
50 changes: 50 additions & 0 deletions implement-shell-tools/cat/cat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import argparse
# -------------------------------------------

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok to have comments, but it would be even better to have the code split into methods with descriptive names :)

# Set up the argument parser
# -------------------------------------------

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()

# -------------------------------------------
# Implement functionality
# -------------------------------------------

line_number = 1

for filepath in args.paths:
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()

lines = content.split("\n")
Comment on lines +33 to +35

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe check if you could use readlines method here.


for line in lines:
if args.number_all_lines:
print(f"{line_number} {line}")
line_number += 1

elif args.number_non_empty_lines:
if line.strip() == "":
print(line)
else:
print(f"{line_number} {line}")
line_number +=1

else:
print(line)
5 changes: 5 additions & 0 deletions implement-shell-tools/ls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ It must act the same as `ls` 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 just `-1`, then adding support for `-a`.

*=======command for running the script===========
python3 ls_py.py -1
python3 ls_py.py -1 sample-files
python3 ls_py.py -1 -a sample-files
73 changes: 73 additions & 0 deletions implement-shell-tools/ls/ls_py.py
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):

Choose a reason for hiding this comment

The 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()
19 changes: 18 additions & 1 deletion implement-shell-tools/wc/README.md
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.

Expand All @@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like some outputs are different?

wc sample-files/*
  1   4  20 sample-files/1.txt
  1   7  39 sample-files/2.txt
  5  24 125 sample-files/3.txt
  7  35 184 total
$ python wc.py sample-files/*
2 4 20 sample-files/1.txt
2 7 39 sample-files/2.txt
6 24 125 sample-files/3.txt
10 35 184 total


* 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/*
93 changes: 93 additions & 0 deletions implement-shell-tools/wc/wc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import argparse
import sys
import re


def main():
# --------------------------------------------------------

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, maybe try splitting into functions instead of comment blocks instead :)

# Set up argparse
# --------------------------------------------------------
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()

# --------------------------------------------------------
# Ensures at least one path exists
# --------------------------------------------------------
if len(args.paths) == 0:
print("wc: no file specified", file=sys.stderr)
sys.exit(1)

totals= {"lines": 0, "words": 0, "chars": 0}

# --------------------------------------------------------
# Loop over each file path and process it
# --------------------------------------------------------
for file_path in args.paths:
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except OSError as err:
print(f"wc: cannot read file'{file_path}': {err}", file=sys.stderr)
continue

# --------------------------------------------------------
# Count values
# --------------------------------------------------------
line_count = len(content.split("\n"))

words = [w for w in re.split(r"\s+", content) if w]
word_count = len(words)

char_count = len(content)

totals["lines"] += line_count
totals["words"] += word_count
totals["chars"] +=char_count

# --------------------------------------------------------
# Decide what to print based on flags
# --------------------------------------------------------
no_flags = not args.l and not args.w and not args.c

if no_flags:
print(f"{line_count} {word_count} {char_count} {file_path}")
continue

if args.l:
print(f"{line_count} {file_path}" )

if args.w:
print(f"{word_count} {file_path}")

if args.c:
print(f"{char_count} {file_path}")


# --------------------------------------------------------
# Print totals if there are multiple files
# --------------------------------------------------------
if len(args.paths) > 1:

Choose a reason for hiding this comment

The 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

no_flags = not args.l and not args.w and not args.c

if no_flags:
print(f"{totals['lines']} {totals['words']} {totals['chars']} total")

if args.l:
print(f"{totals['lines']} total")
if args.w:
print(f"{totals['words']} total")
if args.c:
print(f"{totals['chars']} total")

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