rlmgrep 0.1.13__tar.gz → 0.1.18__tar.gz
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-0.1.13 → rlmgrep-0.1.18}/PKG-INFO +2 -2
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/README.md +1 -1
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/pyproject.toml +1 -1
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/__init__.py +1 -1
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/cli.py +74 -8
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/ingest.py +54 -15
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep.egg-info/PKG-INFO +2 -2
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/__main__.py +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/config.py +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/file_map.py +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/interpreter.py +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/render.py +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep/rlm.py +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep.egg-info/SOURCES.txt +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep.egg-info/dependency_links.txt +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep.egg-info/entry_points.txt +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep.egg-info/requires.txt +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/rlmgrep.egg-info/top_level.txt +0 -0
- {rlmgrep-0.1.13 → rlmgrep-0.1.18}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rlmgrep
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.18
|
|
4
4
|
Summary: Grep-shaped CLI search powered by DSPy RLM
|
|
5
5
|
Author: rlmgrep
|
|
6
6
|
License: MIT
|
|
@@ -128,7 +128,7 @@ rg -l "token" . | rlmgrep --files-from-stdin --answer "What does this token cont
|
|
|
128
128
|
## Input selection
|
|
129
129
|
|
|
130
130
|
- Directories are searched recursively by default. Use `--no-recursive` to stop recursion.
|
|
131
|
-
- Hidden files and `.gitignore`
|
|
131
|
+
- Hidden files and ignore files (`.gitignore`, `.ignore`, `.rgignore`) are respected by default. Use `--hidden` or `--no-ignore` to include them.
|
|
132
132
|
- `--type` uses built-in type mappings (e.g., `py`, `js`, `md`); unknown values are treated as file extensions.
|
|
133
133
|
- `-g/--glob` matches path globs against normalized paths (forward slashes).
|
|
134
134
|
- Paths are printed relative to the current working directory when possible.
|
|
@@ -115,7 +115,7 @@ rg -l "token" . | rlmgrep --files-from-stdin --answer "What does this token cont
|
|
|
115
115
|
## Input selection
|
|
116
116
|
|
|
117
117
|
- Directories are searched recursively by default. Use `--no-recursive` to stop recursion.
|
|
118
|
-
- Hidden files and `.gitignore`
|
|
118
|
+
- Hidden files and ignore files (`.gitignore`, `.ignore`, `.rgignore`) are respected by default. Use `--hidden` or `--no-ignore` to include them.
|
|
119
119
|
- `--type` uses built-in type mappings (e.g., `py`, `js`, `md`); unknown values are treated as file extensions.
|
|
120
120
|
- `-g/--glob` matches path globs against normalized paths (forward slashes).
|
|
121
121
|
- Paths are printed relative to the current working directory when possible.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.18"
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
|
|
8
10
|
import dspy
|
|
@@ -11,7 +13,7 @@ from .config import ensure_default_config, load_config
|
|
|
11
13
|
from .file_map import build_file_map
|
|
12
14
|
from .ingest import (
|
|
13
15
|
FileRecord,
|
|
14
|
-
|
|
16
|
+
build_ignore_spec,
|
|
15
17
|
collect_candidates,
|
|
16
18
|
load_files,
|
|
17
19
|
resolve_type_exts,
|
|
@@ -88,7 +90,12 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
88
90
|
parser.add_argument("-m", dest="max_count", type=int, default=None, help="Max matching lines per file")
|
|
89
91
|
parser.add_argument("-a", "--text", dest="binary_as_text", action="store_true", help="Search binary files as text")
|
|
90
92
|
parser.add_argument("--hidden", action="store_true", help="Include hidden files and directories")
|
|
91
|
-
parser.add_argument(
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"--no-ignore",
|
|
95
|
+
dest="no_ignore",
|
|
96
|
+
action="store_true",
|
|
97
|
+
help="Do not respect ignore files (.gitignore/.ignore/.rgignore)",
|
|
98
|
+
)
|
|
92
99
|
parser.add_argument("--answer", action="store_true", help="Print a narrative answer before grep output")
|
|
93
100
|
parser.add_argument("-y", "--yes", action="store_true", help="Skip file count confirmation")
|
|
94
101
|
parser.add_argument(
|
|
@@ -147,11 +154,65 @@ def _pick(cli_value, config: dict, key: str, default=None):
|
|
|
147
154
|
return default
|
|
148
155
|
|
|
149
156
|
|
|
150
|
-
def _find_git_root(start: Path) -> Path | None:
|
|
157
|
+
def _find_git_root(start: Path) -> tuple[Path | None, Path | None]:
|
|
151
158
|
for p in [start, *start.parents]:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
git_path = p / ".git"
|
|
160
|
+
if git_path.is_dir():
|
|
161
|
+
return p, git_path
|
|
162
|
+
if git_path.is_file():
|
|
163
|
+
try:
|
|
164
|
+
raw = git_path.read_text(encoding="utf-8", errors="ignore").strip()
|
|
165
|
+
except Exception:
|
|
166
|
+
raw = ""
|
|
167
|
+
if raw.startswith("gitdir:"):
|
|
168
|
+
git_dir = raw.split(":", 1)[1].strip()
|
|
169
|
+
git_dir_path = Path(git_dir)
|
|
170
|
+
if not git_dir_path.is_absolute():
|
|
171
|
+
git_dir_path = (p / git_dir_path).resolve()
|
|
172
|
+
return p, git_dir_path
|
|
173
|
+
return p, None
|
|
174
|
+
return None, None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _global_ignore_paths(cwd: Path | None = None) -> list[Path]:
|
|
178
|
+
paths: list[Path] = []
|
|
179
|
+
cwd = cwd or Path.cwd()
|
|
180
|
+
if shutil.which("git"):
|
|
181
|
+
try:
|
|
182
|
+
result = subprocess.run(
|
|
183
|
+
["git", "config", "--get", "--path", "core.excludesfile"],
|
|
184
|
+
cwd=cwd,
|
|
185
|
+
capture_output=True,
|
|
186
|
+
text=True,
|
|
187
|
+
check=False,
|
|
188
|
+
)
|
|
189
|
+
value = (result.stdout or "").strip()
|
|
190
|
+
except Exception:
|
|
191
|
+
value = ""
|
|
192
|
+
if value:
|
|
193
|
+
candidate = Path(value).expanduser()
|
|
194
|
+
if candidate.exists():
|
|
195
|
+
paths.append(candidate)
|
|
196
|
+
|
|
197
|
+
xdg_config = os.getenv("XDG_CONFIG_HOME")
|
|
198
|
+
if xdg_config:
|
|
199
|
+
default_path = Path(xdg_config) / "git" / "ignore"
|
|
200
|
+
else:
|
|
201
|
+
default_path = Path.home() / ".config" / "git" / "ignore"
|
|
202
|
+
if default_path.exists():
|
|
203
|
+
paths.append(default_path)
|
|
204
|
+
|
|
205
|
+
legacy = Path.home() / ".gitignore_global"
|
|
206
|
+
if legacy.exists():
|
|
207
|
+
paths.append(legacy)
|
|
208
|
+
|
|
209
|
+
seen: set[Path] = set()
|
|
210
|
+
unique: list[Path] = []
|
|
211
|
+
for p in paths:
|
|
212
|
+
if p not in seen:
|
|
213
|
+
seen.add(p)
|
|
214
|
+
unique.append(p)
|
|
215
|
+
return unique
|
|
155
216
|
|
|
156
217
|
|
|
157
218
|
def _env_value(name: str) -> str | None:
|
|
@@ -442,8 +503,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
442
503
|
ignore_spec = None
|
|
443
504
|
ignore_root = None
|
|
444
505
|
if not args.no_ignore:
|
|
445
|
-
|
|
446
|
-
|
|
506
|
+
git_root, git_dir = _find_git_root(cwd)
|
|
507
|
+
ignore_root = git_root or cwd
|
|
508
|
+
extra_ignores: list[Path] = []
|
|
509
|
+
if git_dir is not None:
|
|
510
|
+
extra_ignores.append(git_dir / "info" / "exclude")
|
|
511
|
+
extra_ignores.extend(_global_ignore_paths(ignore_root))
|
|
512
|
+
ignore_spec = build_ignore_spec(ignore_root, extra_paths=extra_ignores)
|
|
447
513
|
|
|
448
514
|
candidates = collect_candidates(
|
|
449
515
|
input_paths,
|
|
@@ -164,18 +164,28 @@ def collect_files(paths: Iterable[str], recursive: bool = True) -> list[Path]:
|
|
|
164
164
|
return files
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
IGNORE_FILENAMES = {".gitignore", ".ignore", ".rgignore"}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def build_ignore_spec(
|
|
171
|
+
root: Path, extra_paths: Iterable[Path] | None = None
|
|
172
|
+
) -> "pathspec.PathSpec | None":
|
|
170
173
|
root = root.resolve()
|
|
171
|
-
|
|
174
|
+
ignore_paths: list[Path] = []
|
|
175
|
+
extra_paths = list(extra_paths or [])
|
|
176
|
+
|
|
172
177
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
173
178
|
if ".git" in dirnames:
|
|
174
179
|
dirnames.remove(".git")
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
for name in filenames:
|
|
181
|
+
if name in IGNORE_FILENAMES:
|
|
182
|
+
ignore_paths.append(Path(dirpath) / name)
|
|
183
|
+
|
|
184
|
+
for extra in extra_paths:
|
|
185
|
+
if extra.exists():
|
|
186
|
+
ignore_paths.append(extra)
|
|
177
187
|
|
|
178
|
-
if not
|
|
188
|
+
if not ignore_paths:
|
|
179
189
|
return None
|
|
180
190
|
|
|
181
191
|
def _sort_key(p: Path) -> tuple[int, str]:
|
|
@@ -186,14 +196,16 @@ def build_gitignore_spec(root: Path) -> "pathspec.PathSpec | None":
|
|
|
186
196
|
except ValueError:
|
|
187
197
|
return 0, p.as_posix()
|
|
188
198
|
|
|
189
|
-
|
|
199
|
+
ignore_paths.sort(key=_sort_key)
|
|
190
200
|
|
|
191
201
|
patterns: list[str] = []
|
|
192
|
-
for gi in
|
|
202
|
+
for gi in ignore_paths:
|
|
193
203
|
try:
|
|
194
204
|
rel_dir = gi.parent.relative_to(root).as_posix()
|
|
195
205
|
except ValueError:
|
|
196
206
|
rel_dir = ""
|
|
207
|
+
if rel_dir in {".", ""}:
|
|
208
|
+
rel_dir = ""
|
|
197
209
|
try:
|
|
198
210
|
raw_lines = gi.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
199
211
|
except Exception:
|
|
@@ -202,17 +214,38 @@ def build_gitignore_spec(root: Path) -> "pathspec.PathSpec | None":
|
|
|
202
214
|
line = raw.rstrip("\n")
|
|
203
215
|
if not line:
|
|
204
216
|
continue
|
|
217
|
+
escaped = False
|
|
205
218
|
if line.startswith("\\#") or line.startswith("\\!"):
|
|
206
219
|
line = line[1:]
|
|
207
|
-
|
|
220
|
+
escaped = True
|
|
221
|
+
if not escaped and line.startswith("#"):
|
|
208
222
|
continue
|
|
209
|
-
|
|
210
|
-
|
|
223
|
+
|
|
224
|
+
negated = False
|
|
225
|
+
if not escaped and line.startswith("!"):
|
|
226
|
+
negated = True
|
|
211
227
|
line = line[1:]
|
|
228
|
+
if not line:
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
anchored = False
|
|
212
232
|
if line.startswith("/"):
|
|
233
|
+
anchored = True
|
|
213
234
|
line = line[1:]
|
|
235
|
+
if not line:
|
|
236
|
+
continue
|
|
237
|
+
|
|
214
238
|
if rel_dir:
|
|
215
|
-
|
|
239
|
+
if anchored:
|
|
240
|
+
line = f"{rel_dir}/{line}"
|
|
241
|
+
elif "/" in line:
|
|
242
|
+
line = f"{rel_dir}/{line}"
|
|
243
|
+
else:
|
|
244
|
+
line = f"{rel_dir}/**/{line}"
|
|
245
|
+
else:
|
|
246
|
+
if anchored:
|
|
247
|
+
line = f"/{line}"
|
|
248
|
+
|
|
216
249
|
if negated:
|
|
217
250
|
line = "!" + line
|
|
218
251
|
patterns.append(line)
|
|
@@ -314,6 +347,12 @@ def collect_candidates(
|
|
|
314
347
|
) -> list[Path]:
|
|
315
348
|
files = collect_files(paths, recursive=recursive)
|
|
316
349
|
explicit_files: set[Path] = set()
|
|
350
|
+
ignore_root_resolved: Path | None = None
|
|
351
|
+
if ignore_root is not None:
|
|
352
|
+
try:
|
|
353
|
+
ignore_root_resolved = ignore_root.resolve()
|
|
354
|
+
except Exception:
|
|
355
|
+
ignore_root_resolved = ignore_root
|
|
317
356
|
for raw in paths:
|
|
318
357
|
p = Path(raw)
|
|
319
358
|
if p.exists() and p.is_file():
|
|
@@ -330,9 +369,9 @@ def collect_candidates(
|
|
|
330
369
|
except ValueError:
|
|
331
370
|
key = fp.as_posix()
|
|
332
371
|
|
|
333
|
-
if ignore_spec is not None and
|
|
372
|
+
if ignore_spec is not None and ignore_root_resolved is not None and not is_explicit:
|
|
334
373
|
try:
|
|
335
|
-
rel =
|
|
374
|
+
rel = fp_resolved.relative_to(ignore_root_resolved).as_posix()
|
|
336
375
|
except ValueError:
|
|
337
376
|
rel = None
|
|
338
377
|
if rel and ignore_spec.match_file(rel):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rlmgrep
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.18
|
|
4
4
|
Summary: Grep-shaped CLI search powered by DSPy RLM
|
|
5
5
|
Author: rlmgrep
|
|
6
6
|
License: MIT
|
|
@@ -128,7 +128,7 @@ rg -l "token" . | rlmgrep --files-from-stdin --answer "What does this token cont
|
|
|
128
128
|
## Input selection
|
|
129
129
|
|
|
130
130
|
- Directories are searched recursively by default. Use `--no-recursive` to stop recursion.
|
|
131
|
-
- Hidden files and `.gitignore`
|
|
131
|
+
- Hidden files and ignore files (`.gitignore`, `.ignore`, `.rgignore`) are respected by default. Use `--hidden` or `--no-ignore` to include them.
|
|
132
132
|
- `--type` uses built-in type mappings (e.g., `py`, `js`, `md`); unknown values are treated as file extensions.
|
|
133
133
|
- `-g/--glob` matches path globs against normalized paths (forward slashes).
|
|
134
134
|
- Paths are printed relative to the current working directory when possible.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|