"""Gedächtnis der Inhaltsprüfungen: was einmal beurteilt wurde, bleibt beurteilt. Ein Urteil über eine Codestelle kostet sechs Anfragen und fällt bei Grenzfällen mal so, mal so. Genau daran scheiterte die Wiederholbarkeit: derselbe Stand lieferte in Runde 1 einen Befund, in Runde 2 keinen, in Runde 3 wieder einen. Gespeichert wird deshalb das Urteil, nicht nur das Ergebnis. Der nächste Lauf fragt nur noch, was er nicht schon weiß. Ein Eintrag gilt weiter, solange seine Voraussetzung nachweisbar dasteht: - ein Beleg, solange die zitierte Zeile noch in der Datei steht - eine Fehlanzeige, solange sich am Quelltext nichts geändert hat Fällt die Voraussetzung weg, fällt der Eintrag weg und wird neu gefragt. Damit bleibt gleicher Stand gleiches Ergebnis, und geänderter Stand wird wirklich neu beurteilt. Die Datei liegt im Projektordner. Sie wandert beim Umbenennen mit und verschwindet beim Löschen — ohne Zutun, weil beides den Ordner bewegt. Wer von vorn anfangen will, löscht sie; der nächste Lauf fragt dann wieder alles. """ import hashlib import json import projekte DATEI = ".pruefung.json" # Ein Fach je Frage. Getrennt, weil sie unterschiedlich ungültig werden. FAECHER = ( "stil", # Prüfstelle -> ihr Stilurteil (Prüfung 1) "belege", # Funktion -> Codestelle, die sie umsetzt (Prüfung 2) "ohne_code", # Funktion -> kein Code gefunden (Prüfung 2) "paare", # Funktionspaar -> Dublette oder verschieden (Prüfung 2) ) def _pfad(projekt: str): return projekte.wurzel(projekt) / DATEI def lade(projekt: str) -> dict: """Der gespeicherte Stand. Kaputte oder fehlende Datei heißt: nichts gemerkt.""" try: daten = json.loads(_pfad(projekt).read_text()) except (OSError, ValueError): daten = {} return {fach: dict(daten.get(fach) or {}) for fach in FAECHER} def sichere(projekt: str, stand: dict): """Sortiert und eingerückt — die Datei soll sich lesen und vergleichen lassen. Erst daneben schreiben, dann umbenennen. Ein Absturz mitten im Schreiben ließe sonst eine halbe Datei zurück, und das Gedächtnis eines ganzen Laufs wäre weg — teuer bezahlt und nicht wiederherstellbar. """ text = json.dumps({fach: stand.get(fach) or {} for fach in FAECHER}, indent=1, ensure_ascii=False, sort_keys=True) ziel = _pfad(projekt) neben = ziel.with_suffix(".json.neu") neben.write_text(text + "\n") neben.replace(ziel) def abdruck(*teile: str) -> str: """Kurzer Fingerabdruck über den Stand, der ein Urteil trägt.""" roh = json.dumps([t or "" for t in teile], ensure_ascii=False).encode() return hashlib.sha1(roh).hexdigest()[:16] # Schlüssel sind JSON-Listen. Ein Trennzeichen wäre einfacher, aber Kartennamen und # Funktionstitel dürfen alles enthalten — und ein Schlüssel, der sich nicht wieder # zerlegen lässt, ist keiner. def schluessel(*teile: str) -> str: return json.dumps(list(teile), ensure_ascii=False) def paarschluessel(a: str, b: str) -> str: return json.dumps(sorted((a, b)), ensure_ascii=False) def teile(wert: str) -> list: try: gelesen = json.loads(wert) except ValueError: return [] return gelesen if isinstance(gelesen, list) else [] def behalte(fach: dict, gueltig: set) -> dict: """Einträge zu verschwundenen Stellen wegräumen — sonst wächst die Datei ewig.""" return {k: v for k, v in fach.items() if k in gueltig}