"""mdlite - a deliberately small Markdown renderer (stdlib only).
Renders exactly the subset the lab manual uses: ATX headings (# ## ###),
paragraphs, **bold**, *italic*, `code`, fenced code blocks, unordered and
ordered lists, blockquotes, tables, horizontal rules, and [links](...).
Text is HTML-escaped first; code spans are protected from inline rules.
Why not a library: the cockpit is standard-library-only by law, and a
small renderer whose whole grammar fits on one screen is auditable in a
way a dependency is not. The test suite renders the real manual through
this and asserts no raw Markdown artifacts leak into the HTML.
"""
from __future__ import annotations
import html
import re
def _slug(text: str) -> str:
return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") or "section"
def _inline(text: str) -> str:
"""Inline rules over an already HTML-escaped string.
Code spans are stashed as placeholder tokens first, so bold/italic/link
rules can span across them (e.g. **never edits `policy.json` alone**)
while code content itself stays untouched by those rules."""
codes: list[str] = []
def stash(m: re.Match[str]) -> str:
codes.append(m.group(1))
return f"\x00{len(codes) - 1}\x00"
text = re.sub(r"`([^`]+)`", stash, text)
text = re.sub(r"\*\*([^*]+)\*\*", r"\1", text)
text = re.sub(r"(?\1", text)
text = re.sub(r"\[([^\]]+)\]\(([^)\s]+)\)", r'\1', text)
for idx, code in enumerate(codes):
text = text.replace(f"\x00{idx}\x00", f"{code}")
return text
def render(md: str) -> tuple[str, list[tuple[int, str, str]]]:
"""Render markdown -> (html, toc) where toc = [(level, text, anchor)]."""
lines = md.splitlines()
out: list[str] = []
toc: list[tuple[int, str, str]] = []
seen_slugs: dict[str, int] = {}
para: list[str] = []
i = 0
def flush_para() -> None:
if para:
out.append(f"
{_inline(html.escape(' '.join(para)))}
") para.clear() while i < len(lines): line = lines[i] stripped = line.strip() # fenced code block if stripped.startswith("```"): flush_para() i += 1 code: list[str] = [] while i < len(lines) and not lines[i].strip().startswith("```"): code.append(lines[i]) i += 1 i += 1 # closing fence out.append(f"{html.escape(chr(10).join(code))}")
continue
# heading
m = re.match(r"^(#{1,4})\s+(.*)$", stripped)
if m:
flush_para()
level = len(m.group(1))
text = m.group(2).strip()
base = _slug(re.sub(r"[*`\[\]()]", "", text))
n = seen_slugs.get(base, 0)
seen_slugs[base] = n + 1
anchor = base if n == 0 else f"{base}-{n}"
if level in (2, 3):
toc.append((level, re.sub(r"[*`]", "", text), anchor))
out.append(f"") continue # table if stripped.startswith("|") and i + 1 < len(lines) and \ re.match(r"^\|[\s:|-]+\|$", lines[i + 1].strip()): flush_para() def cells(row: str) -> list[str]: return [c.strip() for c in row.strip().strip("|").split("|")] head = cells(stripped) i += 2 rows: list[list[str]] = [] while i < len(lines) and lines[i].strip().startswith("|"): rows.append(cells(lines[i].strip())) i += 1 thead = "".join(f"" + _inline(html.escape(" ".join(q for q in quote if q))) + "