| 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): Flags for customizing the command's behavior. |
| 8 | files (list[str]): One or more files to search in. |
| 9 | |
| 10 | Returns: |
| 11 | str: Matching lines, possibly with file names and line numbers prepended. |
| 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 number formatting |
| 23 | - File name formatting for multiple files |
| 24 | """ |
| 25 | # Parse flags |
| 26 | line_numbers = '-n' in flags |
| 27 | files_only = '-l' in flags |
| 28 | case_insensitive = '-i' in flags |
| 29 | invert_match = '-v' in flags |
| 30 | exact_match = '-x' in flags |
| 31 | |
| 32 | # Edge Case: Empty pattern |
| 33 | search_pattern = pattern.lower() if case_insensitive else pattern |
| 34 | |
| 35 | results = [] |
| 36 | matching_files = set() |
| 37 | |
| 38 | # Edge Case: Empty file list |
| 39 | if not files: |
| 40 | return "" |
| 41 | |
| 42 | multiple_files = len(files) > 1 |
| 43 | |
| 44 | for file_name in files: |
| 45 | try: |
| 46 | # Edge Case: File not found |
| 47 | with open(file_name, 'r') as file: |
| 48 | lines = file.readlines() |
| 49 | |
| 50 | for i, line in enumerate(lines, start=1): |
| 51 | # Determine if line matches |
| 52 | line_content = line.rstrip('\n') |
| 53 | compare_line = line_content.lower() if case_insensitive else line_content |
| 54 | |
| 55 | if exact_match: |
| 56 | match = (compare_line == search_pattern) |
| 57 | else: |
| 58 | match = (search_pattern in compare_line) |
| 59 | |
| 60 | # Edge Case: Inverted matching |
| 61 | if invert_match: |
| 62 | match = not match |
| 63 | |
| 64 | if match: |
| 65 | if files_only: |
| 66 | matching_files.add(file_name) |
| 67 | # No need to process more lines for this file if we only need file names |
| 68 | break |
| 69 | else: |
| 70 | output_line = line_content |
| 71 | |
| 72 | # Format output according to flags |
| 73 | if line_numbers: |
| 74 | output_line = f"{i}:{output_line}" |
| 75 | |
| 76 | if multiple_files: |
| 77 | output_line = f"{file_name}:{output_line}" |
| 78 | |
| 79 | results.append(output_line) |
| 80 | except FileNotFoundError: |
| 81 | # Edge Case: File not found - continue with other files |
| 82 | continue |
| 83 | |
| 84 | if files_only: |
| 85 | # Edge Case: No matching files |
| 86 | return '\n'.join(sorted(matching_files)) |
| 87 | else: |
| 88 | # Edge Case: No matching lines |
| 89 | return '\n'.join(results) |
| 90 | |
| 91 | # 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 number formatting, File name formatting for multiple files |