rlmgrep 0.1.11__py3-none-any.whl → 0.1.13__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.
- rlmgrep/__init__.py +1 -1
- rlmgrep/cli.py +25 -1
- rlmgrep/ingest.py +87 -1
- {rlmgrep-0.1.11.dist-info → rlmgrep-0.1.13.dist-info}/METADATA +6 -2
- rlmgrep-0.1.13.dist-info/RECORD +14 -0
- rlmgrep-0.1.11.dist-info/RECORD +0 -14
- {rlmgrep-0.1.11.dist-info → rlmgrep-0.1.13.dist-info}/WHEEL +0 -0
- {rlmgrep-0.1.11.dist-info → rlmgrep-0.1.13.dist-info}/entry_points.txt +0 -0
- {rlmgrep-0.1.11.dist-info → rlmgrep-0.1.13.dist-info}/top_level.txt +0 -0
rlmgrep/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.13"
|
rlmgrep/cli.py
CHANGED
|
@@ -9,7 +9,13 @@ import dspy
|
|
|
9
9
|
from . import __version__
|
|
10
10
|
from .config import ensure_default_config, load_config
|
|
11
11
|
from .file_map import build_file_map
|
|
12
|
-
from .ingest import
|
|
12
|
+
from .ingest import (
|
|
13
|
+
FileRecord,
|
|
14
|
+
build_gitignore_spec,
|
|
15
|
+
collect_candidates,
|
|
16
|
+
load_files,
|
|
17
|
+
resolve_type_exts,
|
|
18
|
+
)
|
|
13
19
|
from .rlm import Match, build_lm, run_rlm
|
|
14
20
|
from .render import render_matches
|
|
15
21
|
|
|
@@ -81,6 +87,8 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
81
87
|
parser.add_argument("-B", dest="before", type=int, default=None, help="Context lines before")
|
|
82
88
|
parser.add_argument("-m", dest="max_count", type=int, default=None, help="Max matching lines per file")
|
|
83
89
|
parser.add_argument("-a", "--text", dest="binary_as_text", action="store_true", help="Search binary files as text")
|
|
90
|
+
parser.add_argument("--hidden", action="store_true", help="Include hidden files and directories")
|
|
91
|
+
parser.add_argument("--no-ignore", dest="no_ignore", action="store_true", help="Do not respect .gitignore")
|
|
84
92
|
parser.add_argument("--answer", action="store_true", help="Print a narrative answer before grep output")
|
|
85
93
|
parser.add_argument("-y", "--yes", action="store_true", help="Skip file count confirmation")
|
|
86
94
|
parser.add_argument(
|
|
@@ -139,6 +147,13 @@ def _pick(cli_value, config: dict, key: str, default=None):
|
|
|
139
147
|
return default
|
|
140
148
|
|
|
141
149
|
|
|
150
|
+
def _find_git_root(start: Path) -> Path | None:
|
|
151
|
+
for p in [start, *start.parents]:
|
|
152
|
+
if (p / ".git").is_dir():
|
|
153
|
+
return p
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
142
157
|
def _env_value(name: str) -> str | None:
|
|
143
158
|
val = os.getenv(name)
|
|
144
159
|
if val is None:
|
|
@@ -424,12 +439,21 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
424
439
|
if hard_max is not None and hard_max <= 0:
|
|
425
440
|
hard_max = None
|
|
426
441
|
|
|
442
|
+
ignore_spec = None
|
|
443
|
+
ignore_root = None
|
|
444
|
+
if not args.no_ignore:
|
|
445
|
+
ignore_root = _find_git_root(cwd) or cwd
|
|
446
|
+
ignore_spec = build_gitignore_spec(ignore_root)
|
|
447
|
+
|
|
427
448
|
candidates = collect_candidates(
|
|
428
449
|
input_paths,
|
|
429
450
|
cwd=cwd,
|
|
430
451
|
recursive=args.recursive,
|
|
431
452
|
include_globs=globs,
|
|
432
453
|
type_exts=type_exts,
|
|
454
|
+
include_hidden=args.hidden,
|
|
455
|
+
ignore_spec=ignore_spec,
|
|
456
|
+
ignore_root=ignore_root,
|
|
433
457
|
)
|
|
434
458
|
candidate_count = len(candidates)
|
|
435
459
|
if hard_max is not None and candidate_count > hard_max:
|
rlmgrep/ingest.py
CHANGED
|
@@ -2,8 +2,11 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from fnmatch import fnmatch
|
|
5
|
+
import os
|
|
5
6
|
from pathlib import Path, PurePosixPath
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import Any, Callable, Iterable
|
|
8
|
+
|
|
9
|
+
import pathspec
|
|
7
10
|
|
|
8
11
|
from pypdf import PdfReader
|
|
9
12
|
|
|
@@ -161,6 +164,64 @@ def collect_files(paths: Iterable[str], recursive: bool = True) -> list[Path]:
|
|
|
161
164
|
return files
|
|
162
165
|
|
|
163
166
|
|
|
167
|
+
def build_gitignore_spec(root: Path) -> "pathspec.PathSpec | None":
|
|
168
|
+
if pathspec is None:
|
|
169
|
+
return None
|
|
170
|
+
root = root.resolve()
|
|
171
|
+
gitignore_paths: list[Path] = []
|
|
172
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
173
|
+
if ".git" in dirnames:
|
|
174
|
+
dirnames.remove(".git")
|
|
175
|
+
if ".gitignore" in filenames:
|
|
176
|
+
gitignore_paths.append(Path(dirpath) / ".gitignore")
|
|
177
|
+
|
|
178
|
+
if not gitignore_paths:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def _sort_key(p: Path) -> tuple[int, str]:
|
|
182
|
+
try:
|
|
183
|
+
rel = p.parent.relative_to(root)
|
|
184
|
+
depth = len(rel.parts)
|
|
185
|
+
return depth, rel.as_posix()
|
|
186
|
+
except ValueError:
|
|
187
|
+
return 0, p.as_posix()
|
|
188
|
+
|
|
189
|
+
gitignore_paths.sort(key=_sort_key)
|
|
190
|
+
|
|
191
|
+
patterns: list[str] = []
|
|
192
|
+
for gi in gitignore_paths:
|
|
193
|
+
try:
|
|
194
|
+
rel_dir = gi.parent.relative_to(root).as_posix()
|
|
195
|
+
except ValueError:
|
|
196
|
+
rel_dir = ""
|
|
197
|
+
try:
|
|
198
|
+
raw_lines = gi.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
199
|
+
except Exception:
|
|
200
|
+
continue
|
|
201
|
+
for raw in raw_lines:
|
|
202
|
+
line = raw.rstrip("\n")
|
|
203
|
+
if not line:
|
|
204
|
+
continue
|
|
205
|
+
if line.startswith("\\#") or line.startswith("\\!"):
|
|
206
|
+
line = line[1:]
|
|
207
|
+
elif line.startswith("#"):
|
|
208
|
+
continue
|
|
209
|
+
negated = line.startswith("!")
|
|
210
|
+
if negated:
|
|
211
|
+
line = line[1:]
|
|
212
|
+
if line.startswith("/"):
|
|
213
|
+
line = line[1:]
|
|
214
|
+
if rel_dir:
|
|
215
|
+
line = f"{rel_dir}/{line}"
|
|
216
|
+
if negated:
|
|
217
|
+
line = "!" + line
|
|
218
|
+
patterns.append(line)
|
|
219
|
+
|
|
220
|
+
if not patterns:
|
|
221
|
+
return None
|
|
222
|
+
return pathspec.PathSpec.from_lines("gitwildmatch", patterns)
|
|
223
|
+
|
|
224
|
+
|
|
164
225
|
TYPE_EXTS = {
|
|
165
226
|
"bash": {".bash"},
|
|
166
227
|
"c": {".c", ".h"},
|
|
@@ -237,21 +298,46 @@ def _matches_globs(path: str, globs: list[str]) -> bool:
|
|
|
237
298
|
return False
|
|
238
299
|
|
|
239
300
|
|
|
301
|
+
def _is_hidden_path(path: Path) -> bool:
|
|
302
|
+
return any(part.startswith(".") for part in path.parts if part)
|
|
303
|
+
|
|
304
|
+
|
|
240
305
|
def collect_candidates(
|
|
241
306
|
paths: Iterable[str],
|
|
242
307
|
cwd: Path,
|
|
243
308
|
recursive: bool = True,
|
|
244
309
|
include_globs: list[str] | None = None,
|
|
245
310
|
type_exts: set[str] | None = None,
|
|
311
|
+
include_hidden: bool = False,
|
|
312
|
+
ignore_spec: "pathspec.PathSpec | None" = None,
|
|
313
|
+
ignore_root: Path | None = None,
|
|
246
314
|
) -> list[Path]:
|
|
247
315
|
files = collect_files(paths, recursive=recursive)
|
|
316
|
+
explicit_files: set[Path] = set()
|
|
317
|
+
for raw in paths:
|
|
318
|
+
p = Path(raw)
|
|
319
|
+
if p.exists() and p.is_file():
|
|
320
|
+
explicit_files.add(p.resolve())
|
|
248
321
|
candidates: list[Path] = []
|
|
249
322
|
for fp in files:
|
|
323
|
+
fp_resolved = fp.resolve()
|
|
324
|
+
is_explicit = fp_resolved in explicit_files
|
|
325
|
+
if not include_hidden and not is_explicit and _is_hidden_path(fp):
|
|
326
|
+
continue
|
|
327
|
+
|
|
250
328
|
try:
|
|
251
329
|
key = fp.relative_to(cwd).as_posix()
|
|
252
330
|
except ValueError:
|
|
253
331
|
key = fp.as_posix()
|
|
254
332
|
|
|
333
|
+
if ignore_spec is not None and ignore_root is not None and not is_explicit:
|
|
334
|
+
try:
|
|
335
|
+
rel = fp.relative_to(ignore_root).as_posix()
|
|
336
|
+
except ValueError:
|
|
337
|
+
rel = None
|
|
338
|
+
if rel and ignore_spec.match_file(rel):
|
|
339
|
+
continue
|
|
340
|
+
|
|
255
341
|
if include_globs and not _matches_globs(key, include_globs):
|
|
256
342
|
continue
|
|
257
343
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rlmgrep
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.13
|
|
4
4
|
Summary: Grep-shaped CLI search powered by DSPy RLM
|
|
5
5
|
Author: rlmgrep
|
|
6
6
|
License: MIT
|
|
@@ -8,11 +8,12 @@ Requires-Python: >=3.11
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
Requires-Dist: dspy>=3.1.1
|
|
10
10
|
Requires-Dist: markitdown[all]>=0.1.4
|
|
11
|
+
Requires-Dist: pathspec>=0.12.1
|
|
11
12
|
Requires-Dist: pypdf>=4.0.0
|
|
12
13
|
|
|
13
14
|
# rlmgrep
|
|
14
15
|
|
|
15
|
-
Grep-shaped search powered by DSPy RLM. It accepts a natural-language query, scans the files you point at, and prints matching lines in a grep-like format.
|
|
16
|
+
Grep-shaped search powered by DSPy RLM. It accepts a natural-language query, scans the files you point at, and prints matching lines in a grep-like format. Use `--answer` to get a narrative response grounded in the selected files/directories.
|
|
16
17
|
|
|
17
18
|
## Quickstart
|
|
18
19
|
|
|
@@ -93,6 +94,8 @@ Common options:
|
|
|
93
94
|
- `-m N` max matching lines per file
|
|
94
95
|
- `-g GLOB` include files matching glob (repeatable, comma-separated)
|
|
95
96
|
- `--type T` include file types (repeatable, comma-separated)
|
|
97
|
+
- `--hidden` include hidden files and directories
|
|
98
|
+
- `--no-ignore` do not respect `.gitignore`
|
|
96
99
|
- `--no-recursive` do not recurse directories
|
|
97
100
|
- `-a`, `--text` treat binary files as text
|
|
98
101
|
- `-y`, `--yes` skip file count confirmation
|
|
@@ -125,6 +128,7 @@ rg -l "token" . | rlmgrep --files-from-stdin --answer "What does this token cont
|
|
|
125
128
|
## Input selection
|
|
126
129
|
|
|
127
130
|
- Directories are searched recursively by default. Use `--no-recursive` to stop recursion.
|
|
131
|
+
- Hidden files and `.gitignore` rules are respected by default. Use `--hidden` or `--no-ignore` to include them.
|
|
128
132
|
- `--type` uses built-in type mappings (e.g., `py`, `js`, `md`); unknown values are treated as file extensions.
|
|
129
133
|
- `-g/--glob` matches path globs against normalized paths (forward slashes).
|
|
130
134
|
- Paths are printed relative to the current working directory when possible.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
rlmgrep/__init__.py,sha256=BrGyE0EGAAcw0ie-YqC8DUrNp9CZXcKoms53BtFY3f0,49
|
|
2
|
+
rlmgrep/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
+
rlmgrep/cli.py,sha256=sUtcvf-3U1fpUdFYmEE9j75xSgBAx9Uv3XY1lobHURk,21150
|
|
4
|
+
rlmgrep/config.py,sha256=u1iz-nI8dj-dZETbpIki3RQefHJEyi5oE5zE4_IR8kg,2399
|
|
5
|
+
rlmgrep/file_map.py,sha256=x2Ri1wzK8_87GUorsAV01K_nYLZcv30yIquDeTCcdEw,876
|
|
6
|
+
rlmgrep/ingest.py,sha256=Um4n0jvPaBhn_CieEu1RfdI0O-0k7N--sp2ncuwseqE,11816
|
|
7
|
+
rlmgrep/interpreter.py,sha256=s_nMRxLlAU9C0JmUzUBW5NbVbuH67doVWF54K54STlA,2478
|
|
8
|
+
rlmgrep/render.py,sha256=mCTT6yuKNv7HJ46LzOyLkCbyBedCWSNd7UeubyLXcyM,3356
|
|
9
|
+
rlmgrep/rlm.py,sha256=i3rCTp8OABByF60Un5gO7265gaW4spwU0OFKIz4surg,5750
|
|
10
|
+
rlmgrep-0.1.13.dist-info/METADATA,sha256=ZEzhJPnjR4oyq70tERS1XY5k-69e4FThLsY_1LX6GuQ,7936
|
|
11
|
+
rlmgrep-0.1.13.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
+
rlmgrep-0.1.13.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
|
|
13
|
+
rlmgrep-0.1.13.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
|
|
14
|
+
rlmgrep-0.1.13.dist-info/RECORD,,
|
rlmgrep-0.1.11.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
rlmgrep/__init__.py,sha256=eEU5vUkbBcAVKg20oMuOjMenVs64GjcSE_sBCcK_srU,49
|
|
2
|
-
rlmgrep/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
3
|
-
rlmgrep/cli.py,sha256=Jn7knAQq3Bnb578QK33RxDZ102yFrVSbNFjUKBGkb1o,20417
|
|
4
|
-
rlmgrep/config.py,sha256=u1iz-nI8dj-dZETbpIki3RQefHJEyi5oE5zE4_IR8kg,2399
|
|
5
|
-
rlmgrep/file_map.py,sha256=x2Ri1wzK8_87GUorsAV01K_nYLZcv30yIquDeTCcdEw,876
|
|
6
|
-
rlmgrep/ingest.py,sha256=uCz2el9B-RIT9umFo-gFEdAsmWPP1IJOArFFQY0D_1A,9127
|
|
7
|
-
rlmgrep/interpreter.py,sha256=s_nMRxLlAU9C0JmUzUBW5NbVbuH67doVWF54K54STlA,2478
|
|
8
|
-
rlmgrep/render.py,sha256=mCTT6yuKNv7HJ46LzOyLkCbyBedCWSNd7UeubyLXcyM,3356
|
|
9
|
-
rlmgrep/rlm.py,sha256=i3rCTp8OABByF60Un5gO7265gaW4spwU0OFKIz4surg,5750
|
|
10
|
-
rlmgrep-0.1.11.dist-info/METADATA,sha256=ykp-GtmTqprVgTF1L0tV_Wc9CHI--3FwlYjAQVxcbF0,7610
|
|
11
|
-
rlmgrep-0.1.11.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
12
|
-
rlmgrep-0.1.11.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
|
|
13
|
-
rlmgrep-0.1.11.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
|
|
14
|
-
rlmgrep-0.1.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|