feat: Safety Manager + Traceability + PlantUML in CI
Validate / build-and-test (push) Successful in 30s
Validate / build-and-test (push) Successful in 30s
- Implement Safety Manager (SWA-001, ASIL-D): Hill-Hold + Auto-Apply state machine, 13 unit tests - Update SWA-002 + SWA-001 link coverage so all SWE reqs are covered - New tool: tools/traceability.py — Markdown-frontmatter-basierter Traceability-Checker + HTML/JSON-Matrix-Generator (Doorstop-Format ohne Doorstop-Dependency) - New tool: tools/render_plantuml.py — extrahiert PlantUML-Bloecke aus arch/**.md und rendert via plantuml.com zu SVG - validate.yml: neue Steps Traceability-Check, Matrix-Publish, PlantUML- Render; uploaded als Gitea-Artefakte
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Rendert alle @startuml ... @enduml Bloecke aus arch/**/*.md als SVG.
|
||||
|
||||
Verwendet einen erreichbaren PlantUML-HTTP-Server (Default: www.plantuml.com).
|
||||
In CI kann die Server-URL ueber PLANTUML_SERVER ueberschrieben werden.
|
||||
|
||||
Output: docs/diagrams/<file>-<index>.svg
|
||||
|
||||
Run:
|
||||
python3 tools/render_plantuml.py
|
||||
PLANTUML_SERVER=http://plantuml-server:8080 python3 tools/render_plantuml.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
SERVER = os.environ.get("PLANTUML_SERVER", "https://www.plantuml.com/plantuml")
|
||||
|
||||
# Map standard base64 to PlantUML's base64 alphabet
|
||||
_B64 = (
|
||||
"0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"-_"
|
||||
)
|
||||
|
||||
|
||||
def _encode_3bytes(b1: int, b2: int, b3: int) -> str:
|
||||
c1 = b1 >> 2
|
||||
c2 = ((b1 & 0x3) << 4) | (b2 >> 4)
|
||||
c3 = ((b2 & 0xF) << 2) | (b3 >> 6)
|
||||
c4 = b3 & 0x3F
|
||||
return _B64[c1] + _B64[c2] + _B64[c3] + _B64[c4]
|
||||
|
||||
|
||||
def plantuml_encode(text: str) -> str:
|
||||
"""Encode PlantUML source to its URL-safe representation."""
|
||||
compressed = zlib.compress(text.encode("utf-8"), 9)
|
||||
# Strip zlib header (2 bytes) and Adler32 trailer (4 bytes)
|
||||
raw = compressed[2:-4]
|
||||
out = ""
|
||||
i = 0
|
||||
while i < len(raw):
|
||||
b1 = raw[i]
|
||||
b2 = raw[i + 1] if i + 1 < len(raw) else 0
|
||||
b3 = raw[i + 2] if i + 2 < len(raw) else 0
|
||||
out += _encode_3bytes(b1, b2, b3)
|
||||
i += 3
|
||||
return out
|
||||
|
||||
|
||||
BLOCK_RE = re.compile(r"```plantuml\s*\n(@startuml.*?@enduml)\s*\n```",
|
||||
re.DOTALL)
|
||||
|
||||
|
||||
def extract_blocks(md: Path) -> list[str]:
|
||||
return BLOCK_RE.findall(md.read_text())
|
||||
|
||||
|
||||
def render_one(src: str, out_path: Path) -> None:
|
||||
encoded = plantuml_encode(src)
|
||||
url = f"{SERVER.rstrip('/')}/svg/{encoded}"
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "demo-epb/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
data = resp.read()
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_bytes(data)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
out_dir = REPO / "docs" / "diagrams"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
md_files = sorted((REPO / "arch").rglob("*.md"))
|
||||
if not md_files:
|
||||
print("Keine arch/**/*.md gefunden.")
|
||||
return 0
|
||||
|
||||
total = 0
|
||||
for md in md_files:
|
||||
rel = md.relative_to(REPO)
|
||||
blocks = extract_blocks(md)
|
||||
if not blocks:
|
||||
continue
|
||||
for i, block in enumerate(blocks, start=1):
|
||||
stem = md.stem
|
||||
target = out_dir / f"{stem}-{i}.svg"
|
||||
try:
|
||||
render_one(block, target)
|
||||
print(f" {rel} block {i} -> {target.relative_to(REPO)}")
|
||||
total += 1
|
||||
except Exception as e:
|
||||
print(f" WARN: {rel} block {i} render failed: {e}")
|
||||
print(f"\nDone: {total} Diagrams rendered to {out_dir.relative_to(REPO)}/")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user