From 739c2cd9ad898d27906bff8dc6a3fbc072d78f1b Mon Sep 17 00:00:00 2001 From: nadika Date: Thu, 10 Apr 2025 14:42:15 +0100 Subject: [PATCH 1/4] Implement wc command with -l, -c, -w flags --- implement-shell-tools/wc/wc.py | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 implement-shell-tools/wc/wc.py diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py new file mode 100644 index 0000000..ac69404 --- /dev/null +++ b/implement-shell-tools/wc/wc.py @@ -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 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])) From cd757bb2bd4e166df613eb54892a70a47f0089c3 Mon Sep 17 00:00:00 2001 From: nadika Date: Thu, 10 Apr 2025 18:28:50 +0100 Subject: [PATCH 2/4] Implement cat command with -n, -b flags --- implement-shell-tools/cat/cat.py | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 implement-shell-tools/cat/cat.py diff --git a/implement-shell-tools/cat/cat.py b/implement-shell-tools/cat/cat.py new file mode 100644 index 0000000..39d8b38 --- /dev/null +++ b/implement-shell-tools/cat/cat.py @@ -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) + + + + + From f246d1a7ad4fc74d2d7bfa43f5c4c82daae2b888 Mon Sep 17 00:00:00 2001 From: nadika Date: Fri, 11 Apr 2025 11:00:40 +0100 Subject: [PATCH 3/4] Implement ls command with -1, -a flags --- implement-shell-tools/ls/ls.py | 83 ++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 implement-shell-tools/ls/ls.py diff --git a/implement-shell-tools/ls/ls.py b/implement-shell-tools/ls/ls.py new file mode 100644 index 0000000..f126f89 --- /dev/null +++ b/implement-shell-tools/ls/ls.py @@ -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) \ No newline at end of file From b2e222450b74c47afc5126c6eff43eb79bcece61 Mon Sep 17 00:00:00 2001 From: nadika Date: Fri, 11 Apr 2025 11:01:50 +0100 Subject: [PATCH 4/4] Refactor --- implement-shell-tools/wc/wc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implement-shell-tools/wc/wc.py b/implement-shell-tools/wc/wc.py index ac69404..bb3c097 100644 --- a/implement-shell-tools/wc/wc.py +++ b/implement-shell-tools/wc/wc.py @@ -67,7 +67,7 @@ def wc_counts(filepath): # 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 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)