Logo Das Programmlogo steht jetzt klein neben "powered by ravokk" - dort sitzt die Kennung des Werkzeugs, nicht die des Kunden. Eine eigene Kopfzeile hat dieses Programm nicht; oben sind die Reiter. bauen.py Der Weg von der Quelle zur Setup-Datei war eine Folge von Befehlen, die jemand von Hand eintippen musste. Wer einen Schritt vergisst, liefert eine Setup-Datei mit einer alten EXE aus - und sieht es ihr nicht an. Genau das ist heute passiert. Jetzt ist es ein Befehl, die Nummer kommt aus version.py, und am Ende stehen die SHA256-Summen fuer den Release-Text. pruef_start_exe.py Prueft, was kein anderer Pruefstand prueft: ob die GEBAUTE EXE ein sichtbares Fenster zeigt. Alle uebrigen fahren das Programm aus der Quelle, dort trat der Fehler nie auf. "Der Prozess laeuft" beweist dabei nichts - er lief ja, waehrend nichts zu sehen war. Aufgeraeumt "Rechnung Campinghof.exe" ist weg; gebaute Dateien gehoeren ins Release, nicht ins Repo. Die .spec-Dateien dagegen schon: ohne sie kann niemand die EXE nachbauen, die der Updater ausliefert. Fassung 1.7.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
123 lines
4.2 KiB
Python
123 lines
4.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruefstand: `self` darf nur dort stehen, wo es auch eines gibt.
|
|
|
|
DER FALL, DER DAZU GEFUEHRT HAT (06.09.2026)
|
|
--------------------------------------------
|
|
Nach dem Installieren meldete das Programm beim Start:
|
|
|
|
File "app.py", line 1824, in <module>
|
|
NameError: name 'self' is not defined
|
|
|
|
Im `if __name__ == "__main__"`-Block stand `self.melden_fehler(...)`. Dort gibt
|
|
es kein `self` - das Fenster wird ja gerade NICHT gebaut. Beim Umbau auf die
|
|
eigenen Meldungsfenster war die Zeile mitgezogen worden.
|
|
|
|
WARUM DAS KEINE SYNTAXPRUEFUNG FINDET
|
|
-------------------------------------
|
|
`ast.parse` und `py_compile` pruefen die FORM, nicht die Bedeutung. Ein
|
|
undefinierter Name faellt erst auf, wenn die Zeile ausgefuehrt wird - und diese
|
|
hier laeuft nur, wenn ein ZWEITES Fenster gestartet wird und sich das erste
|
|
nicht nach vorn holen laesst. Also fast nie. Genau deshalb ist sie durch alle
|
|
Pruefstaende gekommen.
|
|
|
|
WAS HIER GEPRUEFT WIRD
|
|
----------------------
|
|
Jede Verwendung des Namens `self` muss in einer Funktion stehen, die `self` auch
|
|
als ersten Parameter hat. Alles andere ist ein Ueberbleibsel aus einer Methode.
|
|
|
|
Geprueft werden beide Programme - der Fehler war in einem, haette aber genauso
|
|
im anderen stecken koennen.
|
|
"""
|
|
import ast
|
|
import io
|
|
import os
|
|
import sys
|
|
|
|
ORDNER = [r"C:\claude\rechnungstool", r"C:\claude\beherbergungssteuer"]
|
|
|
|
fehler = []
|
|
|
|
|
|
def pruefe(bedingung, text):
|
|
zeile = (" OK | " if bedingung else "FEHLER | ") + text
|
|
kod = sys.stdout.encoding or "utf-8"
|
|
print(zeile.encode(kod, "replace").decode(kod))
|
|
if not bedingung:
|
|
fehler.append(text)
|
|
|
|
|
|
def fundstellen(quelltext):
|
|
"""Alle Zeilen, in denen `self` steht, ohne dass es eines gibt."""
|
|
baum = ast.parse(quelltext)
|
|
treffer = []
|
|
|
|
def erster_parameter(knoten):
|
|
args = knoten.args
|
|
alle = list(args.posonlyargs) + list(args.args)
|
|
return alle[0].arg if alle else None
|
|
|
|
def durchgehen(knoten, hat_self):
|
|
for kind in ast.iter_child_nodes(knoten):
|
|
if isinstance(kind, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
# Eine verschachtelte Funktion in einer Methode darf `self` aus
|
|
# der umgebenden Methode benutzen - das ist ein Abschluss und
|
|
# voellig richtig. Verfuegbar ist es also, sobald IRGENDEINE
|
|
# umgebende Funktion es als ersten Parameter hat.
|
|
durchgehen(kind, hat_self or erster_parameter(kind) == "self")
|
|
elif isinstance(kind, ast.Lambda):
|
|
durchgehen(kind, hat_self)
|
|
else:
|
|
if isinstance(kind, ast.Name) and kind.id == "self" and not hat_self:
|
|
treffer.append(kind.lineno)
|
|
durchgehen(kind, hat_self)
|
|
|
|
durchgehen(baum, False)
|
|
return sorted(set(treffer))
|
|
|
|
|
|
# ---- 1. Die Pruefung muss den bekannten Fehler FINDEN -------------------
|
|
# Sonst prueft sie nichts und wiegt uns in Sicherheit.
|
|
kaputt = (
|
|
"class A:\n"
|
|
" def m(self):\n"
|
|
" self.x = 1\n"
|
|
"\n"
|
|
'if __name__ == "__main__":\n'
|
|
" self.melden_fehler('geht nicht')\n"
|
|
)
|
|
gefunden = fundstellen(kaputt)
|
|
pruefe(gefunden == [6],
|
|
f"die Pruefung findet den bekannten Fehler in Zeile 6 (gefunden: {gefunden})")
|
|
|
|
sauber = (
|
|
"class A:\n"
|
|
" def m(self):\n"
|
|
" self.x = 1\n"
|
|
" f = lambda: self.x\n"
|
|
" return f\n"
|
|
)
|
|
pruefe(fundstellen(sauber) == [],
|
|
"und meldet sauberen Code nicht faelschlich")
|
|
|
|
# ---- 2. Beide Programme durchgehen -------------------------------------
|
|
for ordner in ORDNER:
|
|
for name in sorted(os.listdir(ordner)):
|
|
if not name.endswith(".py"):
|
|
continue
|
|
pfad = os.path.join(ordner, name)
|
|
try:
|
|
quelle = io.open(pfad, encoding="utf-8").read()
|
|
stellen = fundstellen(quelle)
|
|
except (OSError, SyntaxError) as e:
|
|
pruefe(False, f"{name} liess sich nicht lesen ({e})")
|
|
continue
|
|
if stellen:
|
|
pruefe(False, f"{os.path.basename(ordner)}/{name}: `self` ohne self "
|
|
f"in Zeile(n) {stellen}")
|
|
|
|
pruefe(True, f"{len(ORDNER)} Programme durchgegangen")
|
|
|
|
if fehler:
|
|
print(f"ROT | {len(fehler)} Punkt(e) nicht erfuellt")
|
|
sys.exit(1)
|
|
print("GRUEN | Nirgends steht `self`, wo es keines gibt")
|