onetool-mcp 1.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bench/__init__.py +5 -0
- bench/cli.py +69 -0
- bench/harness/__init__.py +66 -0
- bench/harness/client.py +692 -0
- bench/harness/config.py +397 -0
- bench/harness/csv_writer.py +109 -0
- bench/harness/evaluate.py +512 -0
- bench/harness/metrics.py +283 -0
- bench/harness/runner.py +899 -0
- bench/py.typed +0 -0
- bench/reporter.py +629 -0
- bench/run.py +487 -0
- bench/secrets.py +101 -0
- bench/utils.py +16 -0
- onetool/__init__.py +4 -0
- onetool/cli.py +391 -0
- onetool/py.typed +0 -0
- onetool_mcp-1.0.0b1.dist-info/METADATA +163 -0
- onetool_mcp-1.0.0b1.dist-info/RECORD +132 -0
- onetool_mcp-1.0.0b1.dist-info/WHEEL +4 -0
- onetool_mcp-1.0.0b1.dist-info/entry_points.txt +3 -0
- onetool_mcp-1.0.0b1.dist-info/licenses/LICENSE.txt +687 -0
- onetool_mcp-1.0.0b1.dist-info/licenses/NOTICE.txt +64 -0
- ot/__init__.py +37 -0
- ot/__main__.py +6 -0
- ot/_cli.py +107 -0
- ot/_tui.py +53 -0
- ot/config/__init__.py +46 -0
- ot/config/defaults/bench.yaml +4 -0
- ot/config/defaults/diagram-templates/api-flow.mmd +33 -0
- ot/config/defaults/diagram-templates/c4-context.puml +30 -0
- ot/config/defaults/diagram-templates/class-diagram.mmd +87 -0
- ot/config/defaults/diagram-templates/feature-mindmap.mmd +70 -0
- ot/config/defaults/diagram-templates/microservices.d2 +81 -0
- ot/config/defaults/diagram-templates/project-gantt.mmd +37 -0
- ot/config/defaults/diagram-templates/state-machine.mmd +42 -0
- ot/config/defaults/onetool.yaml +25 -0
- ot/config/defaults/prompts.yaml +97 -0
- ot/config/defaults/servers.yaml +7 -0
- ot/config/defaults/snippets.yaml +4 -0
- ot/config/defaults/tool_templates/__init__.py +7 -0
- ot/config/defaults/tool_templates/extension.py +52 -0
- ot/config/defaults/tool_templates/isolated.py +61 -0
- ot/config/dynamic.py +121 -0
- ot/config/global_templates/__init__.py +2 -0
- ot/config/global_templates/bench-secrets-template.yaml +6 -0
- ot/config/global_templates/bench.yaml +9 -0
- ot/config/global_templates/onetool.yaml +27 -0
- ot/config/global_templates/secrets-template.yaml +44 -0
- ot/config/global_templates/servers.yaml +18 -0
- ot/config/global_templates/snippets.yaml +235 -0
- ot/config/loader.py +1087 -0
- ot/config/mcp.py +145 -0
- ot/config/secrets.py +190 -0
- ot/config/tool_config.py +125 -0
- ot/decorators.py +116 -0
- ot/executor/__init__.py +35 -0
- ot/executor/base.py +16 -0
- ot/executor/fence_processor.py +83 -0
- ot/executor/linter.py +142 -0
- ot/executor/pack_proxy.py +260 -0
- ot/executor/param_resolver.py +140 -0
- ot/executor/pep723.py +288 -0
- ot/executor/result_store.py +369 -0
- ot/executor/runner.py +496 -0
- ot/executor/simple.py +163 -0
- ot/executor/tool_loader.py +396 -0
- ot/executor/validator.py +398 -0
- ot/executor/worker_pool.py +388 -0
- ot/executor/worker_proxy.py +189 -0
- ot/http_client.py +145 -0
- ot/logging/__init__.py +37 -0
- ot/logging/config.py +315 -0
- ot/logging/entry.py +213 -0
- ot/logging/format.py +188 -0
- ot/logging/span.py +349 -0
- ot/meta.py +1555 -0
- ot/paths.py +453 -0
- ot/prompts.py +218 -0
- ot/proxy/__init__.py +21 -0
- ot/proxy/manager.py +396 -0
- ot/py.typed +0 -0
- ot/registry/__init__.py +189 -0
- ot/registry/models.py +57 -0
- ot/registry/parser.py +269 -0
- ot/registry/registry.py +413 -0
- ot/server.py +315 -0
- ot/shortcuts/__init__.py +15 -0
- ot/shortcuts/aliases.py +87 -0
- ot/shortcuts/snippets.py +258 -0
- ot/stats/__init__.py +35 -0
- ot/stats/html.py +250 -0
- ot/stats/jsonl_writer.py +283 -0
- ot/stats/reader.py +354 -0
- ot/stats/timing.py +57 -0
- ot/support.py +63 -0
- ot/tools.py +114 -0
- ot/utils/__init__.py +81 -0
- ot/utils/batch.py +161 -0
- ot/utils/cache.py +120 -0
- ot/utils/deps.py +403 -0
- ot/utils/exceptions.py +23 -0
- ot/utils/factory.py +179 -0
- ot/utils/format.py +65 -0
- ot/utils/http.py +202 -0
- ot/utils/platform.py +45 -0
- ot/utils/sanitize.py +130 -0
- ot/utils/truncate.py +69 -0
- ot_tools/__init__.py +4 -0
- ot_tools/_convert/__init__.py +12 -0
- ot_tools/_convert/excel.py +279 -0
- ot_tools/_convert/pdf.py +254 -0
- ot_tools/_convert/powerpoint.py +268 -0
- ot_tools/_convert/utils.py +358 -0
- ot_tools/_convert/word.py +283 -0
- ot_tools/brave_search.py +604 -0
- ot_tools/code_search.py +736 -0
- ot_tools/context7.py +495 -0
- ot_tools/convert.py +614 -0
- ot_tools/db.py +415 -0
- ot_tools/diagram.py +1604 -0
- ot_tools/diagram.yaml +167 -0
- ot_tools/excel.py +1372 -0
- ot_tools/file.py +1348 -0
- ot_tools/firecrawl.py +732 -0
- ot_tools/grounding_search.py +646 -0
- ot_tools/package.py +604 -0
- ot_tools/py.typed +0 -0
- ot_tools/ripgrep.py +544 -0
- ot_tools/scaffold.py +471 -0
- ot_tools/transform.py +213 -0
- ot_tools/web_fetch.py +384 -0
ot_tools/ripgrep.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"""Ripgrep text search tools.
|
|
2
|
+
|
|
3
|
+
Provides fast text and regex search in files using ripgrep (rg).
|
|
4
|
+
Requires the `rg` binary in PATH (install with: brew install ripgrep).
|
|
5
|
+
|
|
6
|
+
Inspired by mcp-ripgrep (https://github.com/mcollina/mcp-ripgrep)
|
|
7
|
+
by Matteo Collina, licensed under MIT.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
# Pack for dot notation: ripgrep.search(), ripgrep.count(), etc.
|
|
13
|
+
pack = "ripgrep"
|
|
14
|
+
|
|
15
|
+
__all__ = ["count", "files", "search", "types"]
|
|
16
|
+
|
|
17
|
+
# Dependency declarations for CLI validation
|
|
18
|
+
__ot_requires__ = {
|
|
19
|
+
"cli": [("rg", "brew install ripgrep")],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
import contextlib
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
from typing import TYPE_CHECKING
|
|
26
|
+
|
|
27
|
+
from pydantic import BaseModel, Field
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
from ot.config import get_tool_config
|
|
33
|
+
from ot.logging import LogSpan
|
|
34
|
+
from ot.paths import resolve_cwd_path
|
|
35
|
+
from ot.utils.platform import get_install_hint
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Config(BaseModel):
|
|
39
|
+
"""Pack configuration - discovered by registry."""
|
|
40
|
+
|
|
41
|
+
timeout: float = Field(
|
|
42
|
+
default=60.0,
|
|
43
|
+
ge=1.0,
|
|
44
|
+
le=300.0,
|
|
45
|
+
description="Command timeout in seconds",
|
|
46
|
+
)
|
|
47
|
+
relative_paths: bool = Field(
|
|
48
|
+
default=True,
|
|
49
|
+
description="Output relative paths instead of absolute paths",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _resolve_path(path: str) -> Path:
|
|
54
|
+
"""Resolve a search path relative to project directory.
|
|
55
|
+
|
|
56
|
+
Uses SDK resolve_cwd_path() for consistent path resolution.
|
|
57
|
+
|
|
58
|
+
Path resolution follows project conventions:
|
|
59
|
+
- Relative paths: resolved relative to project directory (OT_CWD)
|
|
60
|
+
- Absolute paths: used as-is
|
|
61
|
+
- ~ paths: expanded to home directory
|
|
62
|
+
- Prefixed paths (CWD/, GLOBAL/, OT_DIR/): resolved to respective dirs
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
path: Path string (can contain ~ or prefixes)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Resolved absolute Path
|
|
69
|
+
"""
|
|
70
|
+
return resolve_cwd_path(path)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _to_relative_output(output: str, base_path: Path) -> str:
|
|
74
|
+
"""Convert absolute paths in ripgrep output to relative paths.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
output: Raw ripgrep output with absolute paths
|
|
78
|
+
base_path: Base path to make paths relative to
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Output with paths converted to relative
|
|
82
|
+
"""
|
|
83
|
+
cfg = get_tool_config("ripgrep", Config)
|
|
84
|
+
if not cfg.relative_paths:
|
|
85
|
+
return output
|
|
86
|
+
|
|
87
|
+
base_str = str(base_path)
|
|
88
|
+
lines = []
|
|
89
|
+
for line in output.split("\n"):
|
|
90
|
+
if line.startswith(base_str):
|
|
91
|
+
# Convert absolute path to relative
|
|
92
|
+
rel_line = line[len(base_str) :].lstrip("/\\")
|
|
93
|
+
lines.append(rel_line)
|
|
94
|
+
else:
|
|
95
|
+
lines.append(line)
|
|
96
|
+
return "\n".join(lines)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _check_rg_installed() -> str | None:
|
|
100
|
+
"""Check if ripgrep is installed.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
None if installed, error message if not.
|
|
104
|
+
"""
|
|
105
|
+
if shutil.which("rg") is None:
|
|
106
|
+
return f"Error: ripgrep (rg) is not installed. {get_install_hint('rg')}"
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _run_rg(
|
|
111
|
+
args: list[str], cwd: str | None = None, timeout: float | None = None
|
|
112
|
+
) -> tuple[bool, str]:
|
|
113
|
+
"""Run ripgrep with the given arguments.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
args: Command line arguments for rg
|
|
117
|
+
cwd: Working directory for the command
|
|
118
|
+
timeout: Command timeout in seconds (defaults to config)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Tuple of (success, output). If success is False, output contains error message.
|
|
122
|
+
"""
|
|
123
|
+
if timeout is None:
|
|
124
|
+
timeout = get_tool_config("ripgrep", Config).timeout
|
|
125
|
+
|
|
126
|
+
with LogSpan(span="ripgrep.exec", args=args[:3] if len(args) > 3 else args) as span:
|
|
127
|
+
try:
|
|
128
|
+
result = subprocess.run(
|
|
129
|
+
["rg", *args],
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True,
|
|
132
|
+
cwd=cwd,
|
|
133
|
+
timeout=timeout,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# rg returns 1 for no matches (not an error), 2 for actual errors
|
|
137
|
+
if result.returncode == 2:
|
|
138
|
+
error_msg = result.stderr.strip() or "Unknown ripgrep error"
|
|
139
|
+
span.add(returncode=2, error=error_msg)
|
|
140
|
+
# Improve error message for common patterns
|
|
141
|
+
if "regex parse error" in error_msg:
|
|
142
|
+
return False, f"Error: Invalid regex pattern\n{error_msg}"
|
|
143
|
+
return False, f"Error: {error_msg}"
|
|
144
|
+
|
|
145
|
+
span.add(returncode=result.returncode, outputLen=len(result.stdout))
|
|
146
|
+
return True, result.stdout
|
|
147
|
+
|
|
148
|
+
except subprocess.TimeoutExpired:
|
|
149
|
+
span.add(error="timeout")
|
|
150
|
+
return False, f"Error: Search timed out after {timeout} seconds"
|
|
151
|
+
except FileNotFoundError:
|
|
152
|
+
span.add(error="not_installed")
|
|
153
|
+
return (
|
|
154
|
+
False,
|
|
155
|
+
f"Error: ripgrep (rg) is not installed. {get_install_hint('rg')}",
|
|
156
|
+
)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
span.add(error=str(e))
|
|
159
|
+
return False, f"Error: {e}"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def search(
|
|
163
|
+
*,
|
|
164
|
+
pattern: str,
|
|
165
|
+
path: str = ".",
|
|
166
|
+
case_sensitive: bool = True,
|
|
167
|
+
fixed_strings: bool = False,
|
|
168
|
+
file_type: str | None = None,
|
|
169
|
+
glob: str | None = None,
|
|
170
|
+
context: int = 0,
|
|
171
|
+
before_context: int = 0,
|
|
172
|
+
after_context: int = 0,
|
|
173
|
+
max_per_file: int | None = None,
|
|
174
|
+
limit: int | None = None,
|
|
175
|
+
word_match: bool = False,
|
|
176
|
+
include_hidden: bool = False,
|
|
177
|
+
invert_match: bool = False,
|
|
178
|
+
multiline: bool = False,
|
|
179
|
+
only_matching: bool = False,
|
|
180
|
+
no_ignore: bool = False,
|
|
181
|
+
heading: bool = False,
|
|
182
|
+
) -> str:
|
|
183
|
+
"""Search files for patterns using ripgrep.
|
|
184
|
+
|
|
185
|
+
Performs fast text/regex search across files. Returns matching lines
|
|
186
|
+
with file paths and line numbers.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
pattern: The regex or literal pattern to search for
|
|
190
|
+
path: Directory or file to search in (default: current directory)
|
|
191
|
+
case_sensitive: Match case-sensitively (default: True)
|
|
192
|
+
fixed_strings: Treat pattern as literal string, not regex (default: False)
|
|
193
|
+
file_type: Search only files of this type (e.g., "py", "js", "ts")
|
|
194
|
+
glob: Search only files matching this glob pattern. Note: glob is applied
|
|
195
|
+
relative to `path`. Use `glob="**/*.py"` with `path="."` for absolute
|
|
196
|
+
patterns, or `glob="*.py"` with `path="src/"` for relative patterns.
|
|
197
|
+
context: Number of lines to show before and after each match
|
|
198
|
+
before_context: Number of lines to show before each match (overrides context)
|
|
199
|
+
after_context: Number of lines to show after each match (overrides context)
|
|
200
|
+
max_per_file: Maximum matches per file (passed to rg --max-count)
|
|
201
|
+
limit: Maximum total matching lines to return (post-processed)
|
|
202
|
+
word_match: Match whole words only (default: False)
|
|
203
|
+
include_hidden: Search hidden files and directories (default: False)
|
|
204
|
+
invert_match: Return lines NOT matching the pattern (default: False)
|
|
205
|
+
multiline: Match patterns spanning multiple lines (default: False)
|
|
206
|
+
only_matching: Show only the matched text, not the full line (default: False)
|
|
207
|
+
no_ignore: Don't respect .gitignore files (default: False)
|
|
208
|
+
heading: Group matches by file with headings (default: False)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Matching lines with file paths and line numbers, or error message.
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
# Basic search
|
|
215
|
+
ripgrep.search(pattern="TODO", path="src/")
|
|
216
|
+
|
|
217
|
+
# Case insensitive search in Python files
|
|
218
|
+
ripgrep.search(pattern="error", path=".", case_sensitive=False, file_type="py")
|
|
219
|
+
|
|
220
|
+
# Fixed string search (pattern with special chars)
|
|
221
|
+
ripgrep.search(pattern="[test]", path=".", fixed_strings=True)
|
|
222
|
+
|
|
223
|
+
# Search with context
|
|
224
|
+
ripgrep.search(pattern="def main", path=".", context=3, limit=5)
|
|
225
|
+
|
|
226
|
+
# Glob patterns are relative to path
|
|
227
|
+
ripgrep.search(pattern="TODO", glob="**/*.py", path=".") # All .py files
|
|
228
|
+
ripgrep.search(pattern="TODO", glob="*.py", path="src/") # Only src/*.py
|
|
229
|
+
|
|
230
|
+
# Find lines NOT containing a pattern
|
|
231
|
+
ripgrep.search(pattern="import", path=".", invert_match=True, file_type="py")
|
|
232
|
+
|
|
233
|
+
# Multiline patterns
|
|
234
|
+
ripgrep.search(pattern="def.*\\n.*return", path=".", multiline=True)
|
|
235
|
+
"""
|
|
236
|
+
with LogSpan(span="ripgrep.search", pattern=pattern, path=path) as s:
|
|
237
|
+
# Check rg is installed
|
|
238
|
+
error = _check_rg_installed()
|
|
239
|
+
if error:
|
|
240
|
+
s.add("error", "not_installed")
|
|
241
|
+
return error
|
|
242
|
+
|
|
243
|
+
# Resolve path relative to effective cwd
|
|
244
|
+
search_path = _resolve_path(path)
|
|
245
|
+
if not search_path.exists():
|
|
246
|
+
s.add("error", "invalid_path")
|
|
247
|
+
return f"Error: Path does not exist: {search_path}"
|
|
248
|
+
|
|
249
|
+
# Build arguments
|
|
250
|
+
args = ["--line-number", "--with-filename"]
|
|
251
|
+
|
|
252
|
+
if not case_sensitive:
|
|
253
|
+
args.append("--ignore-case")
|
|
254
|
+
|
|
255
|
+
if fixed_strings:
|
|
256
|
+
args.append("--fixed-strings")
|
|
257
|
+
|
|
258
|
+
if file_type:
|
|
259
|
+
args.extend(["--type", file_type])
|
|
260
|
+
|
|
261
|
+
if glob:
|
|
262
|
+
args.extend(["--glob", glob])
|
|
263
|
+
|
|
264
|
+
# Context options: specific before/after take precedence over general context
|
|
265
|
+
if before_context > 0:
|
|
266
|
+
args.extend(["-B", str(before_context)])
|
|
267
|
+
if after_context > 0:
|
|
268
|
+
args.extend(["-A", str(after_context)])
|
|
269
|
+
if context > 0 and before_context == 0 and after_context == 0:
|
|
270
|
+
args.extend(["--context", str(context)])
|
|
271
|
+
|
|
272
|
+
if max_per_file:
|
|
273
|
+
args.extend(["--max-count", str(max_per_file)])
|
|
274
|
+
|
|
275
|
+
if word_match:
|
|
276
|
+
args.append("--word-regexp")
|
|
277
|
+
|
|
278
|
+
if include_hidden:
|
|
279
|
+
args.append("--hidden")
|
|
280
|
+
|
|
281
|
+
if invert_match:
|
|
282
|
+
args.append("--invert-match")
|
|
283
|
+
|
|
284
|
+
if multiline:
|
|
285
|
+
args.append("--multiline")
|
|
286
|
+
|
|
287
|
+
if only_matching:
|
|
288
|
+
args.append("--only-matching")
|
|
289
|
+
|
|
290
|
+
if no_ignore:
|
|
291
|
+
args.append("--no-ignore")
|
|
292
|
+
|
|
293
|
+
if heading:
|
|
294
|
+
args.append("--heading")
|
|
295
|
+
|
|
296
|
+
args.extend([pattern, str(search_path)])
|
|
297
|
+
|
|
298
|
+
success, output = _run_rg(args)
|
|
299
|
+
|
|
300
|
+
if not success:
|
|
301
|
+
s.add("error", output)
|
|
302
|
+
return output
|
|
303
|
+
|
|
304
|
+
if not output.strip():
|
|
305
|
+
s.add("matchCount", 0)
|
|
306
|
+
return "No matches found"
|
|
307
|
+
|
|
308
|
+
# Convert to relative paths if configured
|
|
309
|
+
result = _to_relative_output(output.strip(), search_path)
|
|
310
|
+
|
|
311
|
+
# Apply total limit if specified (post-process)
|
|
312
|
+
lines = result.split("\n")
|
|
313
|
+
if limit and len(lines) > limit:
|
|
314
|
+
result = "\n".join(lines[:limit])
|
|
315
|
+
result += f"\n... ({len(lines) - limit} more matches truncated)"
|
|
316
|
+
|
|
317
|
+
# Count matches
|
|
318
|
+
match_count = min(len(lines), limit) if limit else len(lines)
|
|
319
|
+
s.add("matchCount", match_count)
|
|
320
|
+
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def count(
|
|
325
|
+
*,
|
|
326
|
+
pattern: str,
|
|
327
|
+
path: str = ".",
|
|
328
|
+
count_all: bool = False,
|
|
329
|
+
file_type: str | None = None,
|
|
330
|
+
glob: str | None = None,
|
|
331
|
+
include_hidden: bool = False,
|
|
332
|
+
no_ignore: bool = False,
|
|
333
|
+
) -> str:
|
|
334
|
+
"""Count pattern occurrences in files.
|
|
335
|
+
|
|
336
|
+
Returns file paths with match counts per file.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
pattern: The regex or literal pattern to count
|
|
340
|
+
path: Directory or file to search in (default: current directory)
|
|
341
|
+
count_all: Count all matches per line, not just matching lines (default: False)
|
|
342
|
+
file_type: Count only in files of this type (e.g., "py", "js")
|
|
343
|
+
glob: Count only in files matching this glob pattern. Note: glob is applied
|
|
344
|
+
relative to `path`.
|
|
345
|
+
include_hidden: Include hidden files and directories (default: False)
|
|
346
|
+
no_ignore: Don't respect .gitignore files (default: False)
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
File paths with match counts, or error message.
|
|
350
|
+
|
|
351
|
+
Example:
|
|
352
|
+
# Count TODOs in source files
|
|
353
|
+
ripgrep.count(pattern="TODO", path="src/")
|
|
354
|
+
|
|
355
|
+
# Count all imports (including multiple per line)
|
|
356
|
+
ripgrep.count(pattern="import", path=".", count_all=True, file_type="py")
|
|
357
|
+
|
|
358
|
+
# Count with glob patterns (relative to path)
|
|
359
|
+
ripgrep.count(pattern="TODO", glob="**/*.py", path=".")
|
|
360
|
+
ripgrep.count(pattern="import", glob="*.{js,ts}", path="src/")
|
|
361
|
+
"""
|
|
362
|
+
with LogSpan(span="ripgrep.count", pattern=pattern, path=path) as s:
|
|
363
|
+
# Check rg is installed
|
|
364
|
+
error = _check_rg_installed()
|
|
365
|
+
if error:
|
|
366
|
+
s.add("error", "not_installed")
|
|
367
|
+
return error
|
|
368
|
+
|
|
369
|
+
# Resolve path relative to effective cwd
|
|
370
|
+
search_path = _resolve_path(path)
|
|
371
|
+
if not search_path.exists():
|
|
372
|
+
s.add("error", "invalid_path")
|
|
373
|
+
return f"Error: Path does not exist: {search_path}"
|
|
374
|
+
|
|
375
|
+
# Build arguments
|
|
376
|
+
args = ["--count"]
|
|
377
|
+
|
|
378
|
+
if count_all:
|
|
379
|
+
args.append("--count-matches")
|
|
380
|
+
|
|
381
|
+
if file_type:
|
|
382
|
+
args.extend(["--type", file_type])
|
|
383
|
+
|
|
384
|
+
if glob:
|
|
385
|
+
args.extend(["--glob", glob])
|
|
386
|
+
|
|
387
|
+
if include_hidden:
|
|
388
|
+
args.append("--hidden")
|
|
389
|
+
|
|
390
|
+
if no_ignore:
|
|
391
|
+
args.append("--no-ignore")
|
|
392
|
+
|
|
393
|
+
args.extend([pattern, str(search_path)])
|
|
394
|
+
|
|
395
|
+
success, output = _run_rg(args)
|
|
396
|
+
|
|
397
|
+
if not success:
|
|
398
|
+
s.add("error", output)
|
|
399
|
+
return output
|
|
400
|
+
|
|
401
|
+
if not output.strip():
|
|
402
|
+
s.add("matchCount", 0)
|
|
403
|
+
return "No matches found"
|
|
404
|
+
|
|
405
|
+
# Convert to relative paths if configured
|
|
406
|
+
result = _to_relative_output(output.strip(), search_path)
|
|
407
|
+
|
|
408
|
+
# Sum total matches
|
|
409
|
+
total = 0
|
|
410
|
+
for line in result.split("\n"):
|
|
411
|
+
if ":" in line:
|
|
412
|
+
with contextlib.suppress(ValueError):
|
|
413
|
+
total += int(line.split(":")[-1])
|
|
414
|
+
|
|
415
|
+
s.add("totalCount", total)
|
|
416
|
+
s.add("fileCount", len(result.split("\n")))
|
|
417
|
+
|
|
418
|
+
return result
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def files(
|
|
422
|
+
*,
|
|
423
|
+
path: str = ".",
|
|
424
|
+
file_type: str | None = None,
|
|
425
|
+
glob: str | None = None,
|
|
426
|
+
include_hidden: bool = False,
|
|
427
|
+
no_ignore: bool = False,
|
|
428
|
+
sort: str | None = None,
|
|
429
|
+
) -> str:
|
|
430
|
+
"""List files that would be searched.
|
|
431
|
+
|
|
432
|
+
Returns a list of file paths that ripgrep would search based on filters.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
path: Directory to list files in (default: current directory)
|
|
436
|
+
file_type: List only files of this type (e.g., "py", "js")
|
|
437
|
+
glob: List only files matching this glob pattern. Note: glob is applied
|
|
438
|
+
relative to `path`.
|
|
439
|
+
include_hidden: Include hidden files and directories (default: False)
|
|
440
|
+
no_ignore: Don't respect .gitignore files (default: False)
|
|
441
|
+
sort: Sort files by: "path", "modified", "accessed", or "created"
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
List of file paths, one per line.
|
|
445
|
+
|
|
446
|
+
Example:
|
|
447
|
+
# List all Python files
|
|
448
|
+
ripgrep.files(path="src/", file_type="py")
|
|
449
|
+
|
|
450
|
+
# List markdown files
|
|
451
|
+
ripgrep.files(path=".", glob="*.md")
|
|
452
|
+
|
|
453
|
+
# Glob patterns are relative to path
|
|
454
|
+
ripgrep.files(glob="**/*.py", path=".") # All .py files from root
|
|
455
|
+
ripgrep.files(glob="test_*.py", path="tests/") # Test files in tests/
|
|
456
|
+
|
|
457
|
+
# List files sorted by modification time
|
|
458
|
+
ripgrep.files(path="src/", file_type="py", sort="modified")
|
|
459
|
+
|
|
460
|
+
# Include files normally ignored by .gitignore
|
|
461
|
+
ripgrep.files(path=".", no_ignore=True)
|
|
462
|
+
"""
|
|
463
|
+
with LogSpan(span="ripgrep.files", path=path) as s:
|
|
464
|
+
# Check rg is installed
|
|
465
|
+
error = _check_rg_installed()
|
|
466
|
+
if error:
|
|
467
|
+
s.add("error", "not_installed")
|
|
468
|
+
return error
|
|
469
|
+
|
|
470
|
+
# Resolve path relative to effective cwd
|
|
471
|
+
search_path = _resolve_path(path)
|
|
472
|
+
if not search_path.exists():
|
|
473
|
+
s.add("error", "invalid_path")
|
|
474
|
+
return f"Error: Path does not exist: {search_path}"
|
|
475
|
+
|
|
476
|
+
# Build arguments
|
|
477
|
+
args = ["--files"]
|
|
478
|
+
|
|
479
|
+
if file_type:
|
|
480
|
+
args.extend(["--type", file_type])
|
|
481
|
+
|
|
482
|
+
if glob:
|
|
483
|
+
args.extend(["--glob", glob])
|
|
484
|
+
|
|
485
|
+
if include_hidden:
|
|
486
|
+
args.append("--hidden")
|
|
487
|
+
|
|
488
|
+
if no_ignore:
|
|
489
|
+
args.append("--no-ignore")
|
|
490
|
+
|
|
491
|
+
if sort:
|
|
492
|
+
args.extend(["--sort", sort])
|
|
493
|
+
|
|
494
|
+
args.append(str(search_path))
|
|
495
|
+
|
|
496
|
+
success, output = _run_rg(args)
|
|
497
|
+
|
|
498
|
+
if not success:
|
|
499
|
+
s.add("error", output)
|
|
500
|
+
return output
|
|
501
|
+
|
|
502
|
+
if not output.strip():
|
|
503
|
+
s.add("fileCount", 0)
|
|
504
|
+
return "No files found"
|
|
505
|
+
|
|
506
|
+
# Convert to relative paths if configured
|
|
507
|
+
result = _to_relative_output(output.strip(), search_path)
|
|
508
|
+
|
|
509
|
+
file_count = len(result.split("\n"))
|
|
510
|
+
s.add("fileCount", file_count)
|
|
511
|
+
|
|
512
|
+
return result
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def types() -> str:
|
|
516
|
+
"""List supported file types.
|
|
517
|
+
|
|
518
|
+
Returns all file types that ripgrep recognizes, with their
|
|
519
|
+
associated file extensions and patterns.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
List of file types with extensions.
|
|
523
|
+
|
|
524
|
+
Example:
|
|
525
|
+
# Show all supported types
|
|
526
|
+
ripgrep.types()
|
|
527
|
+
"""
|
|
528
|
+
with LogSpan(span="ripgrep.types") as s:
|
|
529
|
+
# Check rg is installed
|
|
530
|
+
error = _check_rg_installed()
|
|
531
|
+
if error:
|
|
532
|
+
s.add("error", "not_installed")
|
|
533
|
+
return error
|
|
534
|
+
|
|
535
|
+
success, output = _run_rg(["--type-list"])
|
|
536
|
+
|
|
537
|
+
if not success:
|
|
538
|
+
s.add("error", output)
|
|
539
|
+
return output
|
|
540
|
+
|
|
541
|
+
type_count = len(output.strip().split("\n"))
|
|
542
|
+
s.add("typeCount", type_count)
|
|
543
|
+
|
|
544
|
+
return output.strip()
|