| 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): Space-separated flags for customizing behavior. |
| 8 | files (list[str]): List of file paths to search in. |
| 9 | |
| 10 | Returns: |
| 11 | str: Matching lines, possibly with file names and line numbers. |
| 12 | |
| 13 | Flags: |
| 14 | -n: Prepend line numbers. |
| 15 | -l: Output only file names. |
| 16 | -i: Case-insensitive matching. |
| 17 | -v: Invert matching. |
| 18 | -x: Match entire line only. |
| 19 | """ |
| 20 | # Parse flags |
| 21 | flag_list = flags.split() if flags else [] |
| 22 | line_numbers = '-n' in flag_list |
| 23 | filenames_only = '-l' in flag_list |
| 24 | case_insensitive = '-i' in flag_list |
| 25 | invert_match = '-v' in flag_list |
| 26 | exact_match = '-x' in flag_list |
| 27 | |
| 28 | # Edge Case: No files provided |
| 29 | if not files: |
| 30 | return "" |
| 31 | |
| 32 | # Edge Case: Pattern is empty |
| 33 | if not pattern: |
| 34 | # An empty pattern would match all lines, but we follow standard behavior |
| 35 | # where empty pattern matches empty strings in lines |
| 36 | pass |
| 37 | |
| 38 | results = [] |
| 39 | matched_files = set() |
| 40 | |
| 41 | # Edge Case: File not found or inaccessible |
| 42 | for file_path in files: |
| 43 | try: |
| 44 | with open(file_path, 'r', encoding='utf-8') as f: |
| 45 | lines = f.readlines() |
| 46 | except FileNotFoundError: |
| 47 | # Silently skip missing files as per typical grep behavior |
| 48 | continue |
| 49 | except IOError: |
| 50 | # Silently skip files that can't be read |
| 51 | continue |
| 52 | |
| 53 | file_has_match = False |
| 54 | |
| 55 | # Edge Case: File is empty |
| 56 | if not lines: |
| 57 | # Empty files have no matching lines, so nothing to do |
| 58 | pass |
| 59 | |
| 60 | for line_num, line in enumerate(lines, start=1): |
| 61 | # Remove trailing newline for matching |
| 62 | line_content = line.rstrip('\n') |
| 63 | |
| 64 | # Determine if line matches |
| 65 | if case_insensitive: |
| 66 | match = pattern.lower() in line_content.lower() |
| 67 | exact = pattern.lower() == line_content.lower() |
| 68 | else: |
| 69 | match = pattern in line_content |
| 70 | exact = pattern == line_content |
| 71 | |
| 72 | # Edge Case: Exact match flag requires full line match |
| 73 | if exact_match: |
| 74 | is_match = exact |
| 75 | else: |
| 76 | is_match = match |
| 77 | |
| 78 | # Edge Case: Invert flag reverses match result |
| 79 | if invert_match: |
| 80 | is_match = not is_match |
| 81 | |
| 82 | if is_match: |
| 83 | file_has_match = True |
| 84 | |
| 85 | # Edge Case: Filenames only flag collects only file names |
| 86 | if filenames_only: |
| 87 | matched_files.add(file_path) |
| 88 | else: |
| 89 | output_line = "" |
| 90 | |
| 91 | # Edge Case: Multiple files require filename prefix |
| 92 | if len(files) > 1: |
| 93 | output_line += f"{file_path}:" |
| 94 | |
| 95 | # Edge Case: Line numbers flag adds line number |
| 96 | if line_numbers: |
| 97 | output_line += f"{line_num}:" |
| 98 | |
| 99 | output_line += line |
| 100 | results.append(output_line) |
| 101 | |
| 102 | # Edge Case: Filenames only flag with single matching file |
| 103 | if filenames_only and file_has_match: |
| 104 | matched_files.add(file_path) |
| 105 | |
| 106 | # Edge Case: Filenames only flag returns sorted file names |
| 107 | if filenames_only: |
| 108 | return "\n".join(sorted(matched_files)) + ("\n" if matched_files else "") |
| 109 | |
| 110 | # Edge Case: No matches found returns empty string |
| 111 | return "".join(results) |
| 112 | |
| 113 | # Handled Edge Cases: No files provided, Pattern is empty, File not found or inaccessible, File is empty, Exact match flag requires full line match, Invert flag reverses match result, Filenames only flag collects only file names, Multiple files require filename prefix, Line numbers flag adds line number, Filenames only flag with single matching file, Filenames only flag returns sorted file names, No matches found returns empty string |