| 1 | 1 | def grep(pattern: str, flags: str, files: list[str]) -> str: |
| 2 | | pass |
| 2 | # Parse flags |
| 3 | line_numbers = 'n' in flags |
| 4 | files_only = 'l' in flags |
| 5 | case_insensitive = 'i' in flags |
| 6 | invert_match = 'v' in flags |
| 7 | exact_match = 'x' in flags |
| 8 | |
| 9 | # Prepare pattern for comparison |
| 10 | search_pattern = pattern.lower() if case_insensitive else pattern |
| 11 | |
| 12 | # Results storage |
| 13 | results = [] |
| 14 | matched_files = set() |
| 15 | |
| 16 | # Process each file |
| 17 | for filename in files: |
| 18 | try: |
| 19 | with open(filename, 'r', encoding='utf-8') as file: |
| 20 | # Edge Case: Handle files that cannot be read |
| 21 | lines = file.readlines() |
| 22 | except IOError: |
| 23 | # If we can't read the file, skip it |
| 24 | continue |
| 25 | |
| 26 | # Process each line |
| 27 | for line_num, line in enumerate(lines, 1): |
| 28 | # Prepare line for comparison |
| 29 | compare_line = line.lower() if case_insensitive else line |
| 30 | |
| 31 | # Determine if line matches |
| 32 | # Strip newline from line for comparison (both exact and substring) |
| 33 | line_content = compare_line.rstrip('\n') |
| 34 | |
| 35 | if exact_match: |
| 36 | # Edge Case: Exact match flag requires the entire line to match the pattern |
| 37 | matches = search_pattern == line_content |
| 38 | else: |
| 39 | # Edge Case: Normal substring search |
| 40 | matches = search_pattern in line_content |
| 41 | |
| 42 | # Apply invert flag |
| 43 | if invert_match: |
| 44 | # Edge Case: Invert match inverts the matching logic |
| 45 | matches = not matches |
| 46 | |
| 47 | # If we have a match |
| 48 | if matches: |
| 49 | # Edge Case: When only file names are requested, we just need to record the file |
| 50 | if files_only: |
| 51 | matched_files.add(filename) |
| 52 | # Don't break here, we need to continue processing for other functionality |
| 53 | else: |
| 54 | # Build the output line |
| 55 | output_line = "" |
| 56 | |
| 57 | # Add filename prefix if multiple files |
| 58 | # Edge Case: Filename prefix is added when searching multiple files |
| 59 | if len(files) > 1: |
| 60 | output_line += f"{filename}:" |
| 61 | |
| 62 | # Add line number if requested |
| 63 | # Edge Case: Line numbers are added when -n flag is used |
| 64 | if line_numbers: |
| 65 | output_line += f"{line_num}:" |
| 66 | |
| 67 | # Add the actual line content |
| 68 | output_line += line |
| 69 | |
| 70 | results.append(output_line) |
| 71 | |
| 72 | # Edge Case: When -l flag is used, return only filenames |
| 73 | if files_only: |
| 74 | # Return matched filenames in the order they appeared in the input |
| 75 | ordered_matches = [f for f in files if f in matched_files] |
| 76 | return "\n".join(ordered_matches) + ("\n" if ordered_matches else "") |
| 77 | |
| 78 | # Edge Case: Handle empty results |
| 79 | if not results: |
| 80 | return "" |
| 81 | |
| 82 | # Join all results with newlines |
| 83 | # Edge Case: Ensure proper newline termination |
| 84 | return "".join(results) |
| 85 | # Handled Edge Cases: Files that cannot be read, exact match flag, normal substring search, invert match flag, file names only request, filename prefix for multiple files, line numbers flag, empty results, proper newline termination |