| 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): Flags for customizing the command's behavior. |
| 8 | files (list[str]): One or more files to search in. |
| 9 | |
| 10 | Returns: |
| 11 | str: All matching lines, possibly with file names and line numbers prepended. |
| 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 - handle both space-separated and concatenated flags |
| 21 | # Remove spaces and split into individual characters |
| 22 | clean_flags = flags.replace(' ', '') |
| 23 | line_numbers = 'n' in clean_flags |
| 24 | files_only = 'l' in clean_flags |
| 25 | case_insensitive = 'i' in clean_flags |
| 26 | invert_match = 'v' in clean_flags |
| 27 | exact_match = 'x' in clean_flags |
| 28 | |
| 29 | # Edge Case: Empty pattern |
| 30 | if not pattern: |
| 31 | pattern_to_match = '' |
| 32 | else: |
| 33 | pattern_to_match = pattern |
| 34 | |
| 35 | result_lines = [] |
| 36 | matching_files = set() |
| 37 | |
| 38 | # Edge Case: No files provided |
| 39 | if not files: |
| 40 | return "" |
| 41 | |
| 42 | # Edge Case: Empty files list |
| 43 | if len(files) == 0: |
| 44 | return "" |
| 45 | |
| 46 | for file_path in files: |
| 47 | try: |
| 48 | with open(file_path, 'r', encoding='utf-8') as file: |
| 49 | lines = file.readlines() |
| 50 | |
| 51 | # Edge Case: File cannot be read |
| 52 | except IOError: |
| 53 | # Skip unreadable files |
| 54 | continue |
| 55 | |
| 56 | file_has_match = False |
| 57 | |
| 58 | # If -l flag is set, we only need to check if there's a match, not process lines |
| 59 | if files_only: |
| 60 | for line_num, line in enumerate(lines, start=1): |
| 61 | # Determine if line matches |
| 62 | line_content = line.rstrip('\n') |
| 63 | if exact_match: |
| 64 | # For exact match, compare the entire line |
| 65 | if case_insensitive: |
| 66 | matches = pattern_to_match.lower() == line_content.lower() |
| 67 | else: |
| 68 | matches = pattern_to_match == line_content |
| 69 | else: |
| 70 | # For substring match, check if pattern is contained in line |
| 71 | if case_insensitive: |
| 72 | matches = pattern_to_match.lower() in line_content.lower() |
| 73 | else: |
| 74 | matches = pattern_to_match in line_content |
| 75 | |
| 76 | # Apply invert flag |
| 77 | if invert_match: |
| 78 | matches = not matches |
| 79 | |
| 80 | if matches: |
| 81 | matching_files.add(file_path) |
| 82 | break # Found a match, no need to check more lines |
| 83 | else: |
| 84 | for line_num, line in enumerate(lines, start=1): |
| 85 | # Determine if line matches |
| 86 | line_content = line.rstrip('\n') |
| 87 | if exact_match: |
| 88 | # For exact match, compare the entire line |
| 89 | if case_insensitive: |
| 90 | matches = pattern_to_match.lower() == line_content.lower() |
| 91 | else: |
| 92 | matches = pattern_to_match == line_content |
| 93 | else: |
| 94 | # For substring match, check if pattern is contained in line |
| 95 | if case_insensitive: |
| 96 | matches = pattern_to_match.lower() in line_content.lower() |
| 97 | else: |
| 98 | matches = pattern_to_match in line_content |
| 99 | |
| 100 | # Apply invert flag |
| 101 | if invert_match: |
| 102 | matches = not matches |
| 103 | |
| 104 | if matches: |
| 105 | file_has_match = True |
| 106 | |
| 107 | output_line = "" |
| 108 | |
| 109 | # Add file name if multiple files |
| 110 | # Edge Case: Single file vs multiple files |
| 111 | if len(files) > 1: |
| 112 | output_line += f"{file_path}:" |
| 113 | |
| 114 | # Add line number if requested |
| 115 | # Edge Case: Line number flag |
| 116 | if line_numbers: |
| 117 | output_line += f"{line_num}:" |
| 118 | |
| 119 | output_line += line.rstrip('\n') |
| 120 | result_lines.append(output_line) |
| 121 | |
| 122 | |
| 123 | # Edge Case: Only file names requested |
| 124 | if files_only: |
| 125 | # Return file names in the order they were encountered |
| 126 | ordered_matching_files = [f for f in files if f in matching_files] |
| 127 | return "\n".join(ordered_matching_files) |
| 128 | |
| 129 | return "\n".join(result_lines) |
| 130 | |
| 131 | # Handled Edge Cases: Empty pattern, No files provided, Empty files list, File cannot be read, Only file names requested, Single file vs multiple files, Line number flag, File has matches and only file names requested |