Dump the state after the last printed record for all -r/--grep/--context filters
This commit is contained in:
1 parent
ecf85445ea
commit
c2821dedb5
3 files changed
+45
-11
No files matched your search
+3
-1
@@ -78,12 +78,14 @@ A revision number identifies a *state position between diffs*, not a diff:
|
||||
`-r` is repeatable and its specs fall into two classes:
|
||||
|
||||
- **Unit specs** — any spec with a letter-prefixed bound (`l`, `s`, `v`, `r`, including mixed forms like `2:s3`) — index into the database file and resolve to a line window. Multiple unit specs intersect restrictively: only the common line range is processed.
|
||||
- **Plain-number specs** — both bounds (or the single index) are plain numbers — index into the *filtered output*: the records left after all unit specs and all `--grep` patterns have been applied. They are a pure display filter and do not affect replay, migrations, or the `-o` output state. Negative values count from the end of the filtered output.
|
||||
- **Plain-number specs** — both bounds (or the single index) are plain numbers — index into the *filtered output*: the records left after all unit specs and all `--grep` patterns have been applied. Negative values count from the end of the filtered output.
|
||||
|
||||
Like repeated `--grep` patterns (which must all match), repeated ranges are restrictive: `-r r1:r5 -r 0:2` prints only the first two records of those in the revision window, regardless of flag order. Plain ranges truncate Python-style and print nothing when the intersection is empty; a single plain index must exist and fails with exit code 2 otherwise.
|
||||
|
||||
`--context N` (default 0) adds GNU grep-style context as the final step: each printed item is expanded by N surrounding records drawn from the records in the processed range, free of the `--grep` and plain-number filters, with overlapping expansions merged so nearby matches do not duplicate context records.
|
||||
|
||||
The JSON dump (`-o`) and migrations operate on the state after the **last printed record**, whatever filtering produced the printout — so a plain `-r 2:5` dump ends at the 5th filtered record, and `--grep` or `--context` shift the dump to their last printed record as well. When nothing is printed (an empty filter result or `-q`), the dump falls back to the state at the end of the unit-spec window (or the end of the file).
|
||||
|
||||
## Meta Format Migrations
|
||||
|
||||
The snapshot `k` field carries an integer meta format version (`kanta.structs.META_FORMAT`, currently 1), bumped manually whenever the record-level format around the payloads changes. k1 introduced revision numbers.
|
||||
|
||||
+11
-4
@@ -244,9 +244,10 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
" vN = version, rN = revision (rN is the record that produced"
|
||||
" state N, with its state). Plain numbers instead index into"
|
||||
" the filtered output: they select among the records left after"
|
||||
" all unit ranges and --grep patterns, like a display filter,"
|
||||
" without affecting the output state. Repeated ranges are"
|
||||
" restrictive: only the common subset is printed. Negative"
|
||||
" all unit ranges and --grep patterns. The -o dump and"
|
||||
" migrations operate on the state after the last printed"
|
||||
" record. Repeated ranges are restrictive: only the common"
|
||||
" subset is printed. Negative"
|
||||
" snapshot values count from the end (s-1 is the last"
|
||||
" snapshot); negative plain numbers count from the end of the"
|
||||
" filtered output. Use ':' for"
|
||||
@@ -622,6 +623,12 @@ async def _run(args: argparse.Namespace) -> int:
|
||||
expanded.update(range(lo, hi))
|
||||
matched = sorted(expanded)
|
||||
items = [pool[idx] for idx in matched]
|
||||
# The dump and migrations operate on the state after the last
|
||||
# printed item; an empty display keeps the window-end state.
|
||||
if items:
|
||||
last_event, _, last_current, _ = items[-1]
|
||||
state = last_current
|
||||
version = last_event.version
|
||||
for event, previous, current, highlight in items:
|
||||
label = record_label(event.line_number, event.record_index)
|
||||
if isinstance(event, SnapshotEvent):
|
||||
@@ -645,7 +652,7 @@ async def _run(args: argparse.Namespace) -> int:
|
||||
if items:
|
||||
_print()
|
||||
|
||||
# Apply optional migrations to the range-end state.
|
||||
# Apply optional migrations to the selected end state.
|
||||
if kanta._impl.migrations is not None:
|
||||
try:
|
||||
previous_version = version
|
||||
|
||||
+31
-6
@@ -28,8 +28,8 @@ def _build(path, increments=3):
|
||||
asyncio.run(build())
|
||||
|
||||
|
||||
def test_plain_range_filters_output_not_state(tmp_path, capsys):
|
||||
"""Plain numbers slice the printed items; -o state is unaffected."""
|
||||
def test_plain_range_restricts_dump_to_last_printed(tmp_path, capsys):
|
||||
"""The JSON dump holds the state after the last printed item."""
|
||||
path = tmp_path / "data.kantadb"
|
||||
_build(path)
|
||||
capsys.readouterr()
|
||||
@@ -41,8 +41,8 @@ def test_plain_range_filters_output_not_state(tmp_path, capsys):
|
||||
assert "inc0" in out.err
|
||||
assert "inc1" in out.err
|
||||
assert "inc2" not in out.err
|
||||
# Display filter only: the output state is the full replayed state.
|
||||
assert json.loads(out.out) == {"users": {}, "counter": 3}
|
||||
# The dump is the state after inc1, the last printed record.
|
||||
assert json.loads(out.out) == {"users": {}, "counter": 2}
|
||||
|
||||
# Inclusive end.
|
||||
assert _run_cli(path, "-r", "0..1") == 0
|
||||
@@ -143,8 +143,8 @@ def test_unit_and_plain_combine(tmp_path, capsys):
|
||||
out = capsys.readouterr()
|
||||
assert "inc0" in out.err
|
||||
assert "inc1" not in out.err
|
||||
# The plain spec does not narrow the output state.
|
||||
assert json.loads(out.out) == {"users": {}, "counter": 2}
|
||||
# The dump is the state after the last printed item (inc0).
|
||||
assert json.loads(out.out) == {"users": {}, "counter": 1}
|
||||
|
||||
|
||||
def test_context_expands_after_grep(tmp_path, capsys):
|
||||
@@ -191,3 +191,28 @@ def test_context_merges_overlaps(tmp_path, capsys):
|
||||
assert err.count(action) == 1
|
||||
assert "bootstrap" not in err
|
||||
assert "inc4" not in err
|
||||
|
||||
|
||||
def test_grep_and_context_shift_dump_to_last_printed(tmp_path, capsys):
|
||||
"""Grep and --context are display filters, but the dump follows the
|
||||
last printed record, so they move it too."""
|
||||
path = tmp_path / "data.kantadb"
|
||||
_build(path)
|
||||
capsys.readouterr()
|
||||
|
||||
# Only inc1 prints; the dump is the state after inc1.
|
||||
assert _run_cli(path, "--grep", "inc1", "-o", "-") == 0
|
||||
out = capsys.readouterr()
|
||||
assert "inc0" not in out.err
|
||||
assert json.loads(out.out) == {"users": {}, "counter": 2}
|
||||
|
||||
# Context extends the printout past the match; the dump follows it.
|
||||
assert _run_cli(path, "--grep", "inc1", "--context", "1", "-o", "-") == 0
|
||||
out = capsys.readouterr()
|
||||
assert "inc2" in out.err
|
||||
assert json.loads(out.out) == {"users": {}, "counter": 3}
|
||||
|
||||
# Nothing printed: the dump falls back to the range-end state.
|
||||
assert _run_cli(path, "--grep", "zzz", "-o", "-") == 0
|
||||
out = capsys.readouterr()
|
||||
assert json.loads(out.out) == {"users": {}, "counter": 3}
|
||||
Reference in new issue
Block a user