Skip to content

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
79 changes: 79 additions & 0 deletions implement-shell-tools/cat/cat.py
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.rstrip("\n")
# 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}")
# 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}")
line_number += 1
else:
print(line_to_print)
# 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)





83 changes: 83 additions & 0 deletions implement-shell-tools/ls/ls.py
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()

# 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 chossen 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 filles included 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()

# 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}")

# 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)
100 changes: 100 additions & 0 deletions implement-shell-tools/wc/wc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Buil-in module to parse command-line arguments and options
import argparse
# Finds all file paths matching a pattern
import glob
# Buil-in module to provide functions for interacting with the operating system, open, write, manipulate (file size, paths etc.)
import os

# 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

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

# 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) - 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 conditinals
# output - a list with integers of libes, 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 files
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]))