| 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: No files provided |
| 22 | if not files: |
| 23 | return "" |
| 24 | |
| 25 | # Edge Case: Pattern is empty |
| 26 | if not pattern: |
| 27 | exact_match = False # Disable exact match if pattern is empty |
| 28 | |
| 29 | results = [] |
| 30 | matching_files = set() |
| 31 | |
| 32 | # Determine search pattern based on case sensitivity |
| 33 | search_pattern = pattern.lower() if case_insensitive else pattern |
| 34 | |
| 35 | # Process each file |
| 36 | for file_path in files: |
| 37 | # Edge Case: File cannot be opened or does not exist |
| 38 | try: |
| 39 | with open(file_path, 'r', encoding='utf-8') as file: |
| 40 | lines = file.readlines() |
| 41 | except (FileNotFoundError, IOError, UnicodeDecodeError): |
| 42 | # Skip files that cannot be read |
| 43 | continue |
| 44 | |
| 45 | file_has_match = False |
| 46 | |
| 47 | # Process each line in the file |
| 48 | for line_num, line in enumerate(lines, 1): |
| 49 | # Determine line content for comparison based on case sensitivity |
| 50 | compare_line = line.lower().rstrip('\n') if case_insensitive else line.rstrip('\n') |
| 51 | |
| 52 | # Determine if line matches based on search criteria |
| 53 | if exact_match: |
| 54 | line_matches = (compare_line == search_pattern) |
| 55 | else: |
| 56 | line_matches = (search_pattern in compare_line) |
| 57 | |
| 58 | # Apply invert flag |
| 59 | if invert_match: |
| 60 | line_matches = not line_matches |
| 61 | |
| 62 | # Edge Case: Empty pattern should match empty lines when not using exact match |
| 63 | if not pattern and not exact_match: |
| 64 | line_matches = True |
| 65 | |
| 66 | if line_matches: |
| 67 | file_has_match = True |
| 68 | |
| 69 | # If we only need file names, we can break early |
| 70 | if files_only: |
| 71 | matching_files.add(file_path) |
| 72 | break |
| 73 | |
| 74 | # Build output line |
| 75 | output_line = "" |
| 76 | |
| 77 | # Add filename prefix if multiple files |
| 78 | if len(files) > 1: |
| 79 | output_line += f"{file_path}:" |
| 80 | |
| 81 | # Add line number if requested |
| 82 | if line_numbers: |
| 83 | output_line += f"{line_num}:" |
| 84 | |
| 85 | # Add the actual line content |
| 86 | output_line += line |
| 87 | |
| 88 | results.append(output_line) |
| 89 | |
| 90 | # If we're only outputting filenames and this file had a match |
| 91 | if files_only and file_has_match: |
| 92 | matching_files.add(file_path) |
| 93 | |
| 94 | # Edge Case: No matches found |
| 95 | if files_only: |
| 96 | return "\n".join(sorted(matching_files)) + ("\n" if matching_files else "") |
| 97 | |
| 98 | # Edge Case: No results to return |
| 99 | if not results: |
| 100 | return "" |
| 101 | |
| 102 | return "".join(results) |
| 103 | |
| 104 | # Handled Edge Cases: No files provided, Pattern is empty, File cannot be opened or does not exist, Empty pattern should match empty lines when not using exact match, No matches found, No results to return |