Pure-Python fallback when ripgrep is not available.
(
pattern: str,
base_path: Path,
*,
glob_pattern: str | None,
type_name: str | None,
output_mode: str,
case_insensitive: bool,
multiline: bool,
show_line_numbers: bool,
context_before: int,
context_after: int,
cwd: Path,
)
| 89 | |
| 90 | |
| 91 | def _grep_fallback_python( |
| 92 | pattern: str, |
| 93 | base_path: Path, |
| 94 | *, |
| 95 | glob_pattern: str | None, |
| 96 | type_name: str | None, |
| 97 | output_mode: str, |
| 98 | case_insensitive: bool, |
| 99 | multiline: bool, |
| 100 | show_line_numbers: bool, |
| 101 | context_before: int, |
| 102 | context_after: int, |
| 103 | cwd: Path, |
| 104 | ) -> dict[str, Any]: |
| 105 | """Pure-Python fallback when ripgrep is not available.""" |
| 106 | flags = re.MULTILINE |
| 107 | if case_insensitive: |
| 108 | flags |= re.IGNORECASE |
| 109 | if multiline: |
| 110 | flags |= re.DOTALL |
| 111 | try: |
| 112 | regex = re.compile(pattern, flags) |
| 113 | except re.error as e: |
| 114 | raise ToolInputError(f"invalid regex: {e}") from e |
| 115 | |
| 116 | files_to_search: list[Path] = [] |
| 117 | if base_path.is_file(): |
| 118 | files_to_search = [base_path] |
| 119 | else: |
| 120 | files_to_search = [p for p in _iter_files(base_path) if p.is_file()] |
| 121 | |
| 122 | if glob_pattern: |
| 123 | patterns = _split_glob_patterns(glob_pattern) |
| 124 | files_to_search = [ |
| 125 | p for p in files_to_search |
| 126 | if any(_matches_glob(p, pat) for pat in patterns) |
| 127 | ] |
| 128 | if type_name: |
| 129 | files_to_search = [p for p in files_to_search if _matches_type(p, type_name)] |
| 130 | |
| 131 | matched_files: list[Path] = [] |
| 132 | content_lines: list[str] = [] |
| 133 | total_matches = 0 |
| 134 | |
| 135 | for file in files_to_search: |
| 136 | try: |
| 137 | text = file.read_text(encoding="utf-8", errors="replace") |
| 138 | except Exception: |
| 139 | continue |
| 140 | if regex.search(text) is None: |
| 141 | continue |
| 142 | matched_files.append(file) |
| 143 | |
| 144 | if output_mode == "content": |
| 145 | lines = text.splitlines() |
| 146 | match_indices: list[int] = [] |
| 147 | for i, line in enumerate(lines): |
| 148 | if regex.search(line) is not None: |
no test coverage detected