-
-
Notifications
You must be signed in to change notification settings - Fork 15
London | Nadika Zavodovska | Module-Tools | Sprint 4 | Implement Shell Tools in Python #59
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
739c2cd
cd757bb
f246d1a
b2e2224
78cf465
825ad82
85c0f5a
ada6555
937b90b
1e4c0d0
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 |
---|---|---|
@@ -1 +1,2 @@ | ||
node_modules | ||
.venv |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# Buil-in module to parse command-line arguments and options | ||
import argparse | ||
# Finds all file paths matching a pattern | ||
import glob | ||
|
||
# Setup argument parser. Creates an instance(object) from built-in ArgumentParser class | ||
parser = argparse.ArgumentParser( | ||
prog="cat command", | ||
description="Implement 'cat' command with -n and -b flags", | ||
epilog="Now you can see the content of files with numbers of lines") | ||
|
||
# Define flags and arguments | ||
parser.add_argument("-n", action="store_true", help="Show the content with numbers of each lines") | ||
parser.add_argument("-b", action="store_true", help="Show the content with numbers of each lines which is not empty") | ||
# Add positional argument for one or more file paths (nargs="+") | ||
parser.add_argument("paths", nargs="+", help="Specify file paths, included wildcards") | ||
|
||
# Create args - an instance of the class argparse.Namespace | ||
args = parser.parse_args() | ||
|
||
# A global variable to keep track of line numbers across all files | ||
line_number = 1 | ||
|
||
# Function to display content and lines for specific condition which a user provides | ||
def display_file_contents(file_path, display_line_numbers = False, display_not_empty_line_numbers = False): | ||
# Make sure that this variable refers to the global line_number to persist across different files | ||
global line_number | ||
try: | ||
# Open file. "r" - read mode. with - ensure the file will be closed after the block of code finishes running. | ||
with open(file_path, "r", encoding = "utf-8") as file_object: | ||
# loopp through each line | ||
for line in file_object: | ||
# rstrip("\n") - removes any trailing newline character (\n) from the end of each line | ||
line_to_print = line | ||
# if a user set "-b" flag | ||
if display_not_empty_line_numbers: | ||
# Print if there is not empty line after removing any whitespace from the start and end of the line | ||
if line.strip(): | ||
# {line_number:6} - string formatting | ||
print(f"{line_number:6} {line_to_print}", end="") | ||
# Increments the line number after printing the line. | ||
line_number += 1 | ||
else: | ||
print(line_to_print) | ||
elif display_line_numbers: | ||
# f - f-string, formatted string literal | ||
print(f"{line_number:6} {line_to_print}", end="") | ||
line_number += 1 | ||
else: | ||
print(line_to_print, end="") | ||
# Handle errors | ||
|
||
# Exception - is the base class for all built-in exceptions | ||
# error - an exception object | ||
except Exception as error: | ||
print(f"Error reading file {file_path}: {error}") | ||
|
||
# Collect all matched files into matched_files list | ||
matched_files = [] | ||
# args.paths- is a list of file paths provided by the user | ||
# glob.glob() takes a string path pattern from user, and returns a list of all files that match it. | ||
# Add, using .extend() method, to the matched_files list | ||
for path in args.paths: | ||
matched_files.extend(glob.glob(path)) | ||
|
||
# print error message if file is not exist | ||
if not matched_files: | ||
print(f"No files matched the given pattern: {args.paths}") | ||
# exit(1) - stop the program with 1 code that indicates the error. exit(0) | ||
exit(1) | ||
|
||
# For in loop to process each file using for in loop | ||
for file_path in matched_files: | ||
display_file_contents(file_path, display_line_numbers = args.n, display_not_empty_line_numbers = args.b) | ||
|
||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# Buil-in module to parse command-line arguments and options | ||
import argparse | ||
# Buil-in module to provide functions for interacting with the operating system, open, write, manipulate (listing directory contents) | ||
import os | ||
# For converting the given path to an absolute path | ||
from pathlib import Path | ||
# For colouring terminal output (blue for directories) | ||
from colorama import Fore, Style, init | ||
|
||
# Sets up colorama for blue colour for directories | ||
init() | ||
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's generally considered best practice to have function definitions first, top level code at the end of the file. It makes it easier if all of the top level logic is in one place, and where another coder would expect to find it. |
||
|
||
# Function to list directories, files | ||
def list_directory_files(file_path, display_hidden_files = False, display_one_per_line = False): | ||
try: | ||
# Get all directories and files using os module listdir() method and return a list | ||
entries = os.listdir(file_path) | ||
# A list to store . and .. | ||
dot_dirs = [] | ||
|
||
# If the user set '-a', will store '.', '..' | ||
if display_hidden_files: | ||
# Adds "." (current directory) and ".." (parent directory) to the list | ||
dot_dirs = [".", ".."] | ||
|
||
# Create an empty list to store non-hidden files, directories | ||
visible_entries = [] | ||
|
||
for entry in entries: | ||
# If it is non-hidden files, add it to the list | ||
if not entry.startswith(".") or display_hidden_files: | ||
visible_entries.append(entry) | ||
# Sort alphabetically lamba function for ignoring "." and case-insensitive | ||
visible_entries.sort(key=lambda name: name.lstrip(".").lower()) | ||
|
||
sorted_entries = dot_dirs + visible_entries | ||
# Create a list for styled directories, files | ||
styled_entries = [] | ||
|
||
# For in loop for styling directories | ||
for entry in sorted_entries: | ||
# Create the full path of the item by combining | ||
full_path = os.path.join(file_path, entry) | ||
# Check for directory | ||
if os.path.isdir(full_path): | ||
# Apply styles for directory, add to the styled_entries | ||
styled_entries.append(Fore.BLUE + Style.BRIGHT + entry + Style.RESET_ALL) | ||
else: | ||
# If file, just add to the list | ||
styled_entries.append(entry) | ||
|
||
# If the user set a "-1" flag, print each entry on a new line | ||
if display_one_per_line: | ||
print("\n".join(styled_entries)) | ||
# If the user doesn't set a "-1" flag, print all entries on the same line | ||
else: | ||
print(" ".join(styled_entries)) | ||
|
||
# Handle errors | ||
|
||
# Exception - is the base class for all built-in exceptions | ||
# error - an exception object | ||
except Exception as error: | ||
print(f"Error reading directory: {error}") | ||
|
||
# Setup argument parser. Creates an instance(object) from built-in ArgumentParser class | ||
parser = argparse.ArgumentParser(prog="ls command", | ||
description="Implement 'ls' command with -1 and -a flags", | ||
epilog="Now you can see the files in the chosen path" | ||
) | ||
# Define flags and arguments | ||
# Define dest="one_per_line" for "-1", because -1 is not valid Python variable | ||
parser.add_argument("-1", dest="one_per_line", action="store_true", help = "Display each file on a new line") | ||
parser.add_argument("-a", action="store_true", help = "Display all files including hidden files") | ||
# Positional argument. Defaults to . (current directory) if not provided. nargs="?" - optional, takes 0 or 1 value | ||
parser.add_argument("path", nargs="?", default = ".", help = "Specify file path to the list") | ||
|
||
# Parse the command-line arguments. args - an instance of the class argparse.Namespace | ||
args = parser.parse_args() | ||
|
||
# Get absolute path | ||
absolute_path = Path(args.path).resolve() | ||
list_directory_files(str(absolute_path), display_hidden_files=args.a, display_one_per_line=args.one_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. You convert the Path object to a string and pass it to your function. An alternative you might want to consider (I'm not saying you have to do it for this exercise, but it might be a good learning experience) having your function take a Path argument instead of a string, and use the Path functions to do a lot of the work inside the function |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# Buil-in module to parse command-line arguments and options | ||
import argparse | ||
# For converting the given path to an absolute path | ||
from pathlib import Path | ||
# For colouring terminal output (blue for directories) | ||
from colorama import Fore, Style, init | ||
|
||
# Sets up colorama for blue colour for directories | ||
init() | ||
|
||
# Function to list directories, files | ||
def list_directory_files(dir_path: Path, display_hidden_files=False, display_one_per_line=False): | ||
try: | ||
# Get all entries (files and directories) as Path objects | ||
entries = list(dir_path.iterdir()) | ||
# A list to store . and .. | ||
dot_dirs = [] | ||
# If the user set '-a', will store '.', '..' | ||
if display_hidden_files: | ||
# Adds "." (current directory) and ".." (parent directory) to the list | ||
dot_dirs = [Path("."), Path("..")] | ||
|
||
# Filter out hidden files if not showing them | ||
if not display_hidden_files: | ||
entries = [e for e in entries if not e.name.startswith(".")] | ||
|
||
# Combine dot_dirs with the visible entries list | ||
visible_entries = dot_dirs + entries | ||
|
||
# Sort alphabetically lamba function for ignoring "." and case-insensitive | ||
visible_entries.sort(key=lambda p: p.name.lstrip(".").lower()) | ||
# Create a list for styled directories, files | ||
styled_entries = [] | ||
|
||
# For in loop for styling directories | ||
for entry in visible_entries: | ||
# Check for directory | ||
if entry.is_dir(): | ||
# Apply styles for directory, add to the styled_entries | ||
styled_entries.append(Fore.BLUE + Style.BRIGHT + entry.name + Style.RESET_ALL) | ||
else: | ||
# If file, just add to the list | ||
styled_entries.append(entry.name) | ||
|
||
# If the user set a "-1" flag, print each entry on a new line | ||
if display_one_per_line: | ||
print("\n".join(styled_entries)) | ||
# If the user doesn't set a "-1" flag, print all entries on the same line | ||
else: | ||
print(" ".join(styled_entries)) | ||
|
||
# Handle errors | ||
|
||
# Exception - is the base class for all built-in exceptions | ||
# error - an exception object | ||
|
||
except Exception as error: | ||
print(f"Error reading directory: {error}") | ||
|
||
# Setup argument parser. Creates an instance(object) from built-in ArgumentParser class | ||
parser = argparse.ArgumentParser(prog="ls command", | ||
description="Implement 'ls' command with -1 and -a flags", | ||
epilog="Now you can see the files in the chosen path" | ||
) | ||
# Define flags and arguments | ||
# Define dest="one_per_line" for "-1", because -1 is not valid Python variable | ||
parser.add_argument("-1", dest="one_per_line", action="store_true", help = "Display each file on a new line") | ||
parser.add_argument("-a", action="store_true", help = "Display all files including hidden files") | ||
# Positional argument. Defaults to . (current directory) if not provided. nargs="?" - optional, takes 0 or 1 value | ||
parser.add_argument("path", nargs="?", default = ".", help = "Specify file path to the list") | ||
|
||
# Parse the command-line arguments. args - an instance of the class argparse.Namespace | ||
args = parser.parse_args() | ||
|
||
# Get absolute path | ||
absolute_path = Path(args.path).resolve() | ||
list_directory_files(absolute_path, display_hidden_files=args.a, display_one_per_line=args.one_per_line) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
colorama |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Built-in module to parse command-line arguments and options | ||
import argparse | ||
# Finds all file paths matching a pattern | ||
import glob | ||
# Built-in module to provide functions for interacting with the operating system, open, write, manipulate (file size, paths etc.) | ||
import os | ||
|
||
# Function to compute line, word, and byte counts. filepath - a string (path to a file) | ||
def wc_counts(filepath): | ||
try: | ||
# Open file. "r" - read mode. with - ensure the file will be closed after the block of code finishes running. | ||
with open(filepath, "r", encoding="utf-8") as file_object: | ||
# Read all the entire content and returns it as a string in file_content_string | ||
file_content_string = file_object.read() | ||
# Count the number of newline characters by using .count() method - counts the number of occurrences of the specified substring | ||
lines = file_content_string.count("\n") | ||
# Count the number of words (split by any whitespace by default) using length of the list | ||
words = len(file_content_string.split()) | ||
# Get the size of the file in bytes. getsize() returns the size of the file in bytes | ||
byte_size = os.path.getsize(filepath) | ||
return lines, words, byte_size | ||
|
||
# Handle errors | ||
|
||
# Exception - is the base class for all built-in exceptions | ||
# error - an exception object | ||
except Exception as error: | ||
# f - f-string, formatted string literal | ||
print(f"Error reading file {filepath}: {error}") | ||
# return fallback values | ||
return 0, 0, 0 | ||
|
||
# Setup argument parser. Creates an instance(object) from built-in ArgumentParser class | ||
parser = argparse.ArgumentParser( | ||
prog="wc command", | ||
description="Implementing 'wc' command (-c, -w, -l flags) functionality in Python", | ||
epilog="That's how you count the bytes, words, and lines" | ||
) | ||
|
||
# Define flags and arguments | ||
parser.add_argument("-l", action="store_true", help="Count lines") | ||
parser.add_argument("-w", action="store_true", help="Count words") | ||
parser.add_argument("-c", action="store_true", help="Count bytes") | ||
# Add positional argument for one or more file paths (nargs="+") | ||
parser.add_argument("paths", nargs="+", help="File paths (wildcards supported)") | ||
|
||
# Parse the command-line arguments. args - an instance of the class argparse.Namespace | ||
args = parser.parse_args() | ||
|
||
# If no flags are provided, display all flags | ||
display_all = not (args.l or args.w or args.c) | ||
|
||
# Initialisation of count total of lines, words, bytes | ||
total_lines, total_words, total_bytes = 0, 0, 0 | ||
|
||
# Collect all matched files into matched_files list | ||
matched_files = [] | ||
# args.paths- is a list of file paths provided by the user | ||
# glob.glob() takes a string path pattern from user, and returns a list of all files that match it. | ||
# Add, using .extend() method, to the matched_files list | ||
for path in args.paths: | ||
matched_files.extend(glob.glob(path)) | ||
|
||
# print error message if file does not exist | ||
if not matched_files: | ||
print(f"No files matched the given pattern: {args.paths}") | ||
# exit(1) - stop the program with 1 code that indicates the error. exit(0) - indicates the program ran successfully | ||
exit(1) | ||
|
||
# For in loop to process each file using for in loop | ||
for filepath in matched_files: | ||
# Get counts for the current file | ||
lines, words, byte_size = wc_counts(filepath) | ||
|
||
# Accumulate totals for summary (if multiple files) | ||
total_lines += lines | ||
total_words += words | ||
total_bytes += byte_size | ||
|
||
# Format the output line. str() - convert integer to a string, .rjust(7)- right-aligns the string representation of the number by padding it with spaces to a total length of 7 characters | ||
# None - don't apply conditionals | ||
# output - a list with integers of lines, words, bytes and file path | ||
output = [ | ||
str(lines).rjust(3) if args.l or display_all else None, | ||
str(words).rjust(3) if args.w or display_all else None, | ||
str(byte_size).rjust(3) if args.c or display_all else None, | ||
filepath | ||
] | ||
# Print the output line (filter out None entries) | ||
print(" ".join([item for item in output if item is not None])) | ||
|
||
# Conditional for multiple files to count and print totals of lines, words, bytes for each file | ||
if len(matched_files) > 1: | ||
output = [ | ||
str(total_lines).rjust(3) if args.l or display_all else None, | ||
str(total_words).rjust(3) if args.w or display_all else None, | ||
str(total_bytes).rjust(3) if args.c or display_all else None, | ||
"total" | ||
] | ||
print(" ".join([item for item in output if item is not None])) |
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.
Colorama isn't a standard library is it? Doesn't this mean you need a requirements.txt?