"""Baut ``docs/updatebericht.md`` — den Bericht zur jeweils aktuellen Fassung. Warum es diesen Bericht gibt ---------------------------- ``CHANGELOG.md`` fuehrt die ganze Geschichte, und der Etikettentext im Git fasst eine Fassung fuer Anwender zusammen. Beides erreicht aber niemanden, der die Anwendung nur installiert hat: Der Changelog liegt im Quelltextarchiv, das Etikett im Git. Dieser Bericht ist das Blatt, das man jemandem zusammen mit der Installationsdatei in die Hand gibt. Warum er erzeugt und nicht geschrieben wird ------------------------------------------- Weil er sonst driftet. Genau das ist in diesem Projekt zweimal passiert - die Sperrdatei stand zwei Fassungen lang still, und die Anzahl der Paketpruefungen wanderte durch neun Dokumentstellen. Der Mittelteil dieses Berichts stammt deshalb **Wort fuer Wort** aus dem Abschnitt der Fassung in ``CHANGELOG.md``; die Fassungsnummer kommt aus ``app/package.json``. Von Hand geschrieben ist nur der Rahmen, und der aendert sich zwischen Fassungen nicht. ``app/tests/dokumentation.test.ts`` haelt fest, dass der Bericht zur Fassung passt und den Changelog-Abschnitt unveraendert enthaelt. Aufruf ------ python tools/updatebericht.py python tools/updatebericht.py --pruefen # meldet nur, ob er aktuell ist """ from __future__ import annotations import io import json import re import sys from pathlib import Path WURZEL = Path(__file__).resolve().parent.parent PAKET = WURZEL / 'app' / 'package.json' CHANGELOG = WURZEL / 'CHANGELOG.md' ZIEL = WURZEL / 'docs' / 'updatebericht.md' #: Ueberschrift eines Fassungsabschnitts: "## [0.23.0] — 2026-08-29". #: Der Gedankenstrich ist ein Halbgeviertstrich, kein Bindestrich. FASSUNGSKOPF = re.compile(r'^## \[(?P[^\]]+)\](?:\s*—\s*(?P\S+))?\s*$') def fassung() -> str: return json.loads(PAKET.read_text(encoding='utf-8'))['version'] def abschnitt(nummer: str) -> tuple[str, list[str]]: """Der Changelog-Abschnitt einer Fassung: Datum und Zeilen ohne Ueberschrift.""" zeilen = CHANGELOG.read_text(encoding='utf-8').split('\n') start = None datum = '' for i, zeile in enumerate(zeilen): treffer = FASSUNGSKOPF.match(zeile) if treffer is not None and treffer.group('nummer') == nummer: start = i datum = treffer.group('datum') or '' break if start is None: raise SystemExit(f'ABBRUCH: CHANGELOG.md hat keinen Abschnitt „## [{nummer}]“.') ende = len(zeilen) for i in range(start + 1, len(zeilen)): # Hinter der aeltesten Fassung steht ein HTML-Kommentar, der zu keiner # gehoert. Ohne diese Bedingung liefe der letzte Abschnitt bis zum # Dateiende und zoege ihn mit hinein. Heute trifft das nichts - erzeugt # wird immer die Fassung aus der package.json, und die ist nie die # aelteste. Die Bedingung steht hier, damit das so bleibt. if FASSUNGSKOPF.match(zeilen[i]) or zeilen[i].startswith('