| 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 file 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 listing only |
| 24 | """ |
| 25 | # Parse flags - they can be combined like "-invx" or separated like "-n -i" |
| 26 | flag_list = [] |
| 27 | if flags: |
| 28 | # Check if flags are space-separated or combined |
| 29 | if ' ' in flags: |
| 30 | flag_list = flags.split() |
| 31 | else: |
| 32 | # Split individual flags from combined string like "-invx" |
| 33 | # Find all valid flag characters after dashes |
| 34 | i = 0 |
| 35 | while i < len(flags): |
| 36 | if flags[i] == '-': |
| 37 | i += 1 |
| 38 | # Process all consecutive valid flag characters |
| 39 | while i < len(flags) and flags[i] in 'lnivx': |
| 40 | flag_list.append('-' + flags[i]) |
| 41 | i += 1 |
| 42 | else: |
| 43 | i += 1 |
| 44 | line_numbers = '-n' in flag_list |
| 45 | list_files_only = '-l' in flag_list |
| 46 | case_insensitive = '-i' in flag_list |
| 47 | invert_match = '-v' in flag_list |
| 48 | exact_match = '-x' in flag_list |
| 49 | |
| 50 | # Edge Case: Empty file list |
| 51 | if not files: |
| 52 | return "" |
| 53 | |
| 54 | results = [] |
| 55 | matching_files = set() |
| 56 | |
| 57 | # Process each file |
| 58 | for file_path in files: |
| 59 | try: |
| 60 | # Edge Case: File not found |
| 61 | with open(file_path, 'r', encoding='utf-8') as file: |
| 62 | line_num = 0 |
| 63 | file_has_match = False |
| 64 | |
| 65 | for line in file: |
| 66 | line_num += 1 |
| 67 | |
| 68 | # Determine if line matches |
| 69 | line_to_check = line.rstrip('\n') |
| 70 | pattern_to_check = pattern |
| 71 | |
| 72 | # Edge Case: Case insensitive matching |
| 73 | if case_insensitive: |
| 74 | line_to_check = line_to_check.lower() |
| 75 | pattern_to_check = pattern_to_check.lower() |
| 76 | |
| 77 | # Edge Case: Exact line matching |
| 78 | if exact_match: |
| 79 | is_match = line_to_check == pattern_to_check |
| 80 | else: |
| 81 | is_match = pattern_to_check in line_to_check |
| 82 | |
| 83 | # Edge Case: Inverted matching |
| 84 | if invert_match: |
| 85 | is_match = not is_match |
| 86 | |
| 87 | if is_match: |
| 88 | file_has_match = True |
| 89 | |
| 90 | # Edge Case: File name listing only |
| 91 | if list_files_only: |
| 92 | matching_files.add(file_path) |
| 93 | break # No need to check more lines in this file |
| 94 | |
| 95 | # Build output line |
| 96 | output_line = "" |
| 97 | |
| 98 | # Edge Case: Multiple files - prepend filename |
| 99 | if len(files) > 1: |
| 100 | output_line += f"{file_path}:" |
| 101 | |
| 102 | # Edge Case: Line numbering |
| 103 | if line_numbers: |
| 104 | output_line += f"{line_num}:" |
| 105 | |
| 106 | output_line += line |
| 107 | results.append(output_line) |
| 108 | |
| 109 | # Edge Case: File name listing only (file has matches) |
| 110 | if list_files_only and file_has_match: |
| 111 | matching_files.add(file_path) |
| 112 | |
| 113 | # Edge Case: File not found |
| 114 | except FileNotFoundError: |
| 115 | # According to typical grep behavior, we silently continue |
| 116 | continue |
| 117 | |
| 118 | # Edge Case: File name listing only (return files in original order) |
| 119 | if list_files_only: |
| 120 | # Maintain original file order, only include files that had matches |
| 121 | ordered_files = [f for f in files if f in matching_files] |
| 122 | return "\n".join(ordered_files) + ("\n" if ordered_files else "") |
| 123 | |
| 124 | # Edge Case: No matches found |
| 125 | return "".join(results) |
| 126 | |
| 127 | # Handled Edge Cases: Empty file 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 listing only |