feat(docs-site): syntax-highlight examples and restyle the site
The manual is authored man-page style: every synopsis, example, option table, and description sits in one 4-space-indented block. On the site that renders as a single unhighlighted grey slab, because an indented block declares no language. Split each block into its paragraphs at site-build time and classify them: synopsis and shell examples become ```fish fences, descriptions become real prose, and column-aligned reference tables keep their indentation. 175 blocks now highlight; the 412 lines of genuine tables are left alone. The transform is site-only. docs/manual/** keeps the indented form the pandoc man-page pipeline and config-help depend on, and a test enforces that no fence is ever written back to the SSOT. Also: - Point Expressive Code at the bundled Catppuccin Mocha/Latte themes so code blocks match the palette in catppuccin.css. - Build the functions sidebar group explicitly. `autogenerate` labelled it with the raw directory slug and republished the directory index as a child of the group it already titled, producing the duplicate "Functions Reference" row. - Skip `Synopsis:` lines when deriving card descriptions; they restated the calling convention the card already shows as its title. - Widen the palette: tinted heading levels, inline code, links, card hover accents, aside accents, and table headers. Fixes a bug where _split_entries stripped the leading indentation of an entry's first line, detaching `Synopsis:` from the block it opens.
This commit is contained in:
@@ -213,6 +213,86 @@ def test_site_build_produces_function_pages():
|
||||
assert "title" in fm, f"{page.name} has no title"
|
||||
|
||||
|
||||
def test_prettify_splits_an_entry_block():
|
||||
"""A man-page-style entry becomes fenced code, prose, and a table."""
|
||||
import build_manual
|
||||
|
||||
body = "\n".join(
|
||||
[
|
||||
" Synopsis: rm [-e | args...]",
|
||||
" Safe rm wrapper routing to trash:",
|
||||
"",
|
||||
" (no args) List current trash contents",
|
||||
" -e/--empty Empty the trash",
|
||||
"",
|
||||
" Falls back to /usr/bin/rm when trash is unavailable.",
|
||||
"",
|
||||
" rm file.txt # moves to trash",
|
||||
" rm -e # empty trash",
|
||||
]
|
||||
)
|
||||
out = build_manual.prettify(body, "rm")
|
||||
|
||||
assert "```fish\nrm [-e | args...]\n```" in out, "synopsis was not fenced as fish"
|
||||
assert "```fish\nrm file.txt" in out, "examples were not fenced as fish"
|
||||
assert out.count("```") == 4, f"expected exactly two fences, got:\n{out}"
|
||||
assert "\nSafe rm wrapper routing to trash:" in out, "description stayed indented"
|
||||
assert (
|
||||
"\n (no args) List current trash contents" in out
|
||||
), "option table lost its indentation"
|
||||
assert (
|
||||
"\nFalls back to /usr/bin/rm when trash is unavailable." in out
|
||||
), "trailing prose stayed indented"
|
||||
|
||||
|
||||
def test_prettify_leaves_reference_tables_alone():
|
||||
"""Column-aligned blocks are data, not shell, and must not be fenced."""
|
||||
import build_manual
|
||||
|
||||
table = " XDG_CONFIG_HOME ~/.config\n XDG_CACHE_HOME ~/.cache"
|
||||
assert "```" not in build_manual.prettify(table), "a reference table got fenced"
|
||||
|
||||
binds = " n / nv / neovim nvim\n e edit"
|
||||
assert "```" not in build_manual.prettify(binds), "an abbreviation table got fenced"
|
||||
|
||||
shell = " set -U __fish_user_dots_path /path/to/dots"
|
||||
assert "```fish" in build_manual.prettify(shell), "a shell block was not fenced"
|
||||
|
||||
|
||||
def test_prettify_is_site_only():
|
||||
"""The SSOT keeps the indented form the man-page pipeline depends on."""
|
||||
import build_manual
|
||||
|
||||
manual = Path(__file__).parent / "manual"
|
||||
for path in manual.rglob("*.md"):
|
||||
assert "```" not in path.read_text(), (
|
||||
f"{path.name} contains a fence: prettify must run at site-build "
|
||||
"time, never be written back to the SSOT"
|
||||
)
|
||||
|
||||
|
||||
def test_sidebar_has_no_duplicate_functions_entry():
|
||||
"""The functions group must not also list itself as one of its children."""
|
||||
import build_manual
|
||||
|
||||
docs = Path(__file__).parent
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
sidebar = build_manual.build_site(docs / "manual", Path(d))
|
||||
|
||||
groups = [e for e in sidebar if "items" in e]
|
||||
assert len(groups) == 1, f"expected one sidebar group, got {len(groups)}"
|
||||
group = groups[0]
|
||||
labels = [item["label"] for item in group["items"]]
|
||||
assert group["label"] not in labels[1:], (
|
||||
f"'{group['label']}' is repeated inside its own group: {labels}"
|
||||
)
|
||||
assert labels[0] == "Overview", f"group should lead with Overview, got {labels[0]}"
|
||||
assert not any(
|
||||
"autogenerate" in item for item in group["items"]
|
||||
), "categories should be listed explicitly, not autogenerated from slugs"
|
||||
assert len(labels) == 15, f"expected Overview + 14 categories, got {len(labels)}"
|
||||
|
||||
|
||||
TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user