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:
parent
45f1013308
commit
dc67f86511
2 changed files with 57 additions and 0 deletions
|
|
@ -94,12 +94,24 @@ RULE_SIZE_SPLIT = "rule:size-split"
|
||||||
#: declared a chapter there, by numbering it in an ascending run its own
|
#: declared a chapter there, by numbering it in an ascending run its own
|
||||||
#: outline sustains.
|
#: outline sustains.
|
||||||
RULE_OUTLINE = "rule:outline"
|
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_NAMES = (
|
||||||
RULE_HEADING,
|
RULE_HEADING,
|
||||||
RULE_TABLE_BLOCK,
|
RULE_TABLE_BLOCK,
|
||||||
RULE_POPPLER_SIZE_AND_BOLD,
|
RULE_POPPLER_SIZE_AND_BOLD,
|
||||||
RULE_SIZE_SPLIT,
|
RULE_SIZE_SPLIT,
|
||||||
RULE_OUTLINE,
|
RULE_OUTLINE,
|
||||||
|
RULE_TABLE_GRID,
|
||||||
)
|
)
|
||||||
|
|
||||||
#: How many characters of context each side of a quote anchor carries. Enough
|
#: 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*$")
|
_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*$")
|
_NUMBERED = re.compile(r"^(?P<number>\d+(?:\.\d+)+)\s+(?P<title>\S.*?)\s*$")
|
||||||
_TABLE_ROW = re.compile(r"^\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
|
# Arm D's grammar. Integer-only BY CONSTRUCTION: `\s+` after the optional
|
||||||
# separator is what keeps `1.1 Brannkonsept` out, because `_NUMBERED` requires
|
# 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
|
# a dot and this requires whitespace, so no line can match both. No exclusion
|
||||||
|
|
|
||||||
|
|
@ -1251,3 +1251,30 @@ def test_the_default_artifact_over_a_grid_table_matches_its_committed_golden(
|
||||||
assert out.read_bytes() == golden.read_bytes(), (
|
assert out.read_bytes() == golden.read_bytes(), (
|
||||||
"the default artifact over a grid table diverges from its committed golden 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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue