| 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 opened |
| 21 | lines = file.readlines() |
| 22 | except FileNotFoundError: |
| 23 | # Edge Case: Handle file not found |
| 24 | continue |
| 25 | except Exception: |
| 26 | # Edge Case: Handle other file reading errors |
| 27 | continue |
| 28 | |
| 29 | # Process each line in the file |
| 30 | for line_num, line in enumerate(lines, 1): |
| 31 | # Prepare line for comparison |
| 32 | compare_line = line.lower() if case_insensitive else line |
| 33 | |
| 34 | # Determine if line matches |
| 35 | if exact_match: |
| 36 | # Edge Case: Exact match flag requires the entire line to match |
| 37 | matches = search_pattern == compare_line.rstrip('\n') |
| 38 | else: |
| 39 | # Edge Case: Normal substring search |
| 40 | matches = search_pattern in compare_line |
| 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 line matches, collect results |
| 48 | if matches: |
| 49 | # Edge Case: When -l flag is used, we only need to know which files match |
| 50 | if files_only: |
| 51 | matched_files.add(filename) |
| 52 | # Optimization: Break early since we only need to know the file matches |
| 53 | break |
| 54 | else: |
| 55 | # Build output line |
| 56 | output_line = "" |
| 57 | # Add filename prefix if multiple files |
| 58 | if len(files) > 1: |
| 59 | output_line += f"{filename}:" |
| 60 | # Add line number if requested |
| 61 | if line_numbers: |
| 62 | output_line += f"{line_num}:" |
| 63 | # Add the actual line |
| 64 | output_line += line |
| 65 | results.append(output_line) |
| 66 | |
| 67 | # Edge Case: Handle -l flag output (only filenames) |
| 68 | if files_only: |
| 69 | # Return filenames in the order they were encountered |
| 70 | ordered_matches = [f for f in files if f in matched_files] |
| 71 | return "\n".join(ordered_matches) + ("\n" if ordered_matches else "") |
| 72 | |
| 73 | # Edge Case: Handle normal output with proper line endings |
| 74 | return "".join(results) |
| 75 | |
| 76 | # Handled Edge Cases: Empty files, files that cannot be opened, file not found, other file reading errors, exact match flag, normal substring search, invert match flag, files only flag, multiple files with filename prefix, line numbers flag, case insensitive flag, proper line endings in output |