rgapi 0.1.27__tar.gz → 0.1.28__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.
@@ -330,7 +330,7 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
330
330
 
331
331
  [[package]]
332
332
  name = "rgapi"
333
- version = "0.1.27"
333
+ version = "0.1.28"
334
334
  dependencies = [
335
335
  "crc32fast",
336
336
  "globset",
@@ -443,9 +443,9 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
443
443
 
444
444
  [[package]]
445
445
  name = "unicode-ident"
446
- version = "1.0.25"
446
+ version = "1.0.26"
447
447
  source = "registry+https://github.com/rust-lang/crates.io-index"
448
- checksum = "ab72a15cf68d77cb0987d3684aa8a45c5ef827e8cb49ee2f30bfd7ba2feb519f"
448
+ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
449
449
 
450
450
  [[package]]
451
451
  name = "walkdir"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rgapi"
3
- version = "0.1.27"
3
+ version = "0.1.28"
4
4
  edition = "2024"
5
5
  rust-version = "1.91"
6
6
  license = "Apache-2.0"
@@ -38,7 +38,7 @@ The GitHub workflow builds wheels for Python 3.10-3.13 on Linux and macOS and pu
38
38
 
39
39
  ## Design notes
40
40
 
41
- Python discovery and `paths=True` results contain absolute `pathlib.Path` objects. Structured search rows retain root-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 16 bits of CRC-32 over the line's UTF-8 bytes); `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. Discovery checks root links with `symlink_metadata` and returns an unfollowed link through a ready stream without a worker. This also supports dangling roots, which the underlying walker would reject. 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.
41
+ Python discovery and `paths=True` results contain absolute `pathlib.Path` objects. Structured search rows retain root-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. Discovery checks root links with `symlink_metadata` and returns an unfollowed link through a ready stream without a worker. This also supports dangling roots, which the underlying walker would reject. 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
42
 
43
43
  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.
44
44
 
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rgapi
3
- Version: 0.1.27
3
+ Version: 0.1.28
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
- Requires-Dist: fastcore>=1.14.6
6
+ Requires-Dist: fastcore>=2.2.29
7
7
  Requires-Dist: fastship>=0.0.12 ; extra == 'dev'
8
8
  Requires-Dist: maturin>=1.0,<2.0 ; extra == 'dev'
9
9
  Requires-Dist: pytest ; extra == 'dev'
@@ -168,7 +168,7 @@ rg("TODO", ".", summary=True, context=1, maxlen=120)
168
168
 
169
169
  The result is `BlockResults`, a list of `SearchBlock` objects. Each block has `path`, `block_index`, `start_line`, `end_line`, `start_lnhash`, `end_lnhash`, `kind`, full `source`, and `matches`.
170
170
 
171
- Matches display as `path:start-end:source`. Context displays as `path:start-end-source`. With `lnhashs=True`, the range uses copyable boundary addresses such as `path:4|a3f2|,6|b1c3|:source`. Newline runs display as `¶`. `maxlen` limits the displayed text without changing `source` or `asdict()`.
171
+ Matches display as `path:start-end:source`. Context displays as `path:start-end-source`. With `lnhashs=True`, the range uses copyable boundary addresses such as `path:4|Py|,6|HD|:source`. Newline runs display as `¶`. `maxlen` limits the displayed text without changing `source` or `asdict()`.
172
172
 
173
173
  In summary mode, `before_context`, `after_context`, and `context` count neighbouring blocks. `max_results` counts matching blocks and retains their context. `summary=True` cannot be combined with `paths` or `count`. It can be combined with `lnhash` for copyable block boundaries.
174
174
 
@@ -147,7 +147,7 @@ rg("TODO", ".", summary=True, context=1, maxlen=120)
147
147
 
148
148
  The result is `BlockResults`, a list of `SearchBlock` objects. Each block has `path`, `block_index`, `start_line`, `end_line`, `start_lnhash`, `end_lnhash`, `kind`, full `source`, and `matches`.
149
149
 
150
- Matches display as `path:start-end:source`. Context displays as `path:start-end-source`. With `lnhashs=True`, the range uses copyable boundary addresses such as `path:4|a3f2|,6|b1c3|:source`. Newline runs display as `¶`. `maxlen` limits the displayed text without changing `source` or `asdict()`.
150
+ Matches display as `path:start-end:source`. Context displays as `path:start-end-source`. With `lnhashs=True`, the range uses copyable boundary addresses such as `path:4|Py|,6|HD|:source`. Newline runs display as `¶`. `maxlen` limits the displayed text without changing `source` or `asdict()`.
151
151
 
152
152
  In summary mode, `before_context`, `after_context`, and `context` count neighbouring blocks. `max_results` counts matching blocks and retains their context. `summary=True` cannot be combined with `paths` or `count`. It can be combined with `lnhash` for copyable block boundaries.
153
153
 
@@ -14,7 +14,7 @@ classifiers = [
14
14
  "Programming Language :: Rust",
15
15
  "Programming Language :: Python :: Implementation :: CPython",
16
16
  ]
17
- dependencies = ["fastcore>=1.14.6"]
17
+ dependencies = ["fastcore>=2.2.29"]
18
18
 
19
19
  [project.optional-dependencies]
20
20
  dev = ["fastship>=0.0.12", "maturin>=1.0,<2.0", "pytest"]
@@ -185,9 +185,11 @@ pub fn compile_regex(pattern: &str, case_sensitive: Option<bool>, smart_case: bo
185
185
  builder.build(pattern).map_err(|e| RgApiError::new(e.to_string()))
186
186
  }
187
187
 
188
- fn line_hash_u16(line: &str) -> u16 { (crc32fast::hash(line.as_bytes()) & 0xffff) as u16 }
189
-
190
- pub(crate) fn format_lnhash(lineno: u64, line: &str) -> String { format!("{}|{:04x}|", lineno, line_hash_u16(line)) }
188
+ pub(crate) fn format_lnhash(lineno: u64, line: &str) -> String {
189
+ const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
190
+ let hash = crc32fast::hash(line.as_bytes()) as usize;
191
+ format!("{}|{}{}|", lineno, ALPHABET[(hash >> 6) & 63] as char, ALPHABET[hash & 63] as char)
192
+ }
191
193
 
192
194
  pub fn search_path(
193
195
  path: &Path,
@@ -185,11 +185,11 @@ def test_depth_size_and_filesystem_options(tmp_path):
185
185
  assert [r.path for r in rg("TODO", str(tmp_path), min_depth=2, max_filesize=5)] == ["sub/small.txt"]
186
186
 
187
187
 
188
- def test_lnhash_matches_stdlib_crc32(tmp_path):
189
- import zlib
188
+ def test_lnhash_matches_fastcore(tmp_path):
189
+ from fastcore.tools import lnhash as py_hash
190
190
  make_tree(tmp_path)
191
- row = rg("TODO", str(tmp_path))[0]
192
- assert row.lnhash == f"{row.line_number}|{zlib.crc32(row.line.encode()) & 0xffff:04x}|"
191
+ for row in rg(".", str(tmp_path)):
192
+ assert row.lnhash == py_hash(row.line_number, row.line)
193
193
 
194
194
 
195
195
  def test_rg_returns_structured_matches_context_and_relative_paths(tmp_path):
@@ -214,8 +214,8 @@ def test_rg_returns_structured_matches_context_and_relative_paths(tmp_path):
214
214
  except AssertionError as e: assert "mutually exclusive" in str(e)
215
215
  else: assert False
216
216
  addr = res[1].lnhash.split("|")
217
- assert addr[0] == "2" and len(addr[1]) == 4 and addr[2:] == [""]
218
- assert int(addr[1], 16) >= 0
217
+ assert addr[0] == "2" and len(addr[1]) == 2 and addr[2:] == [""]
218
+ assert all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' for c in addr[1])
219
219
  expected = (f'SearchLine(kind="match", path="src/app.py", line_number=2, lnhash="{res[1].lnhash}", '
220
220
  'line="TODO here", matches=[(0, 4)])')
221
221
  assert repr(res[1]) == expected
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes