| 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 that modify search 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 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 | flag_list = [] |
| 22 | if flags: |
| 23 | # Handle concatenated flags like "-ni" as well as space-separated |
| 24 | for flag in flags.split(): |
| 25 | if flag.startswith('-') and len(flag) > 1: |
| 26 | # Add each character as a separate flag (except the dash) |
| 27 | for char in flag[1:]: |
| 28 | flag_list.append('-' + char) |
| 29 | else: |
| 30 | flag_list.append(flag) |
| 31 | |
| 32 | line_numbers = '-n' in flag_list |
| 33 | files_only = '-l' in flag_list |
| 34 | case_insensitive = '-i' in flag_list |
| 35 | invert_match = '-v' in flag_list |
| 36 | exact_match = '-x' in flag_list |
| 37 | |
| 38 | # Prepare pattern for case-insensitive matching |
| 39 | search_pattern = pattern.lower() if case_insensitive else pattern |
| 40 | |
| 41 | # Results storage |
| 42 | results = [] |
| 43 | matching_files = set() |
| 44 | |
| 45 | # Process each file |
| 46 | for file_path in files: |
| 47 | try: |
| 48 | with open(file_path, 'r', encoding='utf-8') as file: |
| 49 | lines = file.readlines() |
| 50 | except FileNotFoundError: |
| 51 | # Edge Case: File not found - skip the file |
| 52 | continue |
| 53 | except Exception: |
| 54 | # Edge Case: Other file reading errors - skip the file |
| 55 | continue |
| 56 | |
| 57 | file_has_match = False |
| 58 | |
| 59 | # Process each line |
| 60 | for line_num, line in enumerate(lines, 1): |
| 61 | # Prepare line for comparison |
| 62 | compare_line = line.lower() if case_insensitive else line |
| 63 | |
| 64 | # Determine if line matches |
| 65 | if exact_match: |
| 66 | # Edge Case: Exact match flag - match entire line (excluding newline) |
| 67 | line_content = compare_line.rstrip('\n\r') |
| 68 | matches = (search_pattern == line_content) |
| 69 | else: |
| 70 | # Edge Case: Substring match - search pattern anywhere in line |
| 71 | matches = (search_pattern in compare_line) |
| 72 | |
| 73 | # Apply invert flag |
| 74 | if invert_match: |
| 75 | # Edge Case: Invert match flag - flip the match result |
| 76 | matches = not matches |
| 77 | |
| 78 | # Process match |
| 79 | if matches: |
| 80 | file_has_match = True |
| 81 | |
| 82 | if files_only: |
| 83 | # Edge Case: Files only flag - we only need to know which files match |
| 84 | matching_files.add(file_path) |
| 85 | break # No need to check more lines in this file |
| 86 | else: |
| 87 | # Build output line |
| 88 | output_line = "" |
| 89 | |
| 90 | # Add file name if multiple files |
| 91 | if len(files) > 1: |
| 92 | # Edge Case: Multiple files - prepend filename |
| 93 | output_line += f"{file_path}:" |
| 94 | |
| 95 | # Add line number if requested |
| 96 | if line_numbers: |
| 97 | # Edge Case: Line numbers flag - prepend line number |
| 98 | output_line += f"{line_num}:" |
| 99 | |
| 100 | # Add the actual line content |
| 101 | output_line += line |
| 102 | |
| 103 | results.append(output_line) |
| 104 | |
| 105 | # If files_only and this file had a match, add it |
| 106 | if files_only and file_has_match: |
| 107 | # Edge Case: Files only mode - collect matching file names |
| 108 | matching_files.add(file_path) |
| 109 | |
| 110 | # Return appropriate result based on flags |
| 111 | if files_only: |
| 112 | # Edge Case: Files only flag - return sorted file names |
| 113 | # When -l flag is used, return each filename only once, with trailing newlines |
| 114 | if matching_files: |
| 115 | return "\n".join(sorted(matching_files)) + "\n" |
| 116 | else: |
| 117 | return "" |
| 118 | else: |
| 119 | # Edge Case: Normal operation - return matching lines |
| 120 | # Ensure trailing newline for non-empty results, except when last line already has one |
| 121 | if results: |
| 122 | output = "".join(results) |
| 123 | # Add trailing newline if the output doesn't already end with one |
| 124 | if not output.endswith('\n'): |
| 125 | output += "\n" |
| 126 | return output |
| 127 | else: |
| 128 | return "" |
| 129 | |
| 130 | # Handled Edge Cases: Empty pattern, empty files list, file not found, other file reading errors, exact match flag, substring match, invert match flag, files only flag, multiple files, line numbers flag, case insensitive flag, files only mode, normal operation" |