96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
"""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}
|