rgapi 0.2.0__tar.gz → 0.2.2__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.
@@ -12,11 +12,12 @@ jobs:
12
12
  steps:
13
13
  - uses: actions/checkout@v7
14
14
  - uses: dtolnay/rust-toolchain@stable
15
- - run: cargo test
16
15
  - uses: actions/setup-python@v7
17
16
  with:
18
17
  python-version: '3.12'
18
+ - run: cargo test
19
19
  - run: pip install -e '.[dev]'
20
+ - run: cargo develop
20
21
  - run: pytest -q
21
22
 
22
23
  build:
@@ -61,7 +62,7 @@ jobs:
61
62
  - uses: dtolnay/rust-toolchain@stable
62
63
  - id: crates-auth
63
64
  uses: rust-lang/crates-io-auth-action@v1
64
- - run: cargo publish
65
+ - run: cargo publish -p rgapi
65
66
  env:
66
67
  CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
67
68
  - uses: actions/download-artifact@v8
@@ -11,6 +11,21 @@ dependencies = [
11
11
  "memchr",
12
12
  ]
13
13
 
14
+ [[package]]
15
+ name = "bit-set"
16
+ version = "0.8.0"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
19
+ dependencies = [
20
+ "bit-vec",
21
+ ]
22
+
23
+ [[package]]
24
+ name = "bit-vec"
25
+ version = "0.8.0"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
28
+
14
29
  [[package]]
15
30
  name = "bstr"
16
31
  version = "1.13.1"
@@ -18,7 +33,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
18
33
  checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
19
34
  dependencies = [
20
35
  "memchr",
21
- "regex-automata",
22
36
  "serde_core",
23
37
  ]
24
38
 
@@ -91,6 +105,17 @@ dependencies = [
91
105
  "encoding_rs",
92
106
  ]
93
107
 
108
+ [[package]]
109
+ name = "fancy-regex"
110
+ version = "0.19.2"
111
+ source = "registry+https://github.com/rust-lang/crates.io-index"
112
+ checksum = "d301f5bf187b3c295fce6468d3875037a0bccc5f6b151c63cac2f85babf21912"
113
+ dependencies = [
114
+ "bit-set",
115
+ "regex-automata",
116
+ "regex-syntax",
117
+ ]
118
+
94
119
  [[package]]
95
120
  name = "globset"
96
121
  version = "0.4.20"
@@ -113,19 +138,6 @@ dependencies = [
113
138
  "memchr",
114
139
  ]
115
140
 
116
- [[package]]
117
- name = "grep-regex"
118
- version = "0.1.14"
119
- source = "registry+https://github.com/rust-lang/crates.io-index"
120
- checksum = "0ce0c256c3ad82bcc07b812c15a45ec1d398122e8e15124f96695234db7112ef"
121
- dependencies = [
122
- "bstr",
123
- "grep-matcher",
124
- "log",
125
- "regex-automata",
126
- "regex-syntax",
127
- ]
128
-
129
141
  [[package]]
130
142
  name = "grep-searcher"
131
143
  version = "0.1.17"
@@ -171,9 +183,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
171
183
 
172
184
  [[package]]
173
185
  name = "libc"
174
- version = "0.2.189"
186
+ version = "0.2.190"
175
187
  source = "registry+https://github.com/rust-lang/crates.io-index"
176
- checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
188
+ checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
177
189
 
178
190
  [[package]]
179
191
  name = "log"
@@ -225,9 +237,9 @@ dependencies = [
225
237
 
226
238
  [[package]]
227
239
  name = "pyo3"
228
- version = "0.29.2"
240
+ version = "0.29.3"
229
241
  source = "registry+https://github.com/rust-lang/crates.io-index"
230
- checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
242
+ checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655"
231
243
  dependencies = [
232
244
  "libc",
233
245
  "once_cell",
@@ -239,18 +251,18 @@ dependencies = [
239
251
 
240
252
  [[package]]
241
253
  name = "pyo3-build-config"
242
- version = "0.29.2"
254
+ version = "0.29.3"
243
255
  source = "registry+https://github.com/rust-lang/crates.io-index"
244
- checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
256
+ checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb"
245
257
  dependencies = [
246
258
  "target-lexicon",
247
259
  ]
248
260
 
249
261
  [[package]]
250
262
  name = "pyo3-ffi"
251
- version = "0.29.2"
263
+ version = "0.29.3"
252
264
  source = "registry+https://github.com/rust-lang/crates.io-index"
253
- checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
265
+ checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330"
254
266
  dependencies = [
255
267
  "libc",
256
268
  "pyo3-build-config",
@@ -258,9 +270,9 @@ dependencies = [
258
270
 
259
271
  [[package]]
260
272
  name = "pyo3-macros"
261
- version = "0.29.2"
273
+ version = "0.29.3"
262
274
  source = "registry+https://github.com/rust-lang/crates.io-index"
263
- checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
275
+ checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6"
264
276
  dependencies = [
265
277
  "proc-macro2",
266
278
  "pyo3-macros-backend",
@@ -270,9 +282,9 @@ dependencies = [
270
282
 
271
283
  [[package]]
272
284
  name = "pyo3-macros-backend"
273
- version = "0.29.2"
285
+ version = "0.29.3"
274
286
  source = "registry+https://github.com/rust-lang/crates.io-index"
275
- checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
287
+ checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3"
276
288
  dependencies = [
277
289
  "heck",
278
290
  "proc-macro2",
@@ -308,19 +320,29 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
308
320
 
309
321
  [[package]]
310
322
  name = "rgapi"
311
- version = "0.2.0"
323
+ version = "0.2.2"
312
324
  dependencies = [
313
325
  "crc32fast",
326
+ "fancy-regex",
314
327
  "globset",
315
328
  "grep-matcher",
316
- "grep-regex",
317
329
  "grep-searcher",
318
330
  "ignore",
319
- "pyo3",
331
+ "regex-syntax",
320
332
  "serde",
321
333
  "serde_json",
322
334
  ]
323
335
 
336
+ [[package]]
337
+ name = "rgapi-py"
338
+ version = "0.2.2"
339
+ dependencies = [
340
+ "grep-matcher",
341
+ "pyo3",
342
+ "pyo3-build-config",
343
+ "rgapi",
344
+ ]
345
+
324
346
  [[package]]
325
347
  name = "rustversion"
326
348
  version = "1.0.23"
@@ -1,37 +1,44 @@
1
+ [workspace]
2
+ members = ["py"]
3
+
4
+ [workspace.package]
5
+ version = "0.2.2"
6
+ edition = "2024"
7
+
8
+ [workspace.dependencies]
9
+ grep-matcher = ">=0.1.8"
10
+
1
11
  [package]
2
12
  name = "rgapi"
3
- version = "0.2.0"
4
- edition = "2024"
13
+ version.workspace = true
14
+ edition.workspace = true
5
15
  rust-version = "1.91"
6
16
  license = "Apache-2.0"
7
17
  description = "Python API for ripgrep-style file walking and searching"
8
18
  repository = "https://github.com/AnswerDotAI/rgapi"
9
19
  homepage = "https://github.com/AnswerDotAI/rgapi"
10
20
  documentation = "https://github.com/AnswerDotAI/rgapi"
11
- readme = "README.md"
12
21
 
13
22
  [lib]
14
23
  name = "rgapi"
15
- crate-type = ["cdylib", "rlib"]
16
24
 
17
25
  [dependencies]
18
26
  crc32fast = "1"
27
+ fancy-regex = "0.19"
19
28
  globset = ">=0.4.18"
20
- grep-matcher = ">=0.1.8"
21
- grep-regex = ">=0.1.14"
29
+ grep-matcher.workspace = true
22
30
  grep-searcher = ">=0.1.16"
23
31
  ignore = ">=0.4.26"
24
- pyo3 = { version = ">=0.28", optional = true }
32
+ regex-syntax = "0.8"
25
33
  serde = { version = "=1", features = ["derive"] }
26
34
  serde_json = "=1"
27
35
 
28
- [features]
29
- python = ["dep:pyo3"]
30
- extension-module = ["python", "pyo3/extension-module"]
31
-
32
36
  [lints.clippy]
33
37
  too_many_arguments = "allow"
34
38
 
39
+ [profile.test]
40
+ inherits = "release"
41
+
35
42
  [profile.release]
36
43
  lto = false
37
44
  codegen-units = 16
@@ -39,6 +46,9 @@ codegen-units = 16
39
46
  [profile.release.package.rgapi]
40
47
  incremental = true
41
48
 
49
+ [profile.release.package.rgapi-py]
50
+ incremental = true
51
+
42
52
  [profile.dist]
43
53
  inherits = "release"
44
54
  lto = true
@@ -48,3 +58,6 @@ strip = true
48
58
 
49
59
  [profile.dist.package.rgapi]
50
60
  incremental = false
61
+
62
+ [profile.dist.package.rgapi-py]
63
+ incremental = false
@@ -5,10 +5,11 @@
5
5
  ## Layout
6
6
 
7
7
  ```text
8
- src/walk.rs ignore/globset/grep-regex-backed path walking and filtering
9
- src/search.rs grep-regex/grep-searcher-backed searching
8
+ src/matcher.rs shared fancy-regex engine, smart-case policy, and grep-matcher adapter
9
+ src/walk.rs ignore/globset-backed path walking and filtering
10
+ src/search.rs grep-searcher-backed searching
10
11
  src/block.rs blank-line-delimited block grouping, matching, and block context
11
- src/python.rs PyO3 classes and private core functions
12
+ py/src/lib.rs PyO3 classes and private core functions
12
13
  python/rgapi/ public Python wrappers over `rgapi._core`, plus the `rgapi-nbrg` CLI
13
14
  tests/ pytest coverage for the Python API
14
15
  ```
@@ -18,11 +19,11 @@ The public Python API lives in `python/rgapi/__init__.py`. The extension module
18
19
  ## Commands
19
20
 
20
21
  ```bash
21
- maturin develop
22
+ cargo develop
22
23
  pytest -q
23
24
  ```
24
25
 
25
- Python tests (`tests/`) run against the built extension. `cargo test` covers the Rust-only API, and takes no feature flags: `extension-module` stops pyo3 linking libpython, which a test binary needs. For a fast local loop use `maturin develop && pytest -q`. Run `cargo fmt --check` and `cargo check --all-features` for Rust-only edits. Run `chkstyle` after Python edits once tests pass.
26
+ `cargo develop` and bare `cargo test` use the same Cargo profile and share the core and binding libraries. Unit tests also build a separate `cfg(test)` executable. The published `rgapi` crate has no Python dependency; the unpublished `rgapi-py` crate in `py/` builds the extension. Run `cargo fmt` after Rust edits and `chkstyle` before committing Python edits.
26
27
 
27
28
  ## Release
28
29
 
@@ -30,14 +31,18 @@ The canonical version lives in `Cargo.toml`. `pyproject.toml` gets the Python pa
30
31
 
31
32
  Release flow is: release first, then bump - `ship-release` does both.
32
33
 
33
- 1. Run `maturin develop && pytest -q`.
34
- 2. Confirm the release version in `Cargo.toml` (`[package].version`).
34
+ 1. Run `cargo develop && pytest -q`.
35
+ 2. Confirm the release version in `Cargo.toml` (`[workspace.package].version`).
35
36
  3. Run `ship-release`. It tags `v<version>`, pushes branch and tag (CI builds and publishes), then bumps `Cargo.toml`, refreshes the editable install, and pushes the bump without a tag.
36
37
 
37
38
  The GitHub workflow builds wheels for Python 3.10-3.13 on Linux and macOS and publishes the Rust crate, GitHub release artifacts, and PyPI package when a `v*` tag is pushed.
38
39
 
39
40
  ## Design notes
40
41
 
42
+ All user-supplied regexes use the shared `RegexMatcher` in `src/matcher.rs`, backed by `fancy-regex`. There is no separate fallback engine or native C dependency. The adapter implements `grep_matcher::Matcher` so `grep-searcher` continues to handle file scanning, line numbers, context, and binary detection. `compile_regex` returns rgapi's own exported `RegexMatcher` type.
43
+
44
+ The matcher centralizes smart-case analysis and line-ending policy. Line-safe patterns can search a whole buffer; patterns that can consume line endings or inspect neighbouring lines are evaluated on individual lines. Notebook multiline matching instead uses the whole cell. Filename/path regexes match their whole input. Backtracking-limit errors propagate through the existing result channels, including directory-pruning errors; cancellation must not hide a queued error from async consumers.
45
+
41
46
  Python discovery and `paths=True` results contain absolute `pathlib.Path` objects. Structured search rows hold base-relative string labels with `/` separators. Traversal uses `ignore::WalkParallel`, so result order is not part of the API contract. Search results are structured rows; collected result lists use rg-style `str()` and notebook display. `SearchLine.lnhash` is computed with the same CRC-32-based line-content hash format as exhash (`lineno|hash|`, low 12 bits of CRC-32 over the line's UTF-8 bytes, encoded as two Base64url characters); `lnhashs=True` only changes row display, not `line_number` or matching behavior. Path regexes filter returned/searched paths; `skip_dir` and `skip_dir_re` prune traversal through `ignore::WalkBuilder::filter_entry`. Depth, size, filesystem, hidden, and ignore options use `ignore::WalkBuilder` settings. The `ignore` walker follows every root it is given. Discovery checks each root with `symlink_metadata`. The worker sends each unfollowed link root as a result before walking the other roots. This also supports dangling roots, which the underlying walker would reject. `ls` sets `walk_root_links`, which gives a root link to a directory to the walker. The walker then reports paths under the link. Other discovery roots use absolute paths without canonicalizing. Content searches retain canonical root resolution. `rg_iter` exposes the same parallel search stream that `rg` collects by default; `paths=True` and `count=True` consume that stream with different reducers. Text search skips binary files and invalid UTF-8 content.
42
47
 
43
48
  Streaming engine: `walk.rs` owns the generic machinery. `StreamIter<T>` is the worker-thread-plus-bounded-channel iterator (`sync_channel(8192)`, so producers block rather than buffer without limit when a consumer lags), and `spawn_walk` owns the shared scaffold: walker config, panic catching, cancel flag, and worker thread. `rg_iter` (`T = SearchLine`), `block_iter` (`T = SearchBlock`), `nb_iter` (`T = NbCell`), and `find_iter` (`T = PathBuf`, the path walk) plug entry closures into that engine. Block search reads each file once, searches it once, groups nonblank lines into blocks, maps matching lines to their blocks, and expands context by block index. Each `SearchBlock` carries numeric boundaries plus hashes for its first and last source lines. Python keeps both and chooses the displayed address without another file read.
@@ -1,16 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rgapi
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Requires-Dist: fastcore>=2.2.29
7
+ Requires-Dist: fastws-cli>=0.0.20 ; extra == 'dev'
7
8
  Requires-Dist: fastship>=0.0.12 ; extra == 'dev'
8
9
  Requires-Dist: maturin>=1.0,<2.0 ; extra == 'dev'
9
10
  Requires-Dist: pytest ; extra == 'dev'
10
11
  Provides-Extra: dev
11
12
  License-File: LICENSE
12
13
  Summary: Python API for ripgrep-style file walking and searching
13
- Home-Page: https://github.com/AnswerDotAI/rgapi
14
14
  Author-email: Jeremy Howard <j@fast.ai>
15
15
  License-Expression: Apache-2.0
16
16
  Requires-Python: >=3.10
@@ -23,7 +23,7 @@ Project-URL: Repository, https://github.com/AnswerDotAI/rgapi
23
23
 
24
24
  `rgapi` provides `fd`-style file discovery and `rg`-style text search from Python without starting a shell command.
25
25
 
26
- It uses the same `ignore`, `grep-regex`, and `grep-searcher` crates that ripgrep uses for walking, regex matching, and file scanning. Walking and searching run in parallel by default. Most expensive work stays in Rust.
26
+ It uses ripgrep's `ignore` and `grep-searcher` crates for walking and file scanning, with the pure-Rust `fancy-regex` engine for regex matching. Walking and searching run in parallel by default. Most expensive work stays in Rust.
27
27
 
28
28
  ## Overview
29
29
 
@@ -108,6 +108,16 @@ Set `min_depth` and `max_depth` to bound recursion. `max_filesize` skips files a
108
108
 
109
109
  Search is case-sensitive by default, matching `rg`. Use `smart_case=True` for `rg --smart-case` behaviour. Use `case_sensitive=False` to force case-insensitive matching.
110
110
 
111
+ Patterns support backreferences and lookaround everywhere, including filename and path filters:
112
+
113
+ ```python
114
+ rg(r"\b(\w+)\s+\1\b", ".") # repeated words
115
+ rg(r"(?<=TODO: )\w+", ".") # word following TODO:
116
+ fd(".", pattern=r"^(\w+)_\1\.py$") # names such as utils_utils.py
117
+ ```
118
+
119
+ All regex parameters use `fancy-regex`; no engine-selection flag or C library is required. Ordinary patterns use its non-backtracking engine. Patterns requiring backtracking have a bounded backtracking budget; exceeding it raises `ValueError`, rather than silently skipping a file or path.
120
+
111
121
  Each `SearchLine` has these fields:
112
122
 
113
123
  ```text
@@ -127,7 +137,9 @@ Pass `lnhashs=True` to `rg` or `rg_iter` to display hash addresses instead of li
127
137
 
128
138
  For other result forms, use `rg(..., paths=True)` to return unique matched paths or `rg(..., count=True)` to count match spans. `paths` and `count` cannot both be set.
129
139
 
130
- `^` and `$` match at the start and end of each line. A line ends at `\n` or `\r\n`. `$` also matches before a `\r` that has no `\n` after it. `rg`, `rgstr`, and `nbrg` without `multiline=True` raise `ValueError` for a pattern that contains a literal `\n` or `\r`.
140
+ `^` and `$` match at the start and end of each line. A line ends at `\n` or `\r\n`. `$` also matches before a `\r` that has no `\n` after it. `rg`, `rgstr`, and `compile` reject patterns containing a literal `\n` or `\r`. `nbrg` allows them with `multiline=True`.
141
+
142
+ Content searches and compiled content matchers are confined to individual lines: whitespace classes, backreferences and lookaround cannot cross a line ending, and `\A`/`\z` refer to each line. With `nbrg(..., multiline=True)`, `\A`/`\z` refer to the whole cell, while `^`/`$` still anchor lines. Filename and path filters match the full name or path instead.
131
143
 
132
144
  ### Path results
133
145
 
@@ -159,6 +171,8 @@ Set `timeout_ms` on `rg`, `fd`, `walk`, or `ls` to stop at a deadline and return
159
171
 
160
172
  `complete` is true exactly when `stop_reason` is `None`. `count=True` returns a plain integer without a completion flag. It cannot be combined with `timeout_ms`.
161
173
 
174
+ Deadlines stop result collection and request worker cancellation. Workers cannot interrupt a regex evaluation or filesystem call already in progress; a timeout is not a hard per-match execution limit.
175
+
162
176
  ### Context lines
163
177
 
164
178
  `before_context`, `after_context`, and `context` correspond to `rg -B`, `rg -A`, and `rg -C`. Files containing NUL bytes or invalid UTF-8 are skipped.
@@ -190,7 +204,9 @@ nbrg("read_csv", ".") # cells whose source matches, across all
190
204
  nbrg("read_csv", ".", cell_context=1) # also include neighbouring cells as context
191
205
  ```
192
206
 
193
- Notebook discovery, parsing, and matching run together in one parallel Rust pass. Matching uses `rg`'s regex engine with the same `case_sensitive` and `smart_case` behaviour. `nbrg` accepts the discovery filters from `fd` and `rg`, including `include`, `exclude`, `glob`, `hidden`, `max_depth`, and `skip_dir`.
207
+ Notebook discovery, parsing, and matching run together in one parallel Rust pass. Matching uses the same `fancy-regex` engine as `rgapi.rg`, including backreferences and lookaround, with the same `case_sensitive` and `smart_case` behaviour. `nbrg` accepts the discovery filters from `fd` and `rg`, including `include`, `exclude`, `glob`, `hidden`, `max_depth`, and `skip_dir`.
208
+
209
+ Matching is line-oriented by default. Use `nbrg(r"(foo)\n\1", ".", multiline=True)` to match across lines within a cell. Matches never cross cell boundaries.
194
210
 
195
211
  `nbrg` returns `NbResults`, a list of `NbCell`. Each `NbCell` has:
196
212
 
@@ -248,7 +264,7 @@ async for row in rga_iter("TODO", "."): ...
248
264
 
249
265
  Walking and searching use Rust threads, without `asyncio.to_thread` or the event loop's executor. A callback uses `loop.call_soon_threadsafe` to complete the awaited future or supply results to the generator's queue. The event loop remains unblocked, with normal `contextvars` behaviour.
250
266
 
251
- Cancellation stops the Rust workers within about one row. This includes timeouts from `asyncio.wait_for` or `asyncio.timeout`. It also includes task cancellation, such as starlette cancelling a disconnected client's request.
267
+ Cancellation is checked between rows and files; it does not interrupt a regex evaluation or filesystem call already in progress. This includes timeouts from `asyncio.wait_for` or `asyncio.timeout`. It also includes task cancellation, such as starlette cancelling a disconnected client's request.
252
268
 
253
269
  Wrap an async iterator in `contextlib.aclosing` when leaving its loop early. `break` alone delays generator finalization until garbage collection. The context manager provides prompt cleanup:
254
270
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  `rgapi` provides `fd`-style file discovery and `rg`-style text search from Python without starting a shell command.
4
4
 
5
- It uses the same `ignore`, `grep-regex`, and `grep-searcher` crates that ripgrep uses for walking, regex matching, and file scanning. Walking and searching run in parallel by default. Most expensive work stays in Rust.
5
+ It uses ripgrep's `ignore` and `grep-searcher` crates for walking and file scanning, with the pure-Rust `fancy-regex` engine for regex matching. Walking and searching run in parallel by default. Most expensive work stays in Rust.
6
6
 
7
7
  ## Overview
8
8
 
@@ -87,6 +87,16 @@ Set `min_depth` and `max_depth` to bound recursion. `max_filesize` skips files a
87
87
 
88
88
  Search is case-sensitive by default, matching `rg`. Use `smart_case=True` for `rg --smart-case` behaviour. Use `case_sensitive=False` to force case-insensitive matching.
89
89
 
90
+ Patterns support backreferences and lookaround everywhere, including filename and path filters:
91
+
92
+ ```python
93
+ rg(r"\b(\w+)\s+\1\b", ".") # repeated words
94
+ rg(r"(?<=TODO: )\w+", ".") # word following TODO:
95
+ fd(".", pattern=r"^(\w+)_\1\.py$") # names such as utils_utils.py
96
+ ```
97
+
98
+ All regex parameters use `fancy-regex`; no engine-selection flag or C library is required. Ordinary patterns use its non-backtracking engine. Patterns requiring backtracking have a bounded backtracking budget; exceeding it raises `ValueError`, rather than silently skipping a file or path.
99
+
90
100
  Each `SearchLine` has these fields:
91
101
 
92
102
  ```text
@@ -106,7 +116,9 @@ Pass `lnhashs=True` to `rg` or `rg_iter` to display hash addresses instead of li
106
116
 
107
117
  For other result forms, use `rg(..., paths=True)` to return unique matched paths or `rg(..., count=True)` to count match spans. `paths` and `count` cannot both be set.
108
118
 
109
- `^` and `$` match at the start and end of each line. A line ends at `\n` or `\r\n`. `$` also matches before a `\r` that has no `\n` after it. `rg`, `rgstr`, and `nbrg` without `multiline=True` raise `ValueError` for a pattern that contains a literal `\n` or `\r`.
119
+ `^` and `$` match at the start and end of each line. A line ends at `\n` or `\r\n`. `$` also matches before a `\r` that has no `\n` after it. `rg`, `rgstr`, and `compile` reject patterns containing a literal `\n` or `\r`. `nbrg` allows them with `multiline=True`.
120
+
121
+ Content searches and compiled content matchers are confined to individual lines: whitespace classes, backreferences and lookaround cannot cross a line ending, and `\A`/`\z` refer to each line. With `nbrg(..., multiline=True)`, `\A`/`\z` refer to the whole cell, while `^`/`$` still anchor lines. Filename and path filters match the full name or path instead.
110
122
 
111
123
  ### Path results
112
124
 
@@ -138,6 +150,8 @@ Set `timeout_ms` on `rg`, `fd`, `walk`, or `ls` to stop at a deadline and return
138
150
 
139
151
  `complete` is true exactly when `stop_reason` is `None`. `count=True` returns a plain integer without a completion flag. It cannot be combined with `timeout_ms`.
140
152
 
153
+ Deadlines stop result collection and request worker cancellation. Workers cannot interrupt a regex evaluation or filesystem call already in progress; a timeout is not a hard per-match execution limit.
154
+
141
155
  ### Context lines
142
156
 
143
157
  `before_context`, `after_context`, and `context` correspond to `rg -B`, `rg -A`, and `rg -C`. Files containing NUL bytes or invalid UTF-8 are skipped.
@@ -169,7 +183,9 @@ nbrg("read_csv", ".") # cells whose source matches, across all
169
183
  nbrg("read_csv", ".", cell_context=1) # also include neighbouring cells as context
170
184
  ```
171
185
 
172
- Notebook discovery, parsing, and matching run together in one parallel Rust pass. Matching uses `rg`'s regex engine with the same `case_sensitive` and `smart_case` behaviour. `nbrg` accepts the discovery filters from `fd` and `rg`, including `include`, `exclude`, `glob`, `hidden`, `max_depth`, and `skip_dir`.
186
+ Notebook discovery, parsing, and matching run together in one parallel Rust pass. Matching uses the same `fancy-regex` engine as `rgapi.rg`, including backreferences and lookaround, with the same `case_sensitive` and `smart_case` behaviour. `nbrg` accepts the discovery filters from `fd` and `rg`, including `include`, `exclude`, `glob`, `hidden`, `max_depth`, and `skip_dir`.
187
+
188
+ Matching is line-oriented by default. Use `nbrg(r"(foo)\n\1", ".", multiline=True)` to match across lines within a cell. Matches never cross cell boundaries.
173
189
 
174
190
  `nbrg` returns `NbResults`, a list of `NbCell`. Each `NbCell` has:
175
191
 
@@ -227,7 +243,7 @@ async for row in rga_iter("TODO", "."): ...
227
243
 
228
244
  Walking and searching use Rust threads, without `asyncio.to_thread` or the event loop's executor. A callback uses `loop.call_soon_threadsafe` to complete the awaited future or supply results to the generator's queue. The event loop remains unblocked, with normal `contextvars` behaviour.
229
245
 
230
- Cancellation stops the Rust workers within about one row. This includes timeouts from `asyncio.wait_for` or `asyncio.timeout`. It also includes task cancellation, such as starlette cancelling a disconnected client's request.
246
+ Cancellation is checked between rows and files; it does not interrupt a regex evaluation or filesystem call already in progress. This includes timeouts from `asyncio.wait_for` or `asyncio.timeout`. It also includes task cancellation, such as starlette cancelling a disconnected client's request.
231
247
 
232
248
  Wrap an async iterator in `contextlib.aclosing` when leaving its loop early. `break` alone delays generator finalization until garbage collection. The context manager provides prompt cleanup:
233
249
 
@@ -0,0 +1,17 @@
1
+ [package]
2
+ name = "rgapi-py"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ publish = false
6
+
7
+ [lib]
8
+ crate-type = ["cdylib", "rlib"]
9
+ test = false
10
+
11
+ [dependencies]
12
+ rgapi = { path = ".." }
13
+ grep-matcher.workspace = true
14
+ pyo3 = { version = ">=0.29", features = ["extension-module"] }
15
+
16
+ [build-dependencies]
17
+ pyo3-build-config = "0.29"
@@ -0,0 +1 @@
1
+ fn main() { pyo3_build_config::add_extension_module_link_args(); }