fix(materialize): render a title identically in frontmatter and index
ingest-spec §4 makes the extraction title BOTH the `title` frontmatter and
the index link label — one value, two sites. The frontmatter renderer
single-lined its values while the index label was written raw, so a title
carrying a whitespace run rendered two different ways from one input:
title: Energiforbruk 2024
- [Energiforbruk 2024](ingest-orders.md)
Single-lining is now one named rule applied at every site that renders the
title, instead of a detail of frontmatter rendering that the index path did
not share.
No golden bytes change: no title in this repo, portfolio-optimiser or
portfolio-optimiser-claude carries a whitespace run.
Open question routed to commons, the spec owner: §5 mandates collapsing
whitespace runs in `source_query` only, while `title` is separately required
to be single-line and is already validated as such at manifest load. So
collapsing the title may be over-application — the alternative reading is
that both sites should emit it verbatim. Either reading keeps the two sites
in agreement, which is what this commit establishes; which of the two is
correct is a spec question, not ours.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdVgowYC4LARgvNdNMiuvz
This commit is contained in:
parent
64e3187639
commit
a7e1bc88d8
2 changed files with 49 additions and 6 deletions
|
|
@ -49,11 +49,18 @@ class IngestResult:
|
|||
stamp: str
|
||||
|
||||
|
||||
def _single_line(value: str) -> str:
|
||||
# §5: frontmatter values are single-lined — whitespace runs, including
|
||||
# newlines, collapse to one space — because the format is line-oriented
|
||||
# and parsing stops at the first `---` line. §4 makes the extraction
|
||||
# title BOTH the `title` frontmatter and the index link label, so the
|
||||
# same rule has to reach both sites or the one value renders two ways.
|
||||
return " ".join(value.split())
|
||||
|
||||
|
||||
def _render_frontmatter(frontmatter: dict[str, str]) -> str:
|
||||
# Line-oriented `key: value`; values are single-lined (whitespace runs,
|
||||
# including newlines, collapse to one space — §5) because parsing is
|
||||
# line-oriented and stops at the first `---` line. Insertion order.
|
||||
return "\n".join(f"{key}: {' '.join(value.split())}" for key, value in frontmatter.items())
|
||||
# Line-oriented `key: value`, insertion order.
|
||||
return "\n".join(f"{key}: {_single_line(value)}" for key, value in frontmatter.items())
|
||||
|
||||
|
||||
def _parse_frontmatter(path: Path) -> dict[str, str]:
|
||||
|
|
@ -141,7 +148,7 @@ def _link_in_index(bundle_dir: Path, target_name: str, label: str) -> None:
|
|||
if f"]({target_name})" in body:
|
||||
return
|
||||
prefix = body if body.endswith("\n") else body + "\n"
|
||||
index_path.write_bytes(f"{prefix}- [{label}]({target_name})\n".encode())
|
||||
index_path.write_bytes(f"{prefix}- [{_single_line(label)}]({target_name})\n".encode())
|
||||
|
||||
|
||||
def materialize_bundle(
|
||||
|
|
@ -265,7 +272,8 @@ def materialize_bundle(
|
|||
# bundle_summary as its body; links are appended in extraction order.
|
||||
index_path = bundle / _INDEX_NAME
|
||||
labels_by_target = {
|
||||
generated_filename(extraction.id): extraction.title for extraction in manifest.extractions
|
||||
generated_filename(extraction.id): _single_line(extraction.title)
|
||||
for extraction in manifest.extractions
|
||||
}
|
||||
if not index_path.is_file():
|
||||
_write_bytes(bundle, _INDEX_NAME, manifest.bundle_summary + "\n")
|
||||
|
|
|
|||
|
|
@ -124,6 +124,41 @@ def test_file_source_concept_file_exact_bytes(file_setup: tuple[Path, Path]) ->
|
|||
assert (bundle / "ingest-orders.md").read_bytes() == expected.encode("utf-8")
|
||||
|
||||
|
||||
def test_title_renders_identically_in_frontmatter_and_index(tmp_path: Path) -> None:
|
||||
# §4: the title "becomes the `title` frontmatter AND the index link
|
||||
# label" — one value, two sites, so the two MUST agree. The frontmatter
|
||||
# renderer single-lines its values; the index label was written raw, so
|
||||
# a title carrying a whitespace run diverged between the two.
|
||||
src = tmp_path / "src"
|
||||
manifest_path = write_manifest(
|
||||
src,
|
||||
file_manifest_data(
|
||||
[
|
||||
{
|
||||
"id": "orders",
|
||||
"title": "Energiforbruk 2024",
|
||||
"query": "orders.csv",
|
||||
"okf_type": "dataset",
|
||||
"max_rows": 100,
|
||||
}
|
||||
]
|
||||
),
|
||||
)
|
||||
(src / "data").mkdir()
|
||||
(src / "data" / "orders.csv").write_text("a,b\n1,x\n", encoding="utf-8", newline="")
|
||||
bundle = tmp_path / "bundle"
|
||||
|
||||
materialize_bundle(manifest_path, bundle, ingested_at=INGESTED_AT)
|
||||
|
||||
concept = (bundle / "ingest-orders.md").read_text(encoding="utf-8")
|
||||
index = (bundle / "index.md").read_text(encoding="utf-8")
|
||||
frontmatter_title = next(
|
||||
line.partition(":")[2].strip() for line in concept.splitlines() if line.startswith("title:")
|
||||
)
|
||||
index_label = index.partition("- [")[2].partition("]")[0]
|
||||
assert frontmatter_title == index_label
|
||||
|
||||
|
||||
def test_result_lists_written_concept_files(file_setup: tuple[Path, Path]) -> None:
|
||||
manifest_path, bundle = file_setup
|
||||
result = materialize_bundle(manifest_path, bundle, INGESTED_AT)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue