rlmgrep 0.1.12__py3-none-any.whl → 0.1.14__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 CHANGED
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.1.12"
2
+ __version__ = "0.1.14"
rlmgrep/cli.py CHANGED
@@ -11,7 +11,7 @@ from .config import ensure_default_config, load_config
11
11
  from .file_map import build_file_map
12
12
  from .ingest import (
13
13
  FileRecord,
14
- build_gitignore_spec,
14
+ build_ignore_spec,
15
15
  collect_candidates,
16
16
  load_files,
17
17
  resolve_type_exts,
@@ -88,7 +88,12 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
88
88
  parser.add_argument("-m", dest="max_count", type=int, default=None, help="Max matching lines per file")
89
89
  parser.add_argument("-a", "--text", dest="binary_as_text", action="store_true", help="Search binary files as text")
90
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")
91
+ parser.add_argument(
92
+ "--no-ignore",
93
+ dest="no_ignore",
94
+ action="store_true",
95
+ help="Do not respect ignore files (.gitignore/.ignore/.rgignore)",
96
+ )
92
97
  parser.add_argument("--answer", action="store_true", help="Print a narrative answer before grep output")
93
98
  parser.add_argument("-y", "--yes", action="store_true", help="Skip file count confirmation")
94
99
  parser.add_argument(
@@ -147,11 +152,24 @@ def _pick(cli_value, config: dict, key: str, default=None):
147
152
  return default
148
153
 
149
154
 
150
- def _find_git_root(start: Path) -> Path | None:
155
+ def _find_git_root(start: Path) -> tuple[Path | None, Path | None]:
151
156
  for p in [start, *start.parents]:
152
- if (p / ".git").is_dir():
153
- return p
154
- return None
157
+ git_path = p / ".git"
158
+ if git_path.is_dir():
159
+ return p, git_path
160
+ if git_path.is_file():
161
+ try:
162
+ raw = git_path.read_text(encoding="utf-8", errors="ignore").strip()
163
+ except Exception:
164
+ raw = ""
165
+ if raw.startswith("gitdir:"):
166
+ git_dir = raw.split(":", 1)[1].strip()
167
+ git_dir_path = Path(git_dir)
168
+ if not git_dir_path.is_absolute():
169
+ git_dir_path = (p / git_dir_path).resolve()
170
+ return p, git_dir_path
171
+ return p, None
172
+ return None, None
155
173
 
156
174
 
157
175
  def _env_value(name: str) -> str | None:
@@ -442,8 +460,12 @@ def main(argv: list[str] | None = None) -> int:
442
460
  ignore_spec = None
443
461
  ignore_root = None
444
462
  if not args.no_ignore:
445
- ignore_root = _find_git_root(cwd) or cwd
446
- ignore_spec = build_gitignore_spec(ignore_root)
463
+ git_root, git_dir = _find_git_root(cwd)
464
+ ignore_root = git_root or cwd
465
+ extra_ignores: list[Path] = []
466
+ if git_dir is not None:
467
+ extra_ignores.append(git_dir / "info" / "exclude")
468
+ ignore_spec = build_ignore_spec(ignore_root, extra_paths=extra_ignores)
447
469
 
448
470
  candidates = collect_candidates(
449
471
  input_paths,
rlmgrep/ingest.py CHANGED
@@ -6,10 +6,7 @@ import os
6
6
  from pathlib import Path, PurePosixPath
7
7
  from typing import Any, Callable, Iterable
8
8
 
9
- try:
10
- import pathspec
11
- except Exception: # pragma: no cover - optional at import time
12
- pathspec = None
9
+ import pathspec
13
10
 
14
11
  from pypdf import PdfReader
15
12
 
@@ -167,18 +164,28 @@ def collect_files(paths: Iterable[str], recursive: bool = True) -> list[Path]:
167
164
  return files
168
165
 
169
166
 
170
- def build_gitignore_spec(root: Path) -> "pathspec.PathSpec | None":
171
- if pathspec is None:
172
- return None
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":
173
173
  root = root.resolve()
174
- gitignore_paths: list[Path] = []
174
+ ignore_paths: list[Path] = []
175
+ extra_paths = list(extra_paths or [])
176
+
175
177
  for dirpath, dirnames, filenames in os.walk(root):
176
178
  if ".git" in dirnames:
177
179
  dirnames.remove(".git")
178
- if ".gitignore" in filenames:
179
- gitignore_paths.append(Path(dirpath) / ".gitignore")
180
+ for name in filenames:
181
+ if name in IGNORE_FILENAMES:
182
+ ignore_paths.append(Path(dirpath) / name)
180
183
 
181
- if not gitignore_paths:
184
+ for extra in extra_paths:
185
+ if extra.exists():
186
+ ignore_paths.append(extra)
187
+
188
+ if not ignore_paths:
182
189
  return None
183
190
 
184
191
  def _sort_key(p: Path) -> tuple[int, str]:
@@ -189,10 +196,10 @@ def build_gitignore_spec(root: Path) -> "pathspec.PathSpec | None":
189
196
  except ValueError:
190
197
  return 0, p.as_posix()
191
198
 
192
- gitignore_paths.sort(key=_sort_key)
199
+ ignore_paths.sort(key=_sort_key)
193
200
 
194
201
  patterns: list[str] = []
195
- for gi in gitignore_paths:
202
+ for gi in ignore_paths:
196
203
  try:
197
204
  rel_dir = gi.parent.relative_to(root).as_posix()
198
205
  except ValueError:
@@ -205,13 +212,18 @@ def build_gitignore_spec(root: Path) -> "pathspec.PathSpec | None":
205
212
  line = raw.rstrip("\n")
206
213
  if not line:
207
214
  continue
215
+ escaped = False
208
216
  if line.startswith("\\#") or line.startswith("\\!"):
209
217
  line = line[1:]
210
- elif line.startswith("#"):
218
+ escaped = True
219
+ if not escaped and line.startswith("#"):
211
220
  continue
212
- negated = line.startswith("!")
213
- if negated:
221
+ negated = False
222
+ if not escaped and line.startswith("!"):
223
+ negated = True
214
224
  line = line[1:]
225
+ if not line:
226
+ continue
215
227
  if line.startswith("/"):
216
228
  line = line[1:]
217
229
  if rel_dir:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rlmgrep
3
- Version: 0.1.12
3
+ Version: 0.1.14
4
4
  Summary: Grep-shaped CLI search powered by DSPy RLM
5
5
  Author: rlmgrep
6
6
  License: MIT
@@ -13,7 +13,7 @@ Requires-Dist: pypdf>=4.0.0
13
13
 
14
14
  # rlmgrep
15
15
 
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.
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.
17
17
 
18
18
  ## Quickstart
19
19
 
@@ -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` rules are respected by default. Use `--hidden` or `--no-ignore` to include them.
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.
@@ -0,0 +1,14 @@
1
+ rlmgrep/__init__.py,sha256=N9x0dPNNIgG8onJijVEeDulgDj_3QJTk0cksf5Vs5IA,49
2
+ rlmgrep/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
3
+ rlmgrep/cli.py,sha256=VZTfuMKXTafPtIYwzY93A_rOjUI-txLOCAZ7d5uVjdQ,22002
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=pjpP08myxzZ0LiO2fjiRKGbZsTe_oiwnDBH4jSUdUDA,12200
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.14.dist-info/METADATA,sha256=Y-cqKCOGEvnJo6-buijUP0BrD1tcMpRWTSIzVKkHC_o,7969
11
+ rlmgrep-0.1.14.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ rlmgrep-0.1.14.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
13
+ rlmgrep-0.1.14.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
14
+ rlmgrep-0.1.14.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- rlmgrep/__init__.py,sha256=Oi10B9QTmGI-1i704NHr63Oy1A8W2kzfQCYB6asuv0w,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=St-MaKUBeAUWwEKtjfNej8Wt9aVhg9-VYEm_GDOV7uU,11909
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.12.dist-info/METADATA,sha256=UsbAYXqsGnSUfqdOypf4D4LTbgCaEuwHTCwp5MNMAxU,7849
11
- rlmgrep-0.1.12.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
- rlmgrep-0.1.12.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
13
- rlmgrep-0.1.12.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
14
- rlmgrep-0.1.12.dist-info/RECORD,,