#!/usr/bin/env python3
"""
Erzeugt eine HTML-Startseite (Dashboard) fuer demo-epb.
Scant das Repo nach Word-Dokumenten, Reports, Code, Tests, Architektur, und
schreibt build/index.html mit klickbaren Links.
Run nach `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, oeffnen mit Browser
Verwendung im Release-Bundle:
- Liegt bei demo-epb-vX.Y.Z/index.html
- Verlinkt alle anderen Bundle-Inhalte relativ
"""
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")):
# In release bundle, paths are different; here we use relative-to-repo.
href = os.path.relpath(f, REPO)
# If running for in_release context, paths need adjustment, but for now
# we always use repo-relative.
out.append((f.stem, href))
return out
def status_for(path: Path) -> str:
if path.exists():
return "ok"
return "missing"
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 = "