fix(docs): move pandoc metadata out of index.md frontmatter
index.md is both the man-page LANDING section and a Starlight content page. Astro errors on any frontmatter key outside the fixed man/site/manTitle/helpKeywords schema, so folding the source fish-config.md pandoc header (title/section/header/date/author) onto index.md under a fifth "pandoc" key would break the docs-site build the moment it's scaffolded. Relocate that block to docs/manual/_pandoc.yml: a plain, fence-free YAML file that Astro content collections ignore (leading underscore). split-manual.py now extracts it as raw text (no yaml.safe_load/ safe_dump round-trip) so build-manual.py's --concat re-emits it byte-for-byte instead of merely "verbatim" in comment only. Also guard build_concat against a manual/ tree with no _pandoc.yml/index.md, removing the unconditional index.md parse that previously raised FileNotFoundError on such a tree. Regenerated docs/manual/ via split-manual.py; concat output remains byte-identical to the pre-migration docs/fish-config.md.
This commit is contained in:
+9
-11
@@ -11,8 +11,6 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
import manualtools as mt
|
import manualtools as mt
|
||||||
|
|
||||||
DOCS = Path(__file__).parent
|
DOCS = Path(__file__).parent
|
||||||
@@ -25,17 +23,17 @@ def build_concat(root: Path) -> str:
|
|||||||
Each file contributes `# {manTitle or title}` at a level matching its
|
Each file contributes `# {manTitle or title}` at a level matching its
|
||||||
depth, and its body headings are demoted by the same amount.
|
depth, and its body headings are demoted by the same amount.
|
||||||
|
|
||||||
The root `index.md` (the LANDING page) may carry a `pandoc` key in its
|
`root / "_pandoc.yml"` (if present) holds the original document's
|
||||||
frontmatter — the original document's pandoc metadata block
|
pandoc metadata block (title/section/header/date/author) as raw text,
|
||||||
(title/section/header/date/author). If present, it is re-emitted
|
with no frontmatter fences and no Astro-visible frontmatter key. When
|
||||||
verbatim as the leading `---`-fenced block, ahead of every heading.
|
present, its contents are re-emitted byte-for-byte as the leading
|
||||||
|
`---`-fenced block, ahead of every heading.
|
||||||
"""
|
"""
|
||||||
chunks: list[str] = []
|
chunks: list[str] = []
|
||||||
index_fm, _ = mt.parse(root / "index.md")
|
pandoc_path = root / "_pandoc.yml"
|
||||||
pandoc_meta = index_fm.get("pandoc")
|
if pandoc_path.exists():
|
||||||
if pandoc_meta:
|
raw = pandoc_path.read_text().rstrip("\n")
|
||||||
header = yaml.safe_dump(pandoc_meta, sort_keys=False, allow_unicode=True).rstrip()
|
chunks.append(f"---\n{raw}\n---")
|
||||||
chunks.append(f"---\n{header}\n---")
|
|
||||||
for path, depth in mt.walk(root):
|
for path, depth in mt.walk(root):
|
||||||
fm, body = mt.parse(path)
|
fm, body = mt.parse(path)
|
||||||
if not fm.get("man", True):
|
if not fm.get("man", True):
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
title: FISH-CONFIG
|
||||||
|
section: 7
|
||||||
|
header: Fish Shell Configuration User Manual
|
||||||
|
date: June 2026
|
||||||
|
author: Rootiest
|
||||||
@@ -4,12 +4,6 @@ description: Reference manual for the rootiest fish configuration.
|
|||||||
manTitle: DESCRIPTION
|
manTitle: DESCRIPTION
|
||||||
sidebar:
|
sidebar:
|
||||||
order: 3
|
order: 3
|
||||||
pandoc:
|
|
||||||
title: FISH-CONFIG
|
|
||||||
section: 7
|
|
||||||
header: Fish Shell Configuration User Manual
|
|
||||||
date: June 2026
|
|
||||||
author: Rootiest
|
|
||||||
helpKeywords:
|
helpKeywords:
|
||||||
- description
|
- description
|
||||||
- autopair
|
- autopair
|
||||||
|
|||||||
+29
-6
@@ -50,6 +50,23 @@ def load_keywords() -> dict[str, list[str]]:
|
|||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
def extract_raw_frontmatter(path: Path) -> str | None:
|
||||||
|
"""Return the raw text between a file's opening `---` fences, or None.
|
||||||
|
|
||||||
|
Unlike `mt.parse`, this does not round-trip the block through
|
||||||
|
`yaml.safe_load`/`yaml.safe_dump` — it hands back the exact original
|
||||||
|
bytes (minus the fence lines themselves) so a later verbatim re-emit
|
||||||
|
doesn't have to worry about quoting-style or scalar-coercion drift.
|
||||||
|
"""
|
||||||
|
text = path.read_text()
|
||||||
|
if not text.startswith("---\n"):
|
||||||
|
return None
|
||||||
|
end = text.find("\n---\n", 4)
|
||||||
|
if end == -1:
|
||||||
|
return None
|
||||||
|
return text[4:end]
|
||||||
|
|
||||||
|
|
||||||
def _trim_blank_lines(text: str) -> str:
|
def _trim_blank_lines(text: str) -> str:
|
||||||
"""Drop leading/trailing blank lines without touching interior indentation.
|
"""Drop leading/trailing blank lines without touching interior indentation.
|
||||||
|
|
||||||
@@ -91,10 +108,16 @@ def main() -> int:
|
|||||||
|
|
||||||
keywords = load_keywords()
|
keywords = load_keywords()
|
||||||
# SRC starts with a pandoc metadata block (title/section/header/date/
|
# SRC starts with a pandoc metadata block (title/section/header/date/
|
||||||
# author) consumed by the man-page build. mt.parse() peels it off so it
|
# author) consumed by the man-page build. It has no purpose as an Astro
|
||||||
# isn't silently dropped; it gets stashed on the LANDING (index.md) page
|
# page's frontmatter (index.md is also a website content page, and
|
||||||
# under "pandoc" and re-emitted verbatim by build-manual.py --concat.
|
# Starlight errors on any custom frontmatter key outside the fixed
|
||||||
pandoc_meta, src_body = mt.parse(SRC)
|
# man/site/manTitle/helpKeywords schema), so it is extracted as raw text
|
||||||
|
# and written to docs/manual/_pandoc.yml instead of being folded into
|
||||||
|
# index.md's frontmatter. The leading underscore keeps Astro content
|
||||||
|
# collections from treating it as a page. build-manual.py --concat
|
||||||
|
# reads it back and re-emits it byte-for-byte.
|
||||||
|
pandoc_raw = extract_raw_frontmatter(SRC)
|
||||||
|
_, src_body = mt.parse(SRC)
|
||||||
sections = split_h1(src_body)
|
sections = split_h1(src_body)
|
||||||
order = 0
|
order = 0
|
||||||
# `position` tracks each heading's place in the *original* document,
|
# `position` tracks each heading's place in the *original* document,
|
||||||
@@ -127,11 +150,11 @@ def main() -> int:
|
|||||||
"manTitle": heading,
|
"manTitle": heading,
|
||||||
"sidebar": {"order": position},
|
"sidebar": {"order": position},
|
||||||
}
|
}
|
||||||
if pandoc_meta:
|
|
||||||
fm["pandoc"] = pandoc_meta
|
|
||||||
if kw:
|
if kw:
|
||||||
fm["helpKeywords"] = kw
|
fm["helpKeywords"] = kw
|
||||||
(OUT / "index.md").write_text(mt.serialize(fm, body))
|
(OUT / "index.md").write_text(mt.serialize(fm, body))
|
||||||
|
if pandoc_raw is not None:
|
||||||
|
(OUT / "_pandoc.yml").write_text(pandoc_raw.rstrip("\n") + "\n")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
order += 1
|
order += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user