#!/usr/bin/env python3
"""
Generate an HTML landing page (dashboard) for demo-epb.
Scans the repo for Word documents, reports, code, tests, architecture, and
writes build/index.html with clickable links.
Run after `make test && make coverage && make docs && make test-report &&
python3 tools/traceability.py publish docs/traceability &&
python3 tools/render_plantuml.py`.
Output:
build/index.html — standalone, open in a browser
Use in the release bundle:
- Lives at demo-epb-vX.Y.Z/index.html
- Links to all other bundle contents using relative paths
"""
from __future__ import annotations
import datetime
import html
import os
import re
import subprocess
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
BUILD = REPO / "build"
def count_files(pattern: str, base: Path = REPO) -> int:
return sum(1 for _ in base.glob(pattern))
def git_info() -> tuple[str, str]:
try:
sha = subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"], cwd=str(REPO),
text=True).strip()
except Exception:
sha = "?"
try:
tag = subprocess.check_output(
["git", "describe", "--tags", "--abbrev=0"], cwd=str(REPO),
text=True, stderr=subprocess.DEVNULL).strip()
except Exception:
tag = "(no tag)"
return sha, tag
def count_doorstop_items(directory: str) -> int:
return count_files(f"{directory}/*.md")
def count_tests() -> int:
total = 0
for f in (REPO / "tests" / "unit").glob("test_*.c"):
text = f.read_text()
total += len(re.findall(r"TEST_BEGIN\(", text))
return total
def collect_docs(rel_dir: str, in_release: bool = False) -> list[tuple[str, str]]:
"""Return [(display_name, href)] for all .docx in a directory."""
out = []
d = REPO / rel_dir
if not d.exists():
return out
for f in sorted(d.glob("*.docx")):
href = os.path.relpath(f, REPO)
out.append((f.stem, href))
return out
def kpi_card(label: str, value: str, sub: str = "", color: str = "#1f3864") -> str:
return f"""
{html.escape(value)}
{html.escape(label)}
{html.escape(sub)}
"""
def doc_section(title: str, docs: list[tuple[str, str]], description: str = "") -> str:
if not docs:
items = "
"
def main() -> int:
BUILD.mkdir(parents=True, exist_ok=True)
sha, tag = git_info()
now = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds")
# Counts
n_sg = count_doorstop_items("safety/sg")
n_sys = count_doorstop_items("reqs/sys")
n_swe = count_doorstop_items("reqs/swe")
n_sa = count_doorstop_items("arch/sys")
n_swa = count_doorstop_items("arch/swe")
n_tests = count_tests()
n_impl = sum(1 for f in (REPO / "src").glob("*.c"))
n_stubs = sum(1 for f in (REPO / "src" / "stubs").glob("*.h"))
# Word docs
plans = collect_docs("docs/plans")
safety = collect_docs("docs/safety")
manuals = collect_docs("docs/manuals")
reviews = collect_docs("docs/reviews")
ncs = collect_docs("docs/non-conformities")
misra_r = collect_docs("misra/records")
# Reports — links target BUNDLE paths (relative to index.html in deploy).
# The CI pipeline copies artifacts to exactly these paths, so the links
# always resolve in the deployed bundle.
rep_paths = {
"coverage": "coverage/index.html",
"test": "reports/test-report.html",
"api": "api-doc/index.html",
"trace": "traceability/index.html",
"cppcheck": "reports/cppcheck-report.xml",
}
html_body = f"""
demo-epb {html.escape(tag)} — Project Dashboard
demo-epb — Electric Parking Brake
Version {html.escape(tag)} · Commit {html.escape(sha)} · Generated {html.escape(now)}
Demo project: Complete demonstration of the
slohmaier Dev Process.
This software is intentionally not production code; it is a showcase of the engineering method.