Add Functionalities
You need to support these functionalities as shown in the project objectives section:
-c, --count
-i, --ignore-case
-n, --line-number
-r, --recursive
-v, --invert-match
grepy_cli.py:
import argparse
from grepy.grep import grep, grep_recursive, grep_count
def main():
parser = argparse.ArgumentParser(description='''A grep-like command-line utility from LiteRank,
see https://literank.com/project/9/intro''')
parser.add_argument('pattern', type=str, help='The pattern to search for')
parser.add_argument('file_path', type=str, help='The path to the file to search in')
# Optional arguments
parser.add_argument('-c', '--count', action='store_true', help='Only a count of selected lines is written to standard output.')
parser.add_argument('-i', '--ignore-case', action='store_true', help='Perform case insensitive matching. By default, it is case sensitive.')
parser.add_argument('-n', '--line-number', action='store_true', help='Each output line is preceded by its relative line number in the file, starting at line 1. This option is ignored if -c is specified.')
parser.add_argument('-r', '--recursive', action='store_true', help='Recursively search subdirectories listed.')
parser.add_argument('-v', '--invert-match', action='store_true', help='Selected lines are those not matching any of the specified patterns.')
args = parser.parse_args()
if args.recursive:
result = grep_recursive(args.pattern, args.file_path, get_options(args))
else:
result = grep(args.pattern, args.file_path, get_options(args))
if args.count:
print(grep_count(result))
else:
print_result(result, args.line_number)
def get_options(args):
options = []
if args.ignore_case:
options.append('i')
if args.invert_match:
options.append('v')
return options
def print_result(result, line_number_option):
current_file = None
file_count = len(result)
for file_path, lines in result.items():
for (line_number, line) in lines:
if file_count > 1 and file_path != current_file:
current_file = file_path
print(f"\n{file_path}:")
if line_number_option:
print(f"{line_number}: {line}")
else:
print(line)
if __name__ == '__main__':
main()
argparse
adds all the optional arguments into the app. Function print_result
parses the result dictionary and prints the file_path
and line_number
if it's needed.
grepy/grep.py:
import re
import os
def _filter_lines(pattern, lines, flag):
return [(line_number, line.strip()) for line_number, line in enumerate(lines, start=1) if bool(re.search(pattern, line)) == flag]
def grep(pattern, file_path, options=None):
with open(file_path, 'r') as file:
try:
lines = file.readlines()
except UnicodeDecodeError: # filter out binary files
return {file_path: []}
if options:
if 'i' in options:
pattern = re.compile(pattern, re.IGNORECASE)
if 'v' in options:
matching_lines = _filter_lines(pattern, lines, False)
else:
matching_lines = _filter_lines(pattern, lines, True)
else:
matching_lines = _filter_lines(pattern, lines, True)
return {file_path: matching_lines}
def grep_count(result):
return sum([len(v) for v in result.values()])
def grep_recursive(pattern, directory_path, options=None):
results = {}
for root, _, files in os.walk(directory_path):
for file in files:
file_path = os.path.join(root, file)
results.update(grep(pattern, file_path, options))
return results
Function grep
adds logic for "case-insensitive match" and "invert match."
Function grep_recursive
lists all files recursively, grep
s them, and puts the results into the big dictionary.
Run the normal way:
python3 -m grepy_cli result grepy_cli.py
Result lines:
result = grep_recursive(args.pattern, args.file_path, get_options(args))
result = grep(args.pattern, args.file_path, get_options(args))
print(grep_count(result))
print_result(result, args.line_number)
def print_result(result, line_number_option):
file_count = len(result)
for file_path, lines in result.items():
Do the count:
python3 -m grepy_cli -c result grepy_cli.py
Result number is: 7.
Show the line numbers:
python3 -m grepy_cli -n result grepy_cli.py
Result lines:
21: result = grep_recursive(args.pattern, args.file_path, get_options(args))
23: result = grep(args.pattern, args.file_path, get_options(args))
26: print(grep_count(result))
28: print_result(result, args.line_number)
38: def print_result(result, line_number_option):
40: file_count = len(result)
41: for file_path, lines in result.items():
Use regex pattern
python3 -m grepy_cli -n "\br[a-z]+t" grepy_cli.py
Result lines:
15: parser.add_argument('-n', '--line-number', action='store_true', help='Each output line is preceded by its relative line number in the file, starting at line 1. This option is ignored if -c is specified.')
22: result = grep_recursive(args.pattern, args.file_path, get_options(args))
24: result = grep(args.pattern, args.file_path, get_options(args))
27: print(grep_count(result))
29: print_result(result, args.line_number)
37: return options
39: def print_result(result: Dict[str, MatchResults], line_number_option: bool):
41: file_count = len(result)
42: for file_path, lines in result.items():
Do the invert match:
python3 -m grepy_cli -vn result grepy_cli.py
Result is something like this:
1: import argparse
2:
3: from grepy.grep import grep, grep_recursive, grep_count
4:
5: def main():
...
8: parser.add_argument('pattern', type=str, help='The pattern to search for')
9: parser.add_argument('file_path', type=str, help='The path to the file to search in')
...
50:
51: if __name__ == '__main__':
52: main()
Do the case insensitive match:
python3 -m grepy_cli -i only grepy_cli.py
Result is:
parser.add_argument('-c', '--count', action='store_true', help='Only a count of selected lines is written to standard output.')
Do the big recursive match:
python3 -m grepy_cli -r count .
Result lines should be like:
./grepy_cli.py:
from grepy.grep import grep, grep_recursive, grep_count
parser.add_argument('-c', '--count', action='store_true', help='Only a count of selected lines is written to standard output.')
if args.count:
print(grep_count(result))
file_count = len(result)
if file_count > 1 and file_path != current_file:
./grepy/grep.py:
def grep_count(result):