feat(propose): name the grid-rule grammar Arm E measures

Two constants, no behaviour. `RULE_TABLE_GRID = "rule:table-grid"` joins
`RULE_NAMES`, and `_GRID_RULE` sits beside `_TABLE_ROW` as the grammar of a
pandoc grid-table rule line.

The rule is the author's, not upstream's, and the constant says so: `grep -c -i
"arm" docs/2026-09-02-k3-k4-k5-metode.md` is 0, so the definition was written
for order 20260907T075834Z-18584396-from-.claude. Its axis is a third one --
Arm C names SIZE, Arm D names what the DOCUMENT declared, and this names what
the CONVERTER emitted.

The character class `[-=:+]` is measured, not guessed. Across the three
grid-bearing documents of the K2 corpus, 38 of 38 lines whose stripped form
starts with `+` match this pattern, and those four characters are the complete
set occurring on them. The `:` is pandoc's column-alignment marker and is load
bearing: a first pass with `[-=+]` matched 37 of 38, and through that single
miss read one document as having two tables where it has one. The `\s*` on
both ends mirrors `_TABLE_ROW` because the loop iterates
`splitlines(keepends=True)` -- every line carries its `\n`, and an indented
rule line is a real shape.

Tests first: 2 red, then green. 1233 -> 1235.
ruff check: exit 0. ruff format --check: exit 0. mypy --strict src/: 17 files,
Success. pytest -q: exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-07 10:47:36 +02:00
commit dc67f86511
2 changed files with 57 additions and 0 deletions

View file

@ -94,12 +94,24 @@ RULE_SIZE_SPLIT = "rule:size-split"
#: declared a chapter there, by numbering it in an ascending run its own
#: outline sustains.
RULE_OUTLINE = "rule:outline"
#: Arm E only. Like Arm C and Arm D it is NOT one of Topic 2's ported rules and
#: NOT defined upstream -- `docs/2026-09-02-k3-k4-k5-metode.md` contains no
#: occurrence of the word "arm" at all -- so this definition was written for
#: order 20260907T075834Z-18584396-from-.claude and is reported as the author's.
#: Its axis is a third one. Arm C names SIZE and Arm D names what the DOCUMENT
#: declared; this names what the CONVERTER emitted: a table block that was
#: joined across a grid-table rule line. Emitted ALONGSIDE `rule:table-block`,
#: which is still what opened the span, and only on a block that was ACTUALLY
#: joined -- never on one whose span merely happens to contain a rule line, so
#: a single-row grid table stays byte-identical to Arm D.
RULE_TABLE_GRID = "rule:table-grid"
RULE_NAMES = (
RULE_HEADING,
RULE_TABLE_BLOCK,
RULE_POPPLER_SIZE_AND_BOLD,
RULE_SIZE_SPLIT,
RULE_OUTLINE,
RULE_TABLE_GRID,
)
#: How many characters of context each side of a quote anchor carries. Enough
@ -166,6 +178,24 @@ STOP_WORDS = frozenset(
_ATX = re.compile(r"^(?P<hashes>#{1,6})\s+(?P<title>\S.*?)\s*$")
_NUMBERED = re.compile(r"^(?P<number>\d+(?:\.\d+)+)\s+(?P<title>\S.*?)\s*$")
_TABLE_ROW = re.compile(r"^\s*\|.*\|\s*$")
# Arm E's grammar: a pandoc GRID-table rule line. The converter separates a grid
# table's rows with `+---+---+`, and its header from its body with `+===+===+`.
# Neither matches `_TABLE_ROW`, so `in_table` is reset between every pair of rows
# and ONE table becomes one candidate per row group. Measured on the K2 corpus:
# three documents carry grid tables, and they account for 33 of the 709 entries
# Arm D proposes.
#
# The character class is measured rather than guessed. Across those three
# documents, 38 of 38 lines whose stripped form starts with `+` match this
# pattern, and `+`, `-`, `:`, `=` is the COMPLETE set of characters occurring on
# them. The `:` is pandoc's column-alignment marker, and it is not decoration: a
# first pass with `[-=+]` matched 37 of 38 and, through that one miss, read one
# document as having two tables where it has one.
#
# `\s*` on both ends mirrors `_TABLE_ROW` rather than tightening on it, because
# the loop iterates `splitlines(keepends=True)` -- every line carries its `\n`,
# and an indented rule line is a real shape that must still be admitted.
_GRID_RULE = re.compile(r"^\s*\+[-=:+]+\+\s*$")
# Arm D's grammar. Integer-only BY CONSTRUCTION: `\s+` after the optional
# separator is what keeps `1.1 Brannkonsept` out, because `_NUMBERED` requires
# a dot and this requires whitespace, so no line can match both. No exclusion

View file

@ -1251,3 +1251,30 @@ def test_the_default_artifact_over_a_grid_table_matches_its_committed_golden(
assert out.read_bytes() == golden.read_bytes(), (
"the default artifact over a grid table diverges from its committed golden bytes"
)
def test_the_grid_rule_name_is_registered() -> None:
"""A rule an operator cannot find in `RULE_NAMES` is an unnameable rule."""
assert okf_propose_segments.RULE_TABLE_GRID == "rule:table-grid"
assert okf_propose_segments.RULE_TABLE_GRID in okf_propose_segments.RULE_NAMES
def test_the_grid_rule_grammar_is_the_class_the_corpus_declared() -> None:
"""`[-=:+]`, and the `\\s*` tolerance, are both measured rather than guessed.
Measured on the three grid-bearing documents of the K2 corpus: 38 of 38
lines whose stripped form starts with `+` match this pattern, and the
complete character set on those lines is `+`, `-`, `:`, `=`. The `:` is
pandoc's column-alignment marker; a first pass with the class `[-=+]`
returned 37 and mis-read one document as having two tables instead of one.
Every input carries its trailing newline, because `find_candidates`
iterates `splitlines(keepends=True)` and that is the string the shipped
loop actually sees. A stripped-literal test would be green while the rule
never fired on an indented or trailing-space rule line.
"""
grid = okf_propose_segments._GRID_RULE
for line in ("+---+---+\n", "+===+===+\n", "+:--+--:+\n", "+---+\n", " +---+---+\n"):
assert grid.match(line) is not None, line
for line in ("+\n", "++\n", "|---|---|\n", "+--- +---+\n", "---+---\n", "+abc+\n"):
assert grid.match(line) is None, line