feat(docs): generate fish-config.md from the manual tree
Adds docs/build-manual.py (--concat) and a round-trip test in verify-manual.py that reproduces the pre-migration fish-config.md exactly from docs/manual/. Fixes found by the round-trip check, applied in split-manual.py and re-run to regenerate docs/manual/: - NAME/SYNOPSIS/TABLE OF CONTENTS had no sidebar.order, so they sorted after every numbered section instead of interleaving with DESCRIPTION in original document order. All manual pages now get sidebar.order from a single position counter matching source order. - split_h1/split_h2 used .strip() on section bodies, which also ate leading indentation on the first body line, corrupting the 4-space indented code blocks that open SYNOPSIS and TABLE OF CONTENTS. - The source's leading pandoc metadata block (title/section/header/ date/author) was discarded entirely by the splitter. It's now parsed off via manualtools.parse and stashed on index.md under a "pandoc" key; build-manual.py re-emits it verbatim ahead of the first heading.
This commit is contained in:
@@ -3,12 +3,27 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Verification checks for the docs/manual SSOT pipeline."""
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import manualtools as mt
|
||||
|
||||
# docs/build-manual.py follows this repo's hyphenated CLI-script naming
|
||||
# convention (matching split-manual.py, verify-manual.py), which means it
|
||||
# cannot satisfy a plain `import build_manual` on its own — Python's import
|
||||
# statement never treats a hyphen as an underscore. Load it explicitly under
|
||||
# the name the tests expect and register it in sys.modules; every later
|
||||
# `import build_manual` (including the one inside test_concat_roundtrips_
|
||||
# original below) then finds the cached module instead of touching the path
|
||||
# finder.
|
||||
_build_manual_path = Path(__file__).parent / "build-manual.py"
|
||||
_spec = importlib.util.spec_from_file_location("build_manual", _build_manual_path)
|
||||
_build_manual = importlib.util.module_from_spec(_spec)
|
||||
sys.modules["build_manual"] = _build_manual
|
||||
_spec.loader.exec_module(_build_manual)
|
||||
|
||||
|
||||
def test_parse_roundtrip():
|
||||
fm = {"title": "Git", "sidebar": {"order": 4}, "helpKeywords": ["git", "gi"]}
|
||||
@@ -104,6 +119,64 @@ def test_function_entries_promoted_to_h2():
|
||||
assert "\n## " in f"\n{body}", f"{path.name} has no H2 function entries"
|
||||
|
||||
|
||||
def _normalise(text: str) -> str:
|
||||
"""Collapse whitespace so only content differences survive."""
|
||||
lines = [ln.rstrip() for ln in text.strip().split("\n")]
|
||||
return "\n".join(ln for ln in lines if ln != "")
|
||||
|
||||
|
||||
def test_concat_roundtrips_original():
|
||||
"""The concat of manual/ must reproduce the original fish-config.md.
|
||||
|
||||
Prefers docs/fish-config.md.orig (a snapshot of the pre-migration file)
|
||||
when present. Once that snapshot is deleted post-migration,
|
||||
docs/fish-config.md IS the concat output regenerated in Step 5, so
|
||||
falling back to it turns this into an idempotency regression check
|
||||
instead of going red for a missing file.
|
||||
"""
|
||||
import build_manual
|
||||
|
||||
docs = Path(__file__).parent
|
||||
original = docs / "fish-config.md.orig"
|
||||
label = "original"
|
||||
if not original.exists():
|
||||
original = docs / "fish-config.md"
|
||||
label = "fish-config.md"
|
||||
got = _normalise(build_manual.build_concat(docs / "manual"))
|
||||
want = _normalise(original.read_text())
|
||||
if got != want:
|
||||
import difflib
|
||||
|
||||
diff = list(
|
||||
difflib.unified_diff(
|
||||
want.split("\n"), got.split("\n"), label, "concat", lineterm="", n=1
|
||||
)
|
||||
)[:40]
|
||||
raise AssertionError("concat differs from original:\n" + "\n".join(diff))
|
||||
|
||||
|
||||
def test_every_index_keyword_resolves():
|
||||
"""Every keyword in fish-config.index must match a heading in the concat."""
|
||||
import build_manual
|
||||
|
||||
docs = Path(__file__).parent
|
||||
index = docs / "fish-config.index"
|
||||
if not index.exists():
|
||||
print(" SKIP test_every_index_keyword_resolves (no index file)")
|
||||
return
|
||||
concat = build_manual.build_concat(docs / "manual")
|
||||
headings = {ln.strip() for ln in concat.split("\n") if ln.startswith("#")}
|
||||
missing = []
|
||||
for line in index.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
keyword, heading = line.split("=", 1)
|
||||
if heading.strip() not in headings:
|
||||
missing.append(f"{keyword.strip()} -> {heading.strip()}")
|
||||
assert not missing, "unresolvable index keywords:\n " + "\n ".join(missing)
|
||||
|
||||
|
||||
TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user