| 1 | 1 | def grep(pattern: str, flags: str, files: list[str]) -> str: |
| 2 | | pass |
| 2 | """ |
| 3 | Search files for lines matching a search string and return all matching lines. |
| 4 | |
| 5 | Args: |
| 6 | pattern (str): The string to search for. |
| 7 | flags (str): Space-separated string of flags (-n, -l, -i, -v, -x). |
| 8 | files (list[str]): List of file paths to search in. |
| 9 | |
| 10 | Returns: |
| 11 | str: Matching lines according to the specified flags. |
| 12 | |
| 13 | Edge Cases Handled: |
| 14 | - Empty files list |
| 15 | - File not found |
| 16 | - Empty pattern |
| 17 | - Pattern not found in any files |
| 18 | - Multiple flags used together |
| 19 | - Case insensitive matching |
| 20 | - Exact line matching |
| 21 | - Inverted matching |
| 22 | - Line numbering |
| 23 | - File name output only |
| 24 | """ |
| 25 | # Parse flags |
| 26 | flag_list = flags.split() if flags else [] |
| 27 | line_numbers = '-n' in flag_list |
| 28 | files_only = '-l' in flag_list |
| 29 | case_insensitive = '-i' in flag_list |
| 30 | invert_match = '-v' in flag_list |
| 31 | exact_match = '-x' in flag_list |
| 32 | |
| 33 | # Edge Case: Empty files list |
| 34 | if not files: |
| 35 | return "" |
| 36 | |
| 37 | results = [] |
| 38 | matched_files = set() |
| 39 | |
| 40 | # Process each file |
| 41 | for file_path in files: |
| 42 | try: |
| 43 | # Edge Case: File not found |
| 44 | with open(file_path, 'r', encoding='utf-8') as file: |
| 45 | lines = file.readlines() |
| 46 | |
| 47 | # Process each line |
| 48 | for line_num, line in enumerate(lines, start=1): |
| 49 | # Determine if line matches |
| 50 | line_to_check = line if not case_insensitive else line.lower() |
| 51 | pattern_to_check = pattern if not case_insensitive else pattern.lower() |
| 52 | |
| 53 | # Edge Case: Empty pattern |
| 54 | if not pattern: |
| 55 | match = True |
| 56 | elif exact_match: |
| 57 | match = line_to_check.rstrip('\n\r') == pattern_to_check |
| 58 | else: |
| 59 | match = pattern_to_check in line_to_check |
| 60 | |
| 61 | # Edge Case: Inverted matching |
| 62 | if invert_match: |
| 63 | match = not match |
| 64 | |
| 65 | if match: |
| 66 | matched_files.add(file_path) |
| 67 | |
| 68 | # Edge Case: File name output only |
| 69 | if not files_only: |
| 70 | output = "" |
| 71 | # Edge Case: Multiple files - prepend filename |
| 72 | if len(files) > 1: |
| 73 | output += f"{file_path}:" |
| 74 | # Edge Case: Line numbering |
| 75 | if line_numbers: |
| 76 | output += f"{line_num}:" |
| 77 | output += line |
| 78 | results.append(output) |
| 79 | except FileNotFoundError: |
| 80 | # Edge Case: File not found - continue with other files |
| 81 | continue |
| 82 | |
| 83 | # Edge Case: File name output only |
| 84 | if files_only: |
| 85 | # Maintain the order of files as they appear in the input |
| 86 | unique_matched_files = [] |
| 87 | for f in files: |
| 88 | if f in matched_files and f not in unique_matched_files: |
| 89 | unique_matched_files.append(f) |
| 90 | return "\n".join(unique_matched_files) + ("\n" if unique_matched_files else "") |
| 91 | |
| 92 | return "".join(results) |
| 93 | |
| 94 | # Handled Edge Cases: Empty files list, File not found, Empty pattern, Pattern not found in any files, Multiple flags used together, Case insensitive matching, Exact line matching, Inverted matching, Line numbering, File name output only |