11 Datei(en) geändert. Erzeugt von .vault-tools/auto-push/werkzeuge_spiegel.py.
397 lines
21 KiB
Python
397 lines
21 KiB
Python
"""Wendet die Codebloecke der Taskdateien eines Plans in Wellenreihenfolge auf eine Kopie des Projekts an.
|
|
|
|
Koordinator-Werkzeug (entstanden 2026-09-17, Trading Plattform Teil 3b). Es tut etwas und beurteilt
|
|
nichts; das Urteil faellt danach mit Suite, Mutanten und welle_vergleich.py.
|
|
Geltungsbereich:
|
|
- Zieldatei ist der letzte Pfad in Backticks in einer Ueberschrift (## bis ####) oberhalb des Blocks, den
|
|
PFAD erkennt: mit Schraegstrich und Endung, ohne Schraegstrich nur mit py, ts, tsx, toml, md, css, json.
|
|
Erkennt PFAD in der Ueberschrift nichts, ist Ziel der letzte Token, der (ohne "./") genau so unter
|
|
"## Zieldateien" derselben Taskdatei steht (seit 2026-10-17): So wird eine Datei ohne Endung
|
|
(`host/logrotate/edge-traefik`, `Dockerfile`, `.gitignore`) planbar. Ein absoluter Eintrag zaehlt
|
|
relativ zum Ordner vor "docs/plans" im Pfad des Planordners; ohne diesen zaehlen nur relative Eintraege.
|
|
Ordner, Muster mit "*" und Pfade mit ".." unter Zieldateien sind kein Ziel.
|
|
- Befund statt stillem Ziel (seit 2026-10-17): Nennt eine Ueberschrift kein Ziel, aber einen Token, der
|
|
nach einer Datei aussieht, ist jeder schreibende Block danach ein Befund, auch unter Ueberschriften
|
|
ohne Pfad, bis eine Ueberschrift mit Ziel kommt; nichts wird geschrieben. Nach einer Datei sehen aus:
|
|
ein relativer Pfad mit Schraegstrich und ein Name ohne Schraegstrich, der direkt in der Kopie als Datei
|
|
liegt oder Dateiname eines Eintrags unter Zieldateien ist. Vorher ging der Block still ins vorige Ziel.
|
|
Kein Befund sind Ordner (hinten "/"), absolute Pfade (vorn "/"), Token mit Leerraum und Bezeichner
|
|
ohne gleichnamige Datei: Bestehende Plaene nennen sie in Ueberschriften als Kontext und meinen das
|
|
vorige Ziel. Nicht gesehen: eine neue Datei ohne Endung, ohne Schraegstrich und ohne Eintrag unter
|
|
Zieldateien, ein absoluter Pfad ohne Endung und eine Ueberschrift, die neben einem PFAD-Treffer eine
|
|
Datei ohne Endung nennt (dann gilt der PFAD-Treffer). Gleiche Regel in plan_bloecke.py (dort mit
|
|
--projekt statt Kopie und Planordner); Regressionsnachweis: plan_ziel_treiber.py.
|
|
- Dateien, die "## Zieldateien" als "(gelöscht)" fuehrt, werden in der Kopie geloescht.
|
|
- Bloecke mit Einrueckung (Befehle in Listen) und Prosa-Vorgaben ("wird: ein neuer Abschnitt ...")
|
|
werden uebergangen: Ein Doku-Task mit frei zu formulierendem Text laesst sich nicht anwenden.
|
|
- Marke ist die letzte nicht leere Zeile vor dem Zaun:
|
|
"Aus:" -> merkt den Block als alten Text
|
|
"wird:" -> ersetzt den gemerkten Text (muss genau einmal vorkommen)
|
|
enthaelt "Die ganze Datei ist neu" -> schreibt die Datei
|
|
beginnt mit "Häng diesen Block ans Ende" -> haengt an
|
|
Alles andere (Befehle, erwartete Ausgaben, Prosa-"wird: ...") wird uebergangen und gezaehlt.
|
|
- Zaeune aus n Backticks schliessen erst mit n Backticks, in einer Zeile ohne weiteren Text und hoechstens drei
|
|
Spalten tiefer als der Oeffner; tiefer ist es Inhalt. Eine Zeile mit weiterem Backtick hinter dem Zaun oeffnet
|
|
nichts, sie ist Inline-Code. Beides nach CommonMark 0.31.2, Abschnitt Fenced code blocks, nachgelesen 2026-11-01
|
|
("may be preceded by up to three spaces of indentation", "Info strings for backtick code blocks cannot contain
|
|
backticks"). Abweichend davon oeffnet ein Zaun bei jeder Einrueckung, und Tilde-Zaeune kennt das Werkzeug nicht.
|
|
- Zaunpruefung (seit 2026-11-02, Anlass in zaunbefunde()): Vor dem ersten Schreibvorgang liest zaunbefunde() alle
|
|
uebergebenen Taskdateien. Ein Befund in einer davon, und der ganze Lauf schreibt und loescht nichts, auch nicht
|
|
die heilen Dateien davor. Befund sind, mit n als Zaunlaenge des Blocks:
|
|
1. eine Zeile im Block, die auf mindestens n Backticks endet, ohne zu schliessen (klebender Zaun, auch ein
|
|
Zaun mehr als drei Spalten tiefer als der Oeffner),
|
|
2. eine Zeile im Block, die mit mindestens n Backticks beginnt, ohne zu schliessen (Schlusszaun fehlt,
|
|
Aussenzaun zu kurz),
|
|
3. ein Block, der bis zum Dateiende nicht schliesst,
|
|
4. eine Taskdatei, die sich nicht lesen laesst.
|
|
Herkunft: 1 und 3 stehen im Auftrag zur Haertung. 2 ist Projektvorgabe, denselben stillen Schaden richtet ein
|
|
fehlender Zaun an. 4 ist Projektvorgabe nach AI/Regelwerk/pruefwerkzeuge.md, vorher stuerzte der Lauf mittendrin ab.
|
|
Bewusst entschieden: Ein Text im Block, etwa eine Zeichenkette in einem Test, die am Zeilenende auf n Backticks
|
|
endet oder mit ihnen beginnt, ist derselbe Befund. Von einem klebenden Zaun ist er nicht zu unterscheiden, und die
|
|
Folgen sind ungleich: Der Fehlalarm kostet einen laengeren Zaun um den Block, der uebersehene Zaun schreibt
|
|
Planprosa in die Zieldatei. Gemessen am 2026-11-02 ueber alle verschiedenen Taskdateien im Vault und in den
|
|
Jobordnern: 1 und 3 ohne Treffer, 2 acht Zeilen in drei alten Doku-Tasks, alle echt (Aussenzaun zu kurz, der
|
|
Block endete zu frueh).
|
|
Nicht entscheidbar ist ein nackter Zaun als eigene Zeile im Text, hoechstens drei Spalten tiefer als der Oeffner:
|
|
Er schliesst den Block. Geht die Zahl der Zaeune auf, wird der Rest Prosa, der Block endet dort, und es kommt
|
|
kein Befund (Treiberfall "Luecke:"). Text mit Zaeunen braucht deshalb immer einen laengeren Zaun um den Block.
|
|
Kein Befund: laengere Zaeune um Markdown-Vorlagen mit ##-Ueberschriften und kuerzeren Zaeunen darin, Inline-Code
|
|
und Zaunfolgen mitten in der Zeile oder in Anfuehrungszeichen, Prosa ausserhalb von Bloecken.
|
|
Nicht gesehen (Fangnetz, kein Nachweis): Ein nackter Zaun als Oeffner ist von einem Schlusszaun nicht zu
|
|
unterscheiden. Fehlt der Schlusszaun und kommt als naechstes ein nackter Zaun derselben oder groesserer Laenge,
|
|
schliesst der den Block, und die Rollen tauschen bis zum naechsten Oeffner mit Sprache (das meldet 2) oder bis
|
|
zum Dateiende (das meldet 3 am letzten Zaun, zwei solche Fehler heben sich auf). Ebenso bei einem Oeffner, der
|
|
fehlt, an der Zeile davor klebt oder einen Backtick in der Sprachangabe traegt, bei Zaeunen hinter Listenpunkt
|
|
oder Zitatzeichen und bei Backtick-Zaeunen in Tilde-Bloecken. Nachweis: plan_zaun_treiber.py. plan_bloecke.py
|
|
nutzt dieselben Funktionen.
|
|
- Geschrieben wird immer mit LF.
|
|
Aufruf: python plan_materialisiere.py <planordner> <kopie> <taskdatei> [<taskdatei> ...]
|
|
Exit 0 ohne Befund, 2 mit Befund.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path, PureWindowsPath
|
|
|
|
UEBERSCHRIFT = re.compile(r"^#{2,4} ")
|
|
PFAD = re.compile(r"`([^`\s]+/[^`\s]+\.[A-Za-z0-9]+|[^`\s/]+\.(?:py|ts|tsx|toml|md|css|json))`")
|
|
TOKEN = re.compile(r"`([^`\s]+)`")
|
|
# Eintrag unter "## Zieldateien", dieselbe Form wie in welle_vergleich.py
|
|
ZIEL = re.compile(r"^- `(.+?)`(?:\s*\((.*?)\))?")
|
|
|
|
|
|
def wurzeln(planordner: Path) -> list[PureWindowsPath]:
|
|
"""Der Projektordner vor "docs/plans" im Pfad des Planordners, wie uebergeben und aufgeloest."""
|
|
aus = []
|
|
for pfad in (planordner.absolute(), planordner.resolve()):
|
|
teile = pfad.parts
|
|
for i in range(len(teile) - 2, 0, -1):
|
|
if teile[i] == "docs" and teile[i + 1] == "plans":
|
|
aus.append(PureWindowsPath(*teile[:i]))
|
|
break
|
|
return aus
|
|
|
|
|
|
def zieldateien(zeilen: list[str], wurzeln: list[PureWindowsPath]) -> set[str]:
|
|
"""Relative Pfade unter "## Zieldateien"; ein absoluter zaehlt relativ zu einer der wurzeln.
|
|
|
|
Ordner (hinten "/"), Muster mit "*" und Pfade mit ".." sind kein Ziel.
|
|
"""
|
|
drin, menge = False, set()
|
|
for zeile in zeilen:
|
|
if zeile.startswith("## "):
|
|
drin = zeile.startswith("## Zieldateien")
|
|
continue
|
|
treffer = ZIEL.match(zeile) if drin else None
|
|
if not treffer or treffer.group(1).endswith(("/", "\\")) or "*" in treffer.group(1):
|
|
continue
|
|
pfad = PureWindowsPath(treffer.group(1))
|
|
if pfad.drive or pfad.root:
|
|
relativ = None
|
|
for wurzel in wurzeln:
|
|
try:
|
|
relativ = pfad.relative_to(wurzel)
|
|
break
|
|
except ValueError:
|
|
continue
|
|
if relativ is None:
|
|
continue
|
|
pfad = relativ
|
|
if ".." not in pfad.parts:
|
|
menge.add(pfad.as_posix())
|
|
return menge
|
|
|
|
|
|
def ist_datei(ordner: Path | None, name: str) -> bool:
|
|
"""Liegt `name` als Datei direkt im ordner? Nie fuer einen Namen mit Laufwerk oder Wurzel."""
|
|
if ordner is None or PureWindowsPath(name).drive or PureWindowsPath(name).root:
|
|
return False
|
|
try:
|
|
return (ordner / name).is_file()
|
|
except (OSError, ValueError):
|
|
return False
|
|
|
|
|
|
def ordne(zeile: str, geplant: set[str], ordner: Path | None) -> tuple[str | None, list[str]]:
|
|
"""Zieldatei einer Ueberschrift und die Token, die nach einer Datei aussehen, ohne erkannt zu sein.
|
|
|
|
Ziel ist der letzte PFAD-Treffer, ohne ihn der letzte Token, der (ohne "./") unter "## Zieldateien"
|
|
steht. Ohne Ziel gelten als offen: relative Pfade mit Schraegstrich (kein Ordner, nicht absolut) und
|
|
Namen ohne Schraegstrich, die direkt im ordner als Datei liegen oder Dateiname eines Eintrags sind.
|
|
Nachbesserung 2026-10-17 (Plankritik Paket R1 im Edge-Stack, Befunde 1 und 5): "Stelle 30 —
|
|
`host/logrotate/edge-traefik`" setzte kein Ziel, der Block "Die ganze Datei ist neu" ueberschrieb
|
|
`tests/systemd/test-units.sh` mit Exit 0; `Dockerfile` ging ebenso still ins vorige Ziel. `config/`
|
|
und `/proc` stehen in bestehenden Plaenen ueber Bloecken, die das vorige Ziel meinen.
|
|
"""
|
|
treffer = PFAD.findall(zeile)
|
|
if treffer:
|
|
return treffer[-1], []
|
|
token = TOKEN.findall(zeile)
|
|
ziele = [PureWindowsPath(t).as_posix() for t in token if PureWindowsPath(t).as_posix() in geplant]
|
|
if ziele:
|
|
return ziele[-1], []
|
|
namen = {eintrag.rsplit("/", 1)[-1] for eintrag in geplant}
|
|
offen = [t for t in token if ("/" in t and not t.startswith("/") and not t.endswith("/"))
|
|
or ("/" not in t and (t in namen or ist_datei(ordner, t)))]
|
|
return None, offen
|
|
|
|
|
|
def schreibt(marke: str) -> bool:
|
|
"""Marken, nach denen main() in die Zieldatei schreibt; bei einer neuen Marke hier mitziehen."""
|
|
return (marke == "wird:" or "Die ganze Datei ist neu" in marke or "vollständig ersetzt" in marke
|
|
or marke.startswith("Häng diesen Block ans Ende"))
|
|
|
|
|
|
def normal(text: str) -> str:
|
|
return text.replace("\r\n", "\n").replace("\r", "\n")
|
|
|
|
|
|
def zaun(zeile: str) -> int:
|
|
"""Laenge des Zauns, der in dieser Zeile einen Block oeffnet, sonst 0."""
|
|
kern = zeile.lstrip()
|
|
laenge = len(kern) - len(kern.lstrip("`"))
|
|
return laenge if laenge >= 3 and "`" not in kern[laenge:] else 0
|
|
|
|
|
|
def schliesst(zeile: str, laenge: int, einzug: int) -> bool:
|
|
"""Schliesst die Zeile einen Block mit Zaun der Laenge laenge und Einzug einzug?
|
|
|
|
Mindestens so viele Backticks, sonst nichts in der Zeile, hoechstens drei Spalten tiefer als der Oeffner.
|
|
Nachbesserung 2026-11-02 (Pruefung der Zaunpruefung): Vorher schloss jeder Einzug und "``` ```". Ein eingerueckter
|
|
Zaun in einer Zeichenkette kappte den Block mit 0 Befunden, und plan_bloecke.py hatte das bis dahin gesehen.
|
|
"""
|
|
kern = zeile.lstrip()
|
|
return kern.startswith("`" * laenge) and not kern.rstrip().strip("`") and len(zeile) - len(kern) - einzug <= 3
|
|
|
|
|
|
def zaunbefunde(taskdatei: Path) -> list[tuple[int, str]]:
|
|
"""(Zeile, Meldung) je Stelle, an der die Bloecke der Taskdatei nicht stimmen; leer heisst: bloecke() liest sie richtig.
|
|
|
|
Nachbesserung 2026-11-01 (Kette 050 der Trading Plattform, Auftrag 13): In task-1020 klebte der Zaun an `])`,
|
|
der Block lief bis in den naechsten Task, es kam nur "Aus-Block steht 0-mal", und ein Aus/wird-Paar ging ohne
|
|
Meldung verloren. Unter "Die ganze Datei ist neu" schrieb derselbe Fehler Planprosa mit 0 Befunden in die Datei.
|
|
Fuer die Meldung endet ein Block am klebenden Zaun, und ein Zaun am Zeilenanfang im Block beginnt einen neuen: So
|
|
steht jeder Fehler einmal da, und die Bloecke dahinter stimmen wieder. War es doch ein Text mit Zaunfolge, oeffnet
|
|
der echte Schlusszaun fuer diese Suche einen Block, und ein Folgebefund steht weiter hinten; der laengere Zaun um
|
|
den Block loest beide.
|
|
"""
|
|
try:
|
|
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
|
except (OSError, UnicodeDecodeError) as fehler:
|
|
return [(0, f"Taskdatei nicht lesbar ({fehler})")]
|
|
befunde = []
|
|
i = 0
|
|
while i < len(zeilen):
|
|
laenge = zaun(zeilen[i])
|
|
start = i
|
|
i += 1
|
|
if not laenge:
|
|
continue
|
|
spalte = len(zeilen[start]) - len(zeilen[start].lstrip())
|
|
while i < len(zeilen) and not schliesst(zeilen[i], laenge, spalte):
|
|
if zeilen[i].rstrip().endswith("`" * laenge):
|
|
befunde.append((i + 1, f"Zaun klebt an der Codezeile, Block ab Zeile {start + 1}; der Schlusszaun "
|
|
"gehoert auf eine eigene Zeile, ein Text mit Zaunfolge in einen laengeren Zaun"))
|
|
break
|
|
kern = zeilen[i].lstrip()
|
|
if kern.startswith("`" * laenge):
|
|
befunde.append((i + 1, f"hier oeffnet ein Block, der Block ab Zeile {start + 1} ist aber nicht geschlossen; "
|
|
"es fehlt sein Schlusszaun, oder der Text braucht einen laengeren Zaun"))
|
|
start, laenge, spalte = i, len(kern) - len(kern.lstrip("`")), len(zeilen[i]) - len(kern)
|
|
i += 1
|
|
if i >= len(zeilen):
|
|
befunde.append((start + 1, f"Block schliesst nie (Zaun aus {laenge} Backticks)"))
|
|
i += 1
|
|
return befunde
|
|
|
|
|
|
def bloecke(taskdatei: Path, kopie: Path | None = None):
|
|
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
|
geplant = zieldateien(zeilen, wurzeln(taskdatei.parent))
|
|
ziel = None
|
|
# (Zeile, Token) der letzten Ueberschrift ohne Ziel, die nach einer Datei aussieht; None, sobald ein Ziel kommt
|
|
ungeklaert = None
|
|
i = 0
|
|
while i < len(zeilen):
|
|
zeile = zeilen[i]
|
|
if UEBERSCHRIFT.match(zeile):
|
|
# Der Generator laeuft verzahnt mit main(): Die Kopie traegt hier schon die Bloecke davor
|
|
neu, offen = ordne(zeile, geplant, kopie)
|
|
if neu:
|
|
ziel, ungeklaert = neu, None
|
|
elif offen:
|
|
ungeklaert = (i + 1, offen)
|
|
laenge = zaun(zeile)
|
|
if laenge:
|
|
einzug = len(zeile) - len(zeile.lstrip())
|
|
start = i
|
|
i += 1
|
|
inhalt = []
|
|
while i < len(zeilen) and not schliesst(zeilen[i], laenge, einzug):
|
|
inhalt.append(zeilen[i][einzug:] if zeilen[i][:einzug].strip() == "" else zeilen[i])
|
|
i += 1
|
|
k = start - 1
|
|
while k >= 0 and not zeilen[k].strip():
|
|
k -= 1
|
|
marke = zeilen[k].strip() if k >= 0 else ""
|
|
yield {"ziel": ziel, "marke": marke, "zeile": start + 1, "text": "\n".join(inhalt), "einzug": einzug,
|
|
"ungeklaert": ungeklaert}
|
|
i += 1
|
|
|
|
|
|
def in_kopie(kopie: Path, ziel: str) -> Path | None:
|
|
"""Der Zielpfad in der Kopie, oder None, wenn er aus ihr hinausfuehrt.
|
|
|
|
Nachbesserung 2026-10-11 (Paket E9 der Trading Plattform): Ein absoluter Pfad in einer
|
|
Ueberschrift schlug unter Windows die Kopie (kopie / "D:/..." ist der absolute Pfad), und das
|
|
Skript schrieb mit Exit 0 in die echte Datei; gemessen im Scratchpad an einer Probe-Datei.
|
|
Seitdem ist ein Ziel ausserhalb der Kopie (absolut oder mit "..") ein Befund, und nichts wird
|
|
geschrieben.
|
|
"""
|
|
pfad = (kopie / ziel).resolve()
|
|
try:
|
|
pfad.relative_to(kopie.resolve())
|
|
except ValueError:
|
|
return None
|
|
return pfad
|
|
|
|
|
|
LOESCHZIEL = re.compile(r"^- `(.+?)`\s*\((.*)\)\s*$")
|
|
|
|
|
|
def loesche(taskdatei: Path, kopie: Path) -> int:
|
|
"""Loescht, was der Abschnitt "## Zieldateien" als "(gelöscht)" fuehrt.
|
|
|
|
Der Pfad dort ist absolut und zeigt ins echte Projekt. Geloescht wird in der Kopie die
|
|
Datei am laengsten Pfadende, das dort existiert -- nie etwas ausserhalb der Kopie.
|
|
"""
|
|
drin = False
|
|
anzahl = 0
|
|
for zeile in normal(taskdatei.read_text(encoding="utf-8")).split("\n"):
|
|
if zeile.startswith("## "):
|
|
drin = zeile.startswith("## Zieldateien")
|
|
continue
|
|
treffer = LOESCHZIEL.match(zeile) if drin else None
|
|
if not treffer or "gelöscht" not in treffer.group(2):
|
|
continue
|
|
teile = treffer.group(1).replace("\\", "/").split("/")
|
|
for anfang in range(len(teile)):
|
|
kandidat = kopie.joinpath(*teile[anfang:])
|
|
if ":" not in teile[anfang] and kandidat.is_file():
|
|
kandidat.unlink()
|
|
anzahl += 1
|
|
break
|
|
return anzahl
|
|
|
|
|
|
def main() -> int:
|
|
plan, kopie = Path(sys.argv[1]), Path(sys.argv[2])
|
|
befunde = 0
|
|
# Erst alle Zaeune aller Taskdateien, dann der erste Schreibvorgang: Hinter einem kaputten Zaun stimmt die Datei nicht mehr
|
|
for name in sys.argv[3:]:
|
|
for zeile, meldung in zaunbefunde(plan / name):
|
|
print(f"BEFUND {name}:{zeile}: {meldung}; nichts geschrieben")
|
|
befunde += 1
|
|
if befunde:
|
|
print(f"{befunde} Befund(e).")
|
|
return 2
|
|
for name in sys.argv[3:]:
|
|
taskdatei = plan / name
|
|
alt = None
|
|
zaehler = {"neu": 0, "ersetzt": 0, "angehaengt": 0, "uebergangen": 0}
|
|
for block in bloecke(taskdatei, kopie):
|
|
marke, ziel = block["marke"], block["ziel"]
|
|
if block["einzug"] > 0:
|
|
zaehler["uebergangen"] += 1
|
|
continue
|
|
if marke == "Aus:" or marke.startswith("Aus, Zeile"):
|
|
alt = block
|
|
continue
|
|
if block["ungeklaert"] and schreibt(marke):
|
|
zeile_u, pfade = block["ungeklaert"]
|
|
print(f"BEFUND {name}:{block['zeile']}: Ueberschrift in Zeile {zeile_u} nennt "
|
|
f"{', '.join(f'`{p}`' for p in pfade)} und keine erkannte Zieldatei (fehlt sie unter ## Zieldateien?); "
|
|
f"der Block ginge nach {ziel or 'keinem Ziel'}, nichts geschrieben")
|
|
befunde += 1
|
|
alt = None
|
|
continue
|
|
if marke == "wird:":
|
|
if alt is None or ziel is None:
|
|
print(f"BEFUND {name}:{block['zeile']}: wird-Block ohne Aus-Block oder ohne Zieldatei")
|
|
befunde += 1
|
|
continue
|
|
pfad = in_kopie(kopie, ziel)
|
|
if pfad is None:
|
|
print(f"BEFUND {name}:{block['zeile']}: Zieldatei {ziel} liegt ausserhalb der Kopie, nichts geschrieben")
|
|
befunde += 1
|
|
alt = None
|
|
continue
|
|
if not pfad.exists():
|
|
print(f"BEFUND {name}:{block['zeile']}: Zieldatei {ziel} fehlt in der Kopie")
|
|
befunde += 1
|
|
alt = None
|
|
continue
|
|
text = normal(pfad.read_text(encoding="utf-8"))
|
|
anzahl = text.count(alt["text"])
|
|
if anzahl != 1:
|
|
print(f"BEFUND {name}:{alt['zeile']}: Aus-Block steht {anzahl}-mal in {ziel}")
|
|
befunde += 1
|
|
else:
|
|
with open(pfad, "w", encoding="utf-8", newline="\n") as strom:
|
|
strom.write(text.replace(alt["text"], block["text"]))
|
|
zaehler["ersetzt"] += 1
|
|
alt = None
|
|
continue
|
|
alt = None
|
|
if ("Die ganze Datei ist neu" in marke or "vollständig ersetzt" in marke) and ziel is not None:
|
|
pfad = in_kopie(kopie, ziel)
|
|
if pfad is None:
|
|
print(f"BEFUND {name}:{block['zeile']}: Zieldatei {ziel} liegt ausserhalb der Kopie, nichts geschrieben")
|
|
befunde += 1
|
|
continue
|
|
pfad.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(pfad, "w", encoding="utf-8", newline="\n") as strom:
|
|
strom.write(block["text"].rstrip("\n") + "\n")
|
|
zaehler["neu"] += 1
|
|
continue
|
|
if marke.startswith("Häng diesen Block ans Ende") and ziel is not None:
|
|
pfad = in_kopie(kopie, ziel)
|
|
if pfad is None:
|
|
print(f"BEFUND {name}:{block['zeile']}: Zieldatei {ziel} liegt ausserhalb der Kopie, nichts geschrieben")
|
|
befunde += 1
|
|
continue
|
|
text = normal(pfad.read_text(encoding="utf-8"))
|
|
with open(pfad, "w", encoding="utf-8", newline="\n") as strom:
|
|
strom.write(text.rstrip("\n") + "\n" + block["text"].rstrip("\n") + "\n")
|
|
zaehler["angehaengt"] += 1
|
|
continue
|
|
zaehler["uebergangen"] += 1
|
|
zaehler["geloescht"] = loesche(taskdatei, kopie)
|
|
print(f"{name}: {zaehler}")
|
|
print(f"{befunde} Befund(e).")
|
|
return 2 if befunde else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|