| 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: The string to search for |
| 7 | flags: String containing zero or more flags (-n, -l, -i, -v, -x) |
| 8 | files: List of file paths to search in |
| 9 | |
| 10 | Returns: |
| 11 | String containing all matching lines, formatted according to flags |
| 12 | """ |
| 13 | # Parse flags |
| 14 | line_numbers = '-n' in flags |
| 15 | files_only = '-l' in flags |
| 16 | case_insensitive = '-i' in flags |
| 17 | invert_match = '-v' in flags |
| 18 | exact_match = '-x' in flags |
| 19 | |
| 20 | # Edge Case: Empty pattern |
| 21 | if not pattern: |
| 22 | pattern_to_search = '' |
| 23 | else: |
| 24 | pattern_to_search = pattern.lower() if case_insensitive else pattern |
| 25 | |
| 26 | result_lines = [] |
| 27 | matching_files = set() |
| 28 | |
| 29 | # Edge Case: No files provided |
| 30 | if not files: |
| 31 | return "" |
| 32 | |
| 33 | multiple_files = len(files) > 1 |
| 34 | |
| 35 | for file_path in files: |
| 36 | try: |
| 37 | # Edge Case: File not found or cannot be read |
| 38 | with open(file_path, 'r', encoding='utf-8') as file: |
| 39 | lines = file.readlines() |
| 40 | except FileNotFoundError: |
| 41 | # Edge Case: File not found - skip the file |
| 42 | continue |
| 43 | except IOError: |
| 44 | # Edge Case: File cannot be read - skip the file |
| 45 | continue |
| 46 | |
| 47 | # Edge Case: Empty file |
| 48 | if not lines: |
| 49 | continue |
| 50 | |
| 51 | for line_num, line in enumerate(lines, 1): |
| 52 | # Prepare line for comparison |
| 53 | line_to_check = line.rstrip('\n') |
| 54 | if case_insensitive: |
| 55 | line_to_check = line_to_check.lower() |
| 56 | |
| 57 | # Check for match based on exact_match flag |
| 58 | if exact_match: |
| 59 | is_match = pattern_to_search == line_to_check |
| 60 | else: |
| 61 | is_match = pattern_to_search in line_to_check |
| 62 | |
| 63 | # Apply invert flag |
| 64 | if invert_match: |
| 65 | is_match = not is_match |
| 66 | |
| 67 | if is_match: |
| 68 | # Edge Case: Only file names requested |
| 69 | if files_only: |
| 70 | matching_files.add(file_path) |
| 71 | break # Found a match, no need to check more lines |
| 72 | else: |
| 73 | output_line = "" |
| 74 | # Edge Case: Multiple files - prepend filename |
| 75 | if multiple_files: |
| 76 | output_line += f"{file_path}:" |
| 77 | # Edge Case: Line numbers requested |
| 78 | if line_numbers: |
| 79 | output_line += f"{line_num}:" |
| 80 | output_line += line |
| 81 | result_lines.append(output_line) |
| 82 | |
| 83 | # Edge Case: Only file names requested |
| 84 | if files_only: |
| 85 | return "\n".join(sorted(list(matching_files))) + ("\n" if matching_files else "") |
| 86 | |
| 87 | # Edge Case: No matches found |
| 88 | if not result_lines: |
| 89 | return "" |
| 90 | |
| 91 | return "".join(result_lines) |
| 92 | |
| 93 | # Handled Edge Cases: Empty pattern, No files provided, File not found, File cannot be read, Empty file, Multiple files, Line numbers requested, Only file names requested, No matches found, Case insensitive matching, Invert matching, Exact line matching |