site.py
site.py — documate --html: the same docs, rendered as a static site.
Overview
site.py — documate --html: the same docs, rendered as a static site.
The second consumer of the model/render seam: docs.build_model builds one Model, and
this module renders it as HTML the way docs.render renders it as markdown — same
structure, same docstring prose, so the site can never say something the committed
pages don't. It adds two things the markdown tier can't carry: an overview and an
Architecture page as real, navigable HTML, and client-side search / theming / diagrams.
The output is a build artifact, not a third doc tier: it lands in site_dir
(gitignored, like the graph), is regenerated wholesale by documate --html, and
is NOT gated by documate --check — the committed markdown stays the single source the
gate protects. Host it like any static site (GitHub Pages, python -m http.server).
A .nojekyll marker ships alongside so GitHub Pages serves the files verbatim, and
every link is relative so the site works under a user.github.io/repo/ subpath.
Self-contained: one stylesheet, one script, no build tooling, no framework. The only
network fetch is Mermaid from a CDN to draw the diagrams client-side; offline, the
flowchart text stays readable in its <pre>. Output via ui, logic stdlib only.
depends on src/documate/core.py, src/documate/docs.py, src/documate/ui.py · used by src/documate/cli.py
API
F_inline(text: str) -> str
Escape a prose line for HTML, keeping backtick spans as <code>.
_doxygen, _md_inline, _overview, _page, _proseF_doxygen(text: str) -> str | None
A Doxygen-marked doc (@brief/@param/@return lines, what --rewrite
emits for C) as structured HTML — the brief as the lead paragraph, the
contract as a definition list — or None when the text carries no markers
(the plain-prose path renders it). Continuation lines fold into the tag
above them; unmarked lines stay ordinary paragraphs.
_prose · calls _inlineF_prose(text: str) -> str
Docstring text -> HTML blocks: a Doxygen-marked doc renders structured
(_doxygen); otherwise blank-line-separated paragraphs, with chunks whose
every line is indented (the docstring convention for tables/diagrams/examples, and
what survives ast.get_docstring's dedent) kept verbatim in a <pre>.
_architecture, _page · calls _doxygen, _inlineCclass Guide
One authored page picked up for the site: its nav identity + markdown source.
_guidesF_md_inline(text: str) -> str
_inline plus the guide-markdown spans: **bold**, *em* and [link](url).
Bold substitutes first so the em pass only ever sees single asterisks.
_markdown, flush · calls _inlineF_markdown(text: str) -> str
Authored-guide markdown -> HTML: the subset guides actually use — headings,
paragraphs, fenced code (``mermaid fences become live diagrams), flat lists,
inline code/bold/links, and standalone image lines (consecutive ones share a
figure; a -light./-dark. pair renders as one theme-following image).
Anchor comments (and any other HTML comment) vanish: they're for check`, not
for readers. Not a full markdown engine on purpose; the committed .md stays
the canonical rendering.
_guide, _overview · calls _md_inline, cls, flushFflush() -> None
Close the open paragraph, if any.
_markdown · calls _md_inlineFcls(src: str) -> str
The theme class for one image of a light/dark pair.
_markdownF_guide_rank(g: Guide) -> int
The guide's priority bucket — index into _GUIDE_RANKS, 3 when nothing hits.
_feats, _overviewF_guides(ctx: Context) -> list[Guide]
Every authored page under docs_dir — any *.md without the generated stamp, the same rule the anchor scanner uses — so the site carries the hand-written why alongside the generated what. Ordered for a reader (setup first, process last), stable within a bucket, and the sidebar/search follow the same order.
run · calls GuideF_remote_base(ctx: Context) -> str | None
The https base of the origin remote (git@/ssh/https forms normalized) —
where guide links that point outside the docs tree land, as blob URLs.
None without a usable remote; the caller then treats such links as dead.
_resolve_links · calls runF_page_hrefs(model: Model, guides: list[Guide]) -> dict[str, str]
{docs-relative .md path: site .html file} for everything the site renders — guides, the two headline pages, and each subsystem page under both committed layouts (flat and grouped), so a guide's link works whichever one is on disk.
_resolve_linksF_resolve_links(ctx: Context, model: Model, guides: list[Guide]) -> tuple[list[str], dict[str, Path]]
Rewrite every relative link in the authored guides to its real site target — a sibling .md becomes its .html page, a repo file becomes the remote's blob URL at default_base, a standalone image line points at a flat copy the caller ships into site_dir — and return (dead, assets): a line per link or image that resolves to nothing (the caller fails the build: a shipped dead link is doc rot, the exact thing the tool exists to stop) and {site filename: source file} for every image the site needs. Scheme-carrying links (http, mailto) and bare #anchors pass through; fenced code blocks are left untouched.
run · calls _page_hrefs, _remote_baseFswap(m: re.Match, _g: Guide=g, _at: str=at) -> str
One matched markdown link, rewritten to its site target — or kept as written, recording it dead when nothing resolves.
F_mermaid(kind: str, edges, classes: dict[str, str] | None=None, clusters: dict[str, list[str]] | None=None, caption: str | None=None, tall: bool=False, open_max: bool=False) -> str
A client-rendered flowchart: the mermaid text itself is the offline fallback.
classes/clusters mark nodes and directory boxes without colors — nav.js
injects the matching theme-aware classDef lines at render time. tall lets
the shell grow toward the viewport, open_max makes the figure present itself
full-screen on first load (nav.js honors it on wide screens only), and
caption renders as the figcaption carrying the interaction hints.
_architecture, _overview, _pageF_map_marks(edges: list[tuple[str, str]]) -> tuple[dict[str, str], dict[str, list[str]], str]
(classes, clusters, caption) for a module map, nodes keyed by stem (the
map's node label). Spanning several directories, each directory becomes a
labeled cluster box and one hue class (g0, g1, … in first-seen order,
box classed h0, h1, … to match) — the map then reads as subsystems.
A single-directory map colors by graph role instead: gentry for modules
nothing imports (where reading starts), gleaf for modules importing
nothing else on the map.
_architecture, _overviewF_groups(pages: list[Page]) -> list[tuple[str, list[list[str]]]]
Pages bucketed by directory, order preserved: [(dir, [[slug, filename], …]), …]. The tree renders one collapsible group per directory.
_nav_js · calls _nav_labelsF_search_index(model: Model, guides: list[Guide]) -> list[list[str]]
Everything the palette can jump to: [kind, name, context, href]. Modules and
the two headline pages, plus every documented symbol at its page.html#name.
_nav_jsF_crumb(*parts: str) -> str
A breadcrumb: last part bold, joined by faint slashes.
_architecture, _guide, _overview, _pageF_layout(model: Model, title: str, active: str, crumb: str, body: str, body_class: str='', hero: str='', desc: str='') -> str
Wrap a page body in the shared shell: sidebar (brand + coverage, nav filled by
nav.js), sticky top bar (breadcrumb + search + theme), an optional full-width hero
band, the reading column, and the search palette. active (the page's slug) marks
its nav link; everything else is one shared nav.js and style.css, so a page's size
never grows with the page count. desc feeds the description/OpenGraph meta, so
a shared link unfurls as the page's own summary instead of bare markup.
_architecture, _guide, _overview, _pageF_links(mods) -> str
Chip links to sibling subsystem pages, monospace like everywhere else.
_architecture, _pageF_featured(guides) -> Guide | None
The guide to headline on the landing page — the first getting-started/install page. None -> the landing page stays a plain docs index (nothing to inline).
_overviewF_split_intro(text: str) -> tuple[str, str]
A featured guide's markdown -> (lede, rest): drop the leading # title, take the
first paragraph as the hero lede, hand back the remainder for the Getting started
section — so the opening line isn't printed twice.
_overviewF_plain(text: str) -> str
Markdown spans flattened to plain text — for one-line descriptions.
_guide_desc, _overview, _repo_ledeF_clip(text: str, limit: int) -> str
Text cut at a word boundary near limit, with an ellipsis when clipped.
_feats, _guide_desc, _overview, _repo_ledeF_repo_lede(root) -> str
The repo README's opening paragraph, as the landing-page lede when no getting-started guide provides one. Headings, badges, images, HTML, lists and fenced code are skipped; empty when nothing usable is found.
run · calls _clip, _plainF_guide_desc(text: str) -> str
A guide's first plain paragraph, clipped to one row-description line.
Headings, anchors, lists, tables and fences are skipped — a guide that opens
with ## Abstract describes itself by the prose after it, not the heading.
_guide, _overview · calls _clip, _plainF_shell_block(root) -> list[str]
The README's first shell-fenced block, as terminal lines for the landing hero — the project's own quick start, never invented copy. Empty when the README carries no such block.
runF_rows(items) -> str
An Apple-docs style topic list: linked name + one-line description per row.
items yields (href, name, description-html-or-empty, mono?) tuples.
_overviewF_feats(model: Model, guides) -> str
The explore-card grid under the hero: the fixed destinations (architecture, reference) plus the repo's hardware and troubleshooting guides when it has them. Card copy is the guide's own title — nothing invented.
_overview · calls _clip, _guide_rankF_overview(model: Model, guides=(), lede: str='', cmds: list[str]=()) -> str
index.html — the landing page: a split product hero (name, lede, copyable quick-start command, and the README's own setup session in a terminal window), explore cards, the Getting started guide inlined when the repo ships one, then the guides and the per-directory reference as described topic rows — a front page a newcomer can read, not a wall of file paths.
render · calls _clip, _crumb, _feats, _featured, _guide_desc, _guide_rank, _inline, _layoutF_architecture(model: Model) -> str
architecture.html — docs/ARCHITECTURE.md as a site page: every subsystem in reading order (entry points first), each linking into its per-module page.
render · calls _crumb, _layout, _links, _map_marks, _mermaid, _proseF_guide(model: Model, g: Guide) -> str
One authored page: its title in the hero band, its markdown below (minus the title line the band already carries).
render · calls _crumb, _guide_desc, _layout, _markdownF_page(model: Model, p: Page) -> str
One subsystem page: hero band (name, summary, path), Overview prose, edge chips, flow diagram, then the per-symbol API with kind badges.
render · calls _crumb, _inline, _layout, _links, _mermaid, _proseFrender(model: Model, guides: list[Guide]=(), lede: str='', cmds: list[str]=()) -> dict[str, str]
Model (+ authored guides) -> {filename: html/css/js}. Deterministic, flat:
index (overview) + architecture + one page per subsystem + one per guide + the
shared stylesheet and script — the whole site, ready for any static host.
lede is the landing-page fallback tagline and cmds its hero terminal
session, both mined from the repo README.
run · calls _architecture, _guide, _nav_js, _overview, _pageFrun(ctx: Context) -> int
Write the site under site_dir, pruning orphaned pages of ours (same contract as
docs.run: a renamed source file must not leave its old page behind). Drops a
.nojekyll so GitHub Pages serves the files verbatim.
_remote_base · calls _guides, _repo_lede, _resolve_links, _shell_block, render