This commit is contained in:
team3
2026-07-29 15:19:41 +02:00
parent 2e86d682eb
commit 5d42b4cfc5
155 changed files with 14482 additions and 1736 deletions

View File

@@ -29,8 +29,21 @@ BADGE = ("kern", "optional")
# „funktionen" gehört der Inhaltsprüfung, hat aber dieselbe Gestalt.
ZEILENFELDER = ("regeln", "testen", "funktionen")
# Ein Funktionstitel nennt eine Handlung. Ohne Verb am Ende steht dort ein Bereich
# („Galerie im Modal") oder ein Zustand („Preis aktualisiert") — beides beschreibt
# nichts, was man für sich testen könnte. Deutsche Infinitive enden auf -en, -ln,
# -rn; wie Füllwörter und Wortgrenzen ist auch das an die Sprache gebunden.
_INFINITIV = re.compile(r"(?:e[lr]?n|ln|rn)$", re.I)
# Eine Handlung steht als Infinitiv am Anfang oder am Ende der Zeile („Bestätigen im
# Modal", „Text ändern") oder beginnt mit einer Taste. Alles andere ist ein Ergebnis.
# Nur Anfang und Ende, nicht irgendwo: deutsche Mehrzahl endet auch auf -en.
_TASTE = re.compile(r"^(?:LMB|RMB|MMB|Klick|Doppelklick|Enter|Escape|Strg|Tab)\b", re.I)
SATZENDE = ".;"
_KLAMMER = re.compile(r"[()/]")
# Eine Vorlage ist kein Inhalt. Das Modell schlug `<testschritt>` als Testschritt
# vor — formal richtig, gemeint war „hier fehlt noch etwas".
_LUECKE = re.compile(r"<[^<>]*>|\.\.\.|\bTODO\b|\bXXX\b", re.I)
_STRICH = re.compile(r"^[-*]\s*")
@@ -68,9 +81,15 @@ def _umschreiben(bereich: str, feld: str, zeile: str) -> list[str]:
gruende = []
if _KLAMMER.search(zeile):
gruende.append("Klammer oder Schrägstrich")
if _LUECKE.search(zeile):
gruende.append("Platzhalter statt Inhalt")
grenze = WORTGRENZEN.get((bereich, feld))
if grenze and len(zeile.split()) > grenze:
gruende.append(f"Mehr als {grenze} Wörter")
if bereich == "funktion" and feld == "titel":
woerter = zeile.split()
if woerter and not _INFINITIV.search(woerter[-1]):
gruende.append("Kein Verb im Infinitiv am Ende")
if feld == "ziel":
saetze = [s.strip() for s in re.split(r"[.!?]+", zeile) if s.strip()]
if len(saetze) > ZIEL_SAETZE:
@@ -80,6 +99,30 @@ def _umschreiben(bereich: str, feld: str, zeile: str) -> list[str]:
return gruende
def _handlung(zeile: str) -> bool:
woerter = zeile.split()
if not woerter:
return True
return bool(_TASTE.match(zeile) or _INFINITIV.search(woerter[-1])
or _INFINITIV.search(woerter[0]))
def _zweiter_ablauf(zeilen: list[str]) -> str:
"""Die Zeile, ab der ein zweiter Durchlauf beginnt — sonst leer.
Ein Ablauf ist: handeln, handeln, Ergebnis. Steht nach dem Ergebnis wieder eine
Handlung, sind es zwei Abläufe — und damit zwei Funktionen. Genau so entstand
„Bildergalerie bedienen" mit blättern, Miniatur wählen und Großansicht in einem.
"""
ergebnis = False
for zeile in zeilen:
if ergebnis and _handlung(zeile):
return zeile
if not _handlung(zeile):
ergebnis = True
return ""
def _zeile(bereich: str, feld: str, zeile: str) -> list[dict]:
"""Höchstens ein Befund je Zeile. Zwei Vorschläge für dieselbe Zeile wären nicht
unabhängig — der zweite fände seine Stelle nach dem ersten nicht mehr."""
@@ -94,13 +137,28 @@ def _zeile(bereich: str, feld: str, zeile: str) -> list[dict]:
def befunde(bereich: str, felder: dict) -> list[dict]:
"""Alle messbaren Verstöße einer Prüfstelle, in Reihenfolge der Felder."""
raus = []
# Ein bloßer Titel beschreibt nichts — und lässt sich über alles stülpen. Der
# Prüfer hielt damit einen Druckknopf für „Modal mit Tastatur steuern".
# Nur wenn die ganze Funktion vorliegt: bei einem einzelnen Feld sagt das Fehlen
# der anderen nichts.
inhaltsfelder = ("ziel", "regeln", "testen")
if bereich == "funktion" and all(f in felder for f in inhaltsfelder) \
and not any((felder.get(f) or "").strip() for f in inhaltsfelder):
raus.append(_problem("testen", "", "Funktion ohne Ziel, Regeln und Testen"))
return raus
for feld, text in felder.items():
if feld == "badge":
if text and text not in BADGE:
raus.append(_problem(feld, text, "Weder kern noch optional", "kern"))
continue
for zeile in _zeilen(feld, text or ""):
zeilen = _zeilen(feld, text or "")
for zeile in zeilen:
raus += _zeile(bereich, feld, zeile)
if feld == "testen" and not raus:
# Erst prüfen, wenn die Zeilen für sich in Ordnung sind: sonst hängen
# zwei Vorschläge am selben Feld und der zweite findet seine Stelle nicht.
if bruch := _zweiter_ablauf(zeilen):
raus.append(_problem(feld, bruch, "Zweiter Ablauf in einer Funktion"))
return raus