rlmgrep 0.1.13__py3-none-any.whl → 0.1.15__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.13"
2
+ __version__ = "0.1.15"
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
@@ -164,18 +164,28 @@ def collect_files(paths: Iterable[str], recursive: bool = True) -> list[Path]:
164
164
  return files
165
165
 
166
166
 
167
- def build_gitignore_spec(root: Path) -> "pathspec.PathSpec | None":
168
- if pathspec is None:
169
- 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":
170
173
  root = root.resolve()
171
- gitignore_paths: list[Path] = []
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
- if ".gitignore" in filenames:
176
- gitignore_paths.append(Path(dirpath) / ".gitignore")
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 gitignore_paths:
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
- gitignore_paths.sort(key=_sort_key)
199
+ ignore_paths.sort(key=_sort_key)
190
200
 
191
201
  patterns: list[str] = []
192
- for gi in gitignore_paths:
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,13 +214,18 @@ 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
- elif line.startswith("#"):
220
+ escaped = True
221
+ if not escaped and line.startswith("#"):
208
222
  continue
209
- negated = line.startswith("!")
210
- if negated:
223
+ negated = False
224
+ if not escaped and line.startswith("!"):
225
+ negated = True
211
226
  line = line[1:]
227
+ if not line:
228
+ continue
212
229
  if line.startswith("/"):
213
230
  line = line[1:]
214
231
  if rel_dir:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rlmgrep
3
- Version: 0.1.13
3
+ Version: 0.1.15
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` 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=7lQKxph4q-63ntDsnV2TgQObrDVc7503eBKrCf_wsiE,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=zBdvTc5MxY8FqFKDPxhPs3U3v-bvTouCv8V5Yc4swK4,12258
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.15.dist-info/METADATA,sha256=QqEYGSK4_z-dlWnCPcgibeqxURz-a0VSjeKO23XtuVo,7969
11
+ rlmgrep-0.1.15.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ rlmgrep-0.1.15.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
13
+ rlmgrep-0.1.15.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
14
+ rlmgrep-0.1.15.dist-info/RECORD,,
@@ -1,14 +0,0 @@
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,,