Files
computer-playbook/roles/web-app-docs/files/python/infinito_docs/conf.py
T
kevinveenbirkenbachandClaude Opus 5 d206f2593e feat(web-app-docs): render mermaid fences as diagrams
Every role README carries a ```mermaid flowchart and the documentation
showed it as source. A post-transform now replaces those literal blocks with
<pre class="mermaid"> in html builds, which the runtime picks up; every other
builder keeps the fence as it was.

sphinxcontrib-mermaid injects an inline init script, which this deployment's
CSP refuses, so the runtime and its initialisation are two linked files
instead: mermaid-init.js calls mermaid.initialize with securityLevel strict
and follows the reader's colour scheme. The diagram source is html-escaped
before it is embedded, or an & or < in a node label would break the page.

The runtime is fetched at image build time through ADD --checksum, so a
changed upstream file fails the build rather than shipping. Version and
digest are not ARG defaults, which test_arg_without_default forbids; they
travel meta/services.yml -> vars/main.yml -> compose.yml.j2 the way
DOCS_IMAGE and DOCS_VERSION already do, and an update block registers the
pin with the npm version watcher instead of exempting it.

Verified by building a page with sphinx: the diagram arrives as
<pre class="mermaid"> with its source escaped, a bash fence stays a
highlighted block, and both scripts are linked. A missing mermaid.min.js does
not fail that build, so a local run without the image stays green.

scripts/lint/mermaid.sh keeps validating diagram syntax through mmdc, which
npm installs unpinned. It therefore checks against a newer mermaid than the
11.4.1 readers get, and a diagram using syntax from in between would pass the
lint and render blank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-26 01:02:30 +02:00

100 lines
3.0 KiB
Python

from __future__ import annotations
import logging
import os
import sys
from pathlib import Path
from docutils import nodes
from pygments.lexers.templates import DjangoLexer
from sphinx.domains.python import PythonDomain
from sphinx.highlighting import lexers
logging.basicConfig(
level=logging.DEBUG if {"-v", "--verbose"} & set(sys.argv) else logging.INFO
)
logger = logging.getLogger(__name__)
CONF_DIR = Path(__file__).resolve().parent
templates_path = [str(CONF_DIR / "templates")]
html_static_path = [str(CONF_DIR / "assets")]
project = "Infinito.Nexus - Cyber Master Infrastructure Solution"
copyright = "2025, Kevin Veen-Birkenbach" # noqa: A001
author = "Kevin Veen-Birkenbach"
lexers["jinja"] = DjangoLexer()
lexers["j2"] = DjangoLexer()
exclude_patterns = ["docs/build", "venv", "venv/**"]
html_theme = "sphinxawesome_theme"
html_sidebars = {
"**": ["logo.html", "versions.html", "languages.html", "structure.html"]
}
html_context = {"docs_version": os.environ.get("DOCS_VERSION", "")}
html_favicon = "assets/img/favicon.ico"
html_theme_options = {
"show_prev_next": False,
"logo_light": "assets/img/logo.png",
"logo_dark": "assets/img/logo.png",
}
source_suffix = {
".md": "markdown",
".rst": "restructuredtext",
".yml": "yaml",
".yaml": "yaml",
}
extensions = [
"myst_parser",
"infinito_docs.extensions.local.file_headings",
"infinito_docs.extensions.local.subfolders",
"infinito_docs.extensions.roles_overview",
"infinito_docs.extensions.markdown_include",
"infinito_docs.extensions.parallel_postprocess",
"infinito_docs.extensions.untranslated.yaml",
"infinito_docs.extensions.untranslated.markup",
"infinito_docs.extensions.yaml_source",
"infinito_docs.extensions.directory_index",
"infinito_docs.extensions.mermaid",
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
]
autosummary_generate = True
locale_dirs = ["locale"]
gettext_compact = "docs"
gettext_location = False
myst_enable_extensions = ["colon_fence"]
myst_heading_anchors = 6
myst_url_schemes = ("http", "https", "mailto", "ftp", "tel")
def replace_assets_in_doctree(app, doctree, docname):
for node in doctree.findall(nodes.image):
if "assets/" in node["uri"]:
node["uri"] = node["uri"].replace("assets/", "_static/")
logger.info("Replaced image URI in %s: %s", docname, node["uri"])
for node in doctree.findall(nodes.raw):
if node.get("format") == "html" and "assets/" in node.astext():
rewritten = node.astext().replace("assets/", "_static/")
node.children = [nodes.raw("", rewritten, format="html")]
logger.info("Replaced raw HTML assets in %s.", docname)
def setup(app):
app.connect("doctree-resolved", replace_assets_in_doctree)
python_domain = app.registry.domains.get(PythonDomain.name)
if python_domain is not None:
directive = python_domain.directives.get("currentmodule")
if directive is not None:
directive.optional_arguments = 10
return {"version": "1.0", "parallel_read_safe": True}