Files
planer2/backend/parser.py
2026-07-29 15:19:41 +02:00

101 lines
3.7 KiB
Python

"""Zerlegt den rohen Feature-Text in Sektionen — nur für die Anzeige.
Format: titel (eine Zeile), art, ziel, dann wiederholbare <!-- funktion -->-Blöcke;
<!-- regeln -->- und <!-- testen -->-Blöcke (je eine `- `-Zeile pro Eintrag) gehören zur
davorstehenden Funktion. Alt-Format wird weiter gelesen: <!-- regel -->-Blöcke (einer je
Regel) und <!-- flow --> (heute testen) werden zusammengeführt. Präambel, unbekannte
Marker und verwaiste Blöcke landen in `sonstiges` (wird nicht angezeigt, aber beim
Speichern erhalten).
Trennzeilen aus Bindestrichen (---) sind reine Optik und werden ignoriert.
"""
import re
MARKER = re.compile(r"^<!--\s*([a-zä-ü]+)\s*-->\s*$")
TRENNER = re.compile(r"^-{3,}\s*$")
# „vision" ist keine Einstufung, sondern eine eigene Kartenart: sie hält fest, wie
# es gedacht ist, und wird von keiner Prüfung angefasst. Unbekanntes ist „kern".
VISION = "vision"
ARTEN = ("kern", "optional", VISION)
# Eine Funktion ist entweder geliefert oder noch offen. „offen" heißt: die Karte
# beschreibt sie schon, den Code gibt es noch nicht — bestellt, nicht gebaut.
# Ohne diese Unterscheidung meldet die Vorwärtsprüfung jede geplante Funktion als
# Fehler und will sie streichen.
OFFEN = "offen"
GEBAUT = "gebaut"
STAENDE = (GEBAUT, OFFEN)
def _art(bloecke: list) -> str:
wert = bloecke[0].strip().lower() if bloecke else ""
return wert if wert in ARTEN else "kern"
def zerlege(roh: str) -> dict:
# Erst in Blöcke (schluessel, text) in Dateireihenfolge schneiden
bloecke = []
aktuell = None # None = Präambel vor dem ersten Marker
puffer = []
def ablegen():
text = "\n".join(z for z in puffer if not TRENNER.match(z)).strip()
if text:
bloecke.append((aktuell, text))
puffer.clear()
for zeile in roh.splitlines():
m = MARKER.match(zeile)
if m:
ablegen()
aktuell = m.group(1)
else:
puffer.append(zeile)
ablegen()
titel = []
ziel = []
regeln = []
funktionen = []
sonstiges = []
art = []
for schluessel, text in bloecke:
if schluessel == "titel":
titel.append(text)
elif schluessel == "art":
art.append(text)
elif schluessel == "ziel":
# vor der ersten Funktion: Karten-Ziel, danach: Ziel der Funktion
(funktionen[-1]["ziel"] if funktionen else ziel).append(text)
elif schluessel == "funktion":
funktionen.append({"text": text, "ziel": [], "regeln": [], "testen": [],
"stand": GEBAUT})
elif schluessel == "stand" and funktionen:
wert = text.strip().lower()
funktionen[-1]["stand"] = wert if wert in STAENDE else GEBAUT
elif schluessel in ("regel", "regeln"):
# vor der ersten Funktion: Karten-Regeln, danach: Regeln der Funktion
(funktionen[-1]["regeln"] if funktionen else regeln).append(text)
elif schluessel in ("flow", "testen") and funktionen:
funktionen[-1]["testen"].append(text)
else: # Präambel, unbekannter Marker, regeln/testen ohne funktion
sonstiges.append(text)
for f in funktionen:
f["ziel"] = "\n\n".join(f["ziel"])
f["regeln"] = "\n".join(f["regeln"])
f["testen"] = "\n".join(f["testen"])
# Titel ist eine Zeile: erste nicht-leere
titel_zeilen = [z.strip() for t in titel for z in t.splitlines() if z.strip()]
return {
"titel": titel_zeilen[0] if titel_zeilen else "",
"art": _art(art),
"ziel": "\n\n".join(ziel),
"regeln": "\n".join(regeln),
"funktionen": funktionen,
"sonstiges": "\n\n".join(sonstiges),
}