fix(profiles,materialize,structure,consume): a block sources sequence is decoded, not skipped
One grammar, four call sites. `read_block_mappings` moves out of `consume.read_sources` -- where it was written and measured -- into `profiles`, the module both the flat readers and `consume` already import, and the three copies of the line-oriented frontmatter grammar now decode a block sequence for the keys `STRUCTURED_BLOCK_KEYS` names. Two copies of a block grammar would be two answers to one question. The value TYPE was the real choice and it was measured, not argued. `parse_frontmatter` is public API (`okf.parse_frontmatter`) returning `dict[str, str]`, and a list of mappings is not a `str`. Widening the return type to `str | list[dict[str, str]]` costs 15 `mypy --strict` errors across four of the five modules that touch the reader, plus a signature every caller outside this repository would have to follow. Rendering the entries back into the flow form those same readers already round-trip costs 0. The rendering is a READING projection and says so: it is not a claim that the value is writable -- `yaml_flow_plain` still refuses a `?` and the guard still refuses a quote inside a flow mapping, which is why the producer writes block in the first place. `STRUCTURED_BLOCK_KEYS` is one key wide. `sources` is the key `read_sources` already knows how to read; a fixture in this tree carries a block `verified:` that still reads as an empty value, and a test pins that state so the next widening is a decision rather than a side effect. Nothing nested reaches the document's namespace: the entries land inside their own value, and the K3-20 substitution guarantee is asserted per reader copy. Three tests that pinned the old behaviour are rewritten to what is now true, none weakened on its other half: the block round trip in `test_multi_source_provenance` (the evidence behind `_render_sources`' reason 1), the v0.2 characterization (whose key-space assertion is the half that must never weaken), and K3-22's shipped-file known-positive, where the one difference is counted and pinned at 1. Suite 1807 passed / 1 skipped, rc 0, 94 s -- 1782/1 before plus 25 new. ruff clean, `mypy --strict` clean over 21 files, `uv.lock` untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
eb327bfd0c
commit
28f9a4b540
8 changed files with 211 additions and 65 deletions
|
|
@ -29,7 +29,14 @@ from .manifest import (
|
|||
generated_filename,
|
||||
load_manifest_bytes,
|
||||
)
|
||||
from .profiles import DEFAULT, BundleProfile, unquote_scalar, yaml_flow_plain
|
||||
from .profiles import (
|
||||
DEFAULT,
|
||||
STRUCTURED_BLOCK_KEYS,
|
||||
BundleProfile,
|
||||
block_mapping_value,
|
||||
unquote_scalar,
|
||||
yaml_flow_plain,
|
||||
)
|
||||
from .render import render_fenced_block, render_table
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
@ -121,7 +128,7 @@ def parse_frontmatter(path: Path) -> dict[str, str]:
|
|||
if not lines or lines[0].strip() != "---":
|
||||
return {}
|
||||
frontmatter: dict[str, str] = {}
|
||||
for line in lines[1:]:
|
||||
for position, line in enumerate(lines[1:], start=1):
|
||||
if line.strip() == "---":
|
||||
break
|
||||
# An INDENTED key belongs to the block above it, not to the document.
|
||||
|
|
@ -129,16 +136,27 @@ def parse_frontmatter(path: Path) -> dict[str, str]:
|
|||
# as the top-level keys and, arriving later, SUBSTITUTE for one of them
|
||||
# -- a `sources:` entry's own `title:` silently becoming the document's,
|
||||
# carrying `number` and `parent` with it. Skipping is deliberately not
|
||||
# parsing: the nested value is not read, only refused. The structured
|
||||
# reader is D1b.
|
||||
# parsing: the nested value is not read, only refused. That refusal is
|
||||
# unchanged, and `STRUCTURED_BLOCK_KEYS` does not weaken it: a decoded
|
||||
# block lands INSIDE its own value, never in this namespace. The
|
||||
# structured reader is still D1b.
|
||||
if line[:1] in (" ", "\t"):
|
||||
continue
|
||||
key, sep, value = line.partition(":")
|
||||
if sep:
|
||||
# A `"`-wrapped value is how the emitter writes a scalar a YAML
|
||||
# reader would refuse plain (K3-22); read back as that reader
|
||||
# would. A `'`-wrapped one is returned as it stands.
|
||||
frontmatter[key.strip()] = unquote_scalar(value.strip())
|
||||
# would. A `'`-wrapped one is returned as it stands. A named key
|
||||
# whose value is a block sequence is DECODED rather than left
|
||||
# empty (K3-24) -- an empty value is an address disappearing with
|
||||
# nothing raised.
|
||||
name, raw = key.strip(), value.strip()
|
||||
rendered = (
|
||||
block_mapping_value(lines, position)
|
||||
if not raw and name in STRUCTURED_BLOCK_KEYS
|
||||
else None
|
||||
)
|
||||
frontmatter[name] = unquote_scalar(raw) if rendered is None else rendered
|
||||
return frontmatter
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue