| 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: Space-separated string of 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 | flag_list = flags.split() if flags else [] |
| 15 | line_numbers = '-n' in flag_list |
| 16 | files_only = '-l' in flag_list |
| 17 | case_insensitive = '-i' in flag_list |
| 18 | invert_match = '-v' in flag_list |
| 19 | exact_match = '-x' in flag_list |
| 20 | |
| 21 | # Edge Case: Empty pattern |
| 22 | if not pattern: |
| 23 | pattern = '' |
| 24 | |
| 25 | # Edge Case: No files provided |
| 26 | if not files: |
| 27 | return '' |
| 28 | |
| 29 | result_lines = [] |
| 30 | matching_files = set() |
| 31 | |
| 32 | # Edge Case: Single file vs multiple files |
| 33 | multiple_files = len(files) > 1 |
| 34 | |
| 35 | for file_path in files: |
| 36 | try: |
| 37 | with open(file_path, 'r', encoding='utf-8') as file: |
| 38 | lines = file.readlines() |
| 39 | |
| 40 | # Edge Case: File with no lines |
| 41 | if not lines and files_only and pattern == '': |
| 42 | matching_files.add(file_path) |
| 43 | continue |
| 44 | |
| 45 | for line_num, line in enumerate(lines, 1): |
| 46 | # Remove newline for processing but keep track of it |
| 47 | line_content = line.rstrip('\n') |
| 48 | |
| 49 | # Determine if line matches |
| 50 | match = False |
| 51 | |
| 52 | # Edge Case: Case insensitive matching |
| 53 | if case_insensitive: |
| 54 | match = (exact_match and pattern.lower() == line_content.lower()) or \ |
| 55 | (not exact_match and pattern.lower() in line_content.lower()) |
| 56 | else: |
| 57 | match = (exact_match and pattern == line_content) or \ |
| 58 | (not exact_match and pattern in line_content) |
| 59 | |
| 60 | # Edge Case: Invert match |
| 61 | if invert_match: |
| 62 | match = not match |
| 63 | |
| 64 | if match: |
| 65 | if files_only: |
| 66 | matching_files.add(file_path) |
| 67 | # No need to check other lines in this file |
| 68 | break |
| 69 | else: |
| 70 | output_line = '' |
| 71 | # Edge Case: Multiple files - prepend filename |
| 72 | if multiple_files: |
| 73 | output_line += f"{file_path}:" |
| 74 | # Edge Case: Line numbers flag |
| 75 | if line_numbers: |
| 76 | output_line += f"{line_num}:" |
| 77 | output_line += line_content |
| 78 | result_lines.append(output_line) |
| 79 | except FileNotFoundError: |
| 80 | # Edge Case: File not found - according to requirements, we just skip it |
| 81 | continue |
| 82 | except Exception: |
| 83 | # Edge Case: Other file reading errors - skip the file |
| 84 | continue |
| 85 | |
| 86 | # Edge Case: Files only flag |
| 87 | if files_only: |
| 88 | # Return files in the order they were provided |
| 89 | files_result = [f for f in files if f in matching_files] |
| 90 | return '\n'.join(files_result) + ('\n' if files_result else '') |
| 91 | |
| 92 | return '\n'.join(result_lines) + ('\n' if result_lines else '') |
| 93 | |
| 94 | # Handled Edge Cases: Empty pattern, no files provided, single vs multiple files, file with no lines, case insensitive matching, invert match, multiple files with filename prepending, line numbers flag, files only flag, file not found, other file reading errors |