#!/usr/bin/env python3
"""Render the versioned Mermaid architecture diagrams.
Reads ``*.mmd`` sources from a directory (``.serena/diagrams`` by
default) and writes a single browsable HTML index. With ``--format
svg`` or ``--format png`` it shells out to ``mmdc`` from
``@mermaid-js/mermaid-cli`` instead.
Examples::
scripts/render-diagrams
scripts/render-diagrams --format svg
scripts/render-diagrams --diagram architecture-flow
"""
from __future__ import annotations
import argparse
import html
import shutil
import subprocess
import sys
from pathlib import Path
DEFAULT_SRC = Path(".serena/diagrams")
DEFAULT_MERMAID_VERSION = "11"
SOURCE_SUFFIXES = (".mmd", ".mermaid")
class RenderError(RuntimeError):
"""Raised when a diagram cannot be rendered."""
def discover_diagrams(src: Path, out: Path | None = None) -> list[Path]:
"""Return the diagram sources under ``src``, sorted by path.
``out``, when given, is skipped so a previous render is never read
back as a source.
"""
if not src.is_dir():
return []
ignored = out.resolve() if out is not None else None
found: list[Path] = []
for path in src.rglob("*"):
if not path.is_file() or path.suffix.lower() not in SOURCE_SUFFIXES:
continue
if ignored is not None and ignored in path.resolve().parents:
continue
found.append(path)
return sorted(found)
def filter_diagrams(diagrams: list[Path], names: list[str] | None) -> list[Path]:
"""Keep only the diagrams whose file stem is in ``names``."""
if not names:
return diagrams
wanted = set(names)
return [path for path in diagrams if path.stem in wanted]
def build_html(
diagrams: list[Path],
src: Path,
mermaid_version: str = DEFAULT_MERMAID_VERSION,
) -> str:
"""Build a self-contained HTML index embedding every diagram source."""
sections = []
for path in diagrams:
source = path.read_text(encoding="utf-8").strip()
name = path.relative_to(src).as_posix()
sections.append(
f'{html.escape(name)}
\n"
f'{html.escape(source)}\n'
f"
No diagrams found.
" return f"""