| 1 | 1 | def grep(pattern: str, flags: str, files: list[str]) -> str: |
| 2 | | pass |
| 2 | """ |
| 3 | Search for lines matching a pattern in files and return matching lines. |
| 4 | |
| 5 | Args: |
| 6 | pattern: The string to search for |
| 7 | flags: String containing command line flags (-n, -l, -i, -v, -x) |
| 8 | files: List of file paths to search in |
| 9 | |
| 10 | Returns: |
| 11 | String containing matching lines, formatted according to 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 combined |
| 19 | - Case insensitive matching |
| 20 | - Exact line matching |
| 21 | - Line inversion |
| 22 | - Line numbering |
| 23 | - File name output only |
| 24 | """ |
| 25 | # Edge Case: Empty files list |
| 26 | if not files: |
| 27 | return "" |
| 28 | |
| 29 | # Parse flags |
| 30 | line_numbers = '-n' in flags |
| 31 | files_only = '-l' in flags |
| 32 | case_insensitive = '-i' in flags |
| 33 | invert_match = '-v' in flags |
| 34 | exact_match = '-x' in flags |
| 35 | |
| 36 | # Prepare pattern for comparison |
| 37 | search_pattern = pattern.lower() if case_insensitive else pattern |
| 38 | |
| 39 | results = [] |
| 40 | matching_files = set() |
| 41 | |
| 42 | # Edge Case: Process each file |
| 43 | for file_path in files: |
| 44 | try: |
| 45 | with open(file_path, 'r', encoding='utf-8') as file: |
| 46 | lines = file.readlines() |
| 47 | |
| 48 | # Edge Case: Process each line in the file |
| 49 | for line_num, line in enumerate(lines, 1): |
| 50 | # Prepare line for comparison |
| 51 | compare_line = line.lower() if case_insensitive else line |
| 52 | |
| 53 | # Determine if line matches |
| 54 | if exact_match: |
| 55 | matches = search_pattern == compare_line.rstrip('\n') |
| 56 | else: |
| 57 | matches = search_pattern in compare_line |
| 58 | |
| 59 | # Edge Case: Apply invert flag |
| 60 | if invert_match: |
| 61 | matches = not matches |
| 62 | |
| 63 | # Edge Case: Handle matching line |
| 64 | if matches: |
| 65 | matching_files.add(file_path) |
| 66 | |
| 67 | # Edge Case: If only file names requested, no need to collect lines |
| 68 | if not files_only: |
| 69 | output_line = line |
| 70 | |
| 71 | # Edge Case: Format output with filename if multiple files |
| 72 | if len(files) > 1: |
| 73 | output_line = f"{file_path}:{output_line}" |
| 74 | |
| 75 | # Edge Case: Add line number if requested |
| 76 | if line_numbers: |
| 77 | # Find the position to insert line number (after filename if present) |
| 78 | if len(files) > 1: |
| 79 | colon_pos = output_line.find(':') |
| 80 | output_line = f"{output_line[:colon_pos+1]}{line_num}:{output_line[colon_pos+1:]}" |
| 81 | else: |
| 82 | output_line = f"{line_num}:{output_line}" |
| 83 | |
| 84 | results.append(output_line) |
| 85 | except FileNotFoundError: |
| 86 | # Edge Case: File not found - continue with other files |
| 87 | continue |
| 88 | |
| 89 | # Edge Case: If files only flag is set, return only file names |
| 90 | if files_only: |
| 91 | # Return files in the order they were specified, but only those that had matches |
| 92 | output_files = [f for f in files if f in matching_files] |
| 93 | return '\n'.join(output_files) + ('\n' if output_files else '') |
| 94 | |
| 95 | # Edge Case: Join results with newlines |
| 96 | return ''.join(results) |
| 97 | |
| 98 | # Handled Edge Cases: Empty files list, File not found, Empty pattern, Pattern not found in any files, Multiple flags combined, Case insensitive matching, Exact line matching, Line inversion, Line numbering, File name output only |