Merge pull request 'Bugfix/42 umlaute sonderzeichen' (#9) from bugfix/42-umlaute-sonderzeichen into unstable
Reviewed-on: #9
This commit was merged in pull request #9.
This commit is contained in:
@@ -37,6 +37,7 @@ Legende: ⚠ = über 600 Zeilen, nie komplett laden, per `grep -n "def "` navigi
|
||||
| `functions/sys_wrapper.py` | 106 | `get_plugin_root`, `join_path`, `file_exists`, `read_text`, `add_to_sys_path`, `getattr_safe` |
|
||||
| `functions/landkreise.py` | 48 | Sächsische Landkreise aus `data/landkreise.json` (`LANDKREISE`, `ALLE_LANDKREISE`) |
|
||||
| `functions/laenderkennung.py` | 52 | ALKIS-Länderkennung je Aufrufer-Präfix |
|
||||
| `functions/text_wrapper.py` | 25 | `sanitize_bezeichner(text, ersatz="_")`: Freitext → Layer-/Dateiname, Umlaute/ß/_/- bleiben erhalten |
|
||||
|
||||
### modules/ – Prüfen, Laden, Schreiben, Drucken
|
||||
|
||||
|
||||
@@ -9,17 +9,17 @@ selbst (Docstrings) und aus der Testbaseline.
|
||||
|
||||
Stand der Baseline: `docs/status/testbaseline.json` (SHA-256 `aeabc946a7f3`)
|
||||
|
||||
Gesamt: 3 Pakete, 30 Testdateien, 332 Testfälle – 287 grün, 37 rot (Baseline), 8 übersprungen.
|
||||
Mit Beschreibung (Docstring): 100 von 332 (30 %).
|
||||
Gesamt: 3 Pakete, 31 Testdateien, 339 Testfälle – 294 grün, 37 rot (Baseline), 8 übersprungen.
|
||||
Mit Beschreibung (Docstring): 107 von 339 (32 %).
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Paket | Testdateien | Testfälle | grün | rot (Baseline) | übersprungen | mit Beschreibung |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| `sn_basis` | 13 | 87 | 87 | 0 | 0 | 0 von 87 (0 %) |
|
||||
| `sn_basis` | 14 | 94 | 94 | 0 | 0 | 7 von 94 (7 %) |
|
||||
| `sn_verfahrensgebiet` | 12 | 201 | 157 | 36 | 8 | 99 von 201 (49 %) |
|
||||
| `sn_plan41` | 5 | 44 | 43 | 1 | 0 | 1 von 44 (2 %) |
|
||||
| **Summe** | **30** | **332** | **287** | **37** | **8** | **100 von 332 (30 %)** |
|
||||
| **Summe** | **31** | **339** | **294** | **37** | **8** | **107 von 339 (32 %)** |
|
||||
|
||||
### Legende
|
||||
|
||||
@@ -31,7 +31,7 @@ Mit Beschreibung (Docstring): 100 von 332 (30 %).
|
||||
|
||||
## Paket `sn_basis`
|
||||
|
||||
Plattform-Plugin, Ordner `Plugin_SN_Basis` – 13 Testdateien mit 87 Testfällen (87 grün, 0 rot, 0 übersprungen).
|
||||
Plattform-Plugin, Ordner `Plugin_SN_Basis` – 14 Testdateien mit 94 Testfällen (94 grün, 0 rot, 0 übersprungen).
|
||||
|
||||
### `Plugin_SN_Basis/tests/test_bootstrap.py`
|
||||
|
||||
@@ -225,6 +225,24 @@ Testgruppen:
|
||||
| `TestStilpruefer.test_datei_existiert_falsche_endung` | — (Docstring fehlt) | grün |
|
||||
| `TestStilpruefer.test_datei_existiert_nicht` | — (Docstring fehlt) | grün |
|
||||
|
||||
### `Plugin_SN_Basis/tests/test_text_wrapper.py`
|
||||
|
||||
Worum es in der Datei geht: Unit-Tests für `sn_basis.functions.text_wrapper`.
|
||||
|
||||
Testgruppen:
|
||||
|
||||
- `TestSanitizeBezeichner` (7 Testfälle): — (Docstring fehlt)
|
||||
|
||||
| Testfall | prüft | Status |
|
||||
| --- | --- | --- |
|
||||
| `TestSanitizeBezeichner.test_umlaute_bleiben_erhalten` | Prüft, dass Umlaute und ß nicht durch den Ersatz entfernt werden. | grün |
|
||||
| `TestSanitizeBezeichner.test_bindestrich_bleibt_erhalten` | Prüft, dass ein Bindestrich im Bezeichner erhalten bleibt (Issue #42). | grün |
|
||||
| `TestSanitizeBezeichner.test_unterstrich_bleibt_erhalten` | Prüft, dass ein Unterstrich im Bezeichner erhalten bleibt. | grün |
|
||||
| `TestSanitizeBezeichner.test_leerzeichen_wird_durch_ersatz_zusammengefasst` | Prüft, dass Leerzeichen (kein zulässiges Zeichen) zu einem Ersatzzeichen werden. | grün |
|
||||
| `TestSanitizeBezeichner.test_fuehrender_und_folgender_ersatz_wird_entfernt` | Prüft, dass ein Ersatzzeichen am Anfang oder Ende des Ergebnisses entfernt wird. | grün |
|
||||
| `TestSanitizeBezeichner.test_eigenes_ersatzzeichen_wird_verwendet` | Prüft, dass ein abweichendes Ersatzzeichen anstelle von Unterstrich genutzt wird. | grün |
|
||||
| `TestSanitizeBezeichner.test_nur_unzulaessige_zeichen_ergibt_leeren_string` | Prüft, dass ein Bezeichner ganz aus unzulässigen Zeichen zu einem leeren String wird. | grün |
|
||||
|
||||
### `Plugin_SN_Basis/tests/test_variable_wrapper.py`
|
||||
|
||||
Worum es in der Datei geht: — (Docstring fehlt)
|
||||
@@ -736,7 +754,7 @@ Arbeitsliste: Diese Testfälle haben keinen Docstring; was sie fachlich prüfen,
|
||||
aus dem Quelltext erschließen. Jeder Eintrag braucht eine einzeilige Beschreibung im Testfall,
|
||||
dann verschwindet er hier von selbst.
|
||||
|
||||
### `sn_basis` – 87 von 87 Testfällen ohne Beschreibung
|
||||
### `sn_basis` – 87 von 94 Testfällen ohne Beschreibung
|
||||
|
||||
#### `Plugin_SN_Basis/tests/test_datagrabber.py` (6 von 6) – auch der Modul-Docstring fehlt
|
||||
|
||||
|
||||
@@ -52,6 +52,19 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
|
||||
- **`print()` ist im Code verbreitet**, aber kein Logging. Neue Ausgaben über `message_wrapper` (Nutzer) oder gar nicht; Debug-`print` vor dem PR entfernen.
|
||||
- **Testrunner pro Repo (`tests/run_tests.py`) und Workspace-Runner unterscheiden sich.** Für PR-Nachweise zählt `scripts/run_tests.py --baseline`.
|
||||
- **Tests der Verfahrensgebiet-Suite sind teils veraltet** (erwarten Einzeldatei-/Einzellayer-Schnittstellen vor der Mehrfachauswahl). Vor dem Fixen prüfen, ob der Test oder der Code falsch ist; siehe Baseline.
|
||||
- **Bezeichner (Layernamen, Dateinamen) nie mit eigenem Regex bereinigen.** `sanitize_bezeichner` aus
|
||||
`sn_basis.functions.text_wrapper` verwenden – Umlaute, `ß`, `_` und `-` wurden in mehreren KI-generierten
|
||||
Regexen vergessen (Plugin_SN_Basis #42).
|
||||
- **Deutscher Text ohne Umlaute/ß ist ein eigenständiges Muster, kein Einzelfall.** Bei der Aufarbeitung von
|
||||
#42 fanden sich über alle Fachplugins verteilt MessageBox-Texte, Labels und Docstrings, die wortwörtlich als
|
||||
ASCII generiert wurden (`gueltig` statt `gültig`, `ueber` statt `über`, `Massstab`/`Groesse` als Anzeigetext
|
||||
statt `Maßstab`/`Größe`) – typisch für KI-generierten Text, der Sonderzeichen vermeidet. Neuen deutschen Text
|
||||
(Meldungen, Labels, Kommentare, Docstrings) immer mit echten Umlauten/ß schreiben, nicht mit `ae`/`oe`/`ue`/`ss`
|
||||
behelfen. Vor dem Commit `python scripts/pruefe_umlaute.py [<repo>]` laufen lassen – findet genau dieses
|
||||
Muster (String-Inhalte und Kommentare mit Leerzeichen, die einen bekannten ASCII-Wortstamm enthalten;
|
||||
technische Bezeichner ohne Leerzeichen werden nicht angetastet). Die Wortstammliste im Skript ist bewusst
|
||||
konservativ (keine Fehlalarme wichtiger als Vollständigkeit) – Ersatz fürs Lesen des eigenen Textes ist sie
|
||||
nicht.
|
||||
|
||||
## Zusammenarbeit
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Findet deutschen Text in .py-Dateien, der offenbar ohne Umlaute/ß geschrieben wurde.
|
||||
|
||||
Hintergrund: Plugin_SN_Basis #42. KI-generierter Text weicht gelegentlich auf eine
|
||||
ASCII-Ersatzschreibweise aus ("gueltig" statt "gültig", "ueber" statt "über") statt
|
||||
echte Umlaute zu verwenden. Das betrifft vor allem Meldungstexte (MessageBox,
|
||||
zeige_hinweis, QLabel) und Docstrings/Kommentare.
|
||||
|
||||
Technische Bezeichner (Variablennamen, "aktion"-Codes, WFS-Feldnamen,
|
||||
Projektvariablen-Keys) sind davon ausdrücklich nicht betroffen: Sie sind bewusst
|
||||
ASCII und enthalten kein Leerzeichen, während geschriebener deutscher Text so gut
|
||||
wie immer eines enthält. Deshalb prüft dieses Skript nur String-Inhalte mit
|
||||
mindestens einem Leerzeichen sowie Kommentartext - keine Bezeichner.
|
||||
|
||||
Kein Ersatz fürs Korrekturlesen: Die Liste verdächtiger Wortstämme ist bewusst kurz
|
||||
gehalten (nur eindeutige Fälle), um keine Fehlalarme zu erzeugen. Neue, hier nicht
|
||||
erfasste Wortstämme ergänzen.
|
||||
|
||||
Aufrufe (aus dem Workspace-Root):
|
||||
python scripts/pruefe_umlaute.py # alle Repos aus workspace.toml mit Paketnamen
|
||||
python scripts/pruefe_umlaute.py Basis # nur ein Repo (Kurzname genügt)
|
||||
|
||||
Exit-Code: 0 = keine Funde, 1 = mindestens ein Fund.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MANIFEST = ROOT / "workspace.toml"
|
||||
|
||||
# Nur eindeutige Fälle: Wortstämme, die als ASCII-Ersatzschreibweise so gut wie nie
|
||||
# etwas anderes als ein fehlender Umlaut/ß sein können. Absichtlich kein "ss" für
|
||||
# "ß" (zu viele legitime Doppel-S-Wörter) und kein "ae/oe/ue" pauschal (zu viele
|
||||
# Fehlalarme in technischen Bezeichnern und Fremdwörtern).
|
||||
VERDAECHTIGE_STAEMME = [
|
||||
r"gueltig\w*", r"ungueltig\w*", r"verfuegbar\w*", r"ausgewaehlt\w*",
|
||||
r"gewaehlt\w*", r"waehlen\b", r"auswaehlen\b", r"loeschen\b", r"loescht\w*",
|
||||
r"groesse\w*", r"zuruecksetzen\b", r"erlaeuter\w*", r"bestaetig\w*",
|
||||
r"natuerlich\w*", r"moeglich\w*", r"zusaetzlich\w*", r"abhaengig\w*",
|
||||
r"unzulaessig\w*", r"ueberpruef\w*", r"vollstaendig\w*", r"geaendert\w*",
|
||||
r"aenderung\w*", r"einfuegen\b", r"ausfuehr\w*", r"benoetig\w*",
|
||||
r"gewuenscht\w*", r"ueber\w*", r"fuer\b", r"hoehe\w*", r"waere\b",
|
||||
]
|
||||
_STAMM_PATTERN = re.compile(
|
||||
r"\b(?:" + "|".join(VERDAECHTIGE_STAEMME) + r")\b", re.IGNORECASE
|
||||
)
|
||||
|
||||
# Erlaubt genau diese exakten Fundstellen (Datei, Zeile) - für Ausnahmefälle, die
|
||||
# geprüft und bewusst akzeptiert wurden. Aktuell leer: siehe Docstring, warum
|
||||
# technische Bezeichner die Heuristik ohnehin nicht erreichen.
|
||||
ALLOWLIST: set[tuple[str, int]] = set()
|
||||
|
||||
_STRING_PATTERN = re.compile(
|
||||
r'"""(?P<triple_d>(?:.|\n)*?)"""'
|
||||
r"|'''(?P<triple_s>(?:.|\n)*?)'''"
|
||||
r'|"(?P<double>(?:[^"\\\n]|\\.)*)"'
|
||||
r"|'(?P<single>(?:[^'\\\n]|\\.)*)'"
|
||||
)
|
||||
_KOMMENTAR_PATTERN = re.compile(r"#(?P<text>.*)$", re.MULTILINE)
|
||||
|
||||
|
||||
def _kandidaten(quelltext: str):
|
||||
"""Liefert (start_offset, text) für String-Inhalte und Kommentare mit Leerzeichen."""
|
||||
for m in _STRING_PATTERN.finditer(quelltext):
|
||||
text = next(g for g in m.groups() if g is not None)
|
||||
if " " in text:
|
||||
yield m.start(), text
|
||||
for m in _KOMMENTAR_PATTERN.finditer(quelltext):
|
||||
text = m.group("text")
|
||||
if " " in text:
|
||||
yield m.start("text"), text
|
||||
|
||||
|
||||
def pruefe_datei(pfad: Path) -> list[tuple[int, str, str]]:
|
||||
"""Liefert Funde als (Zeilennummer, Wortstamm, Kontext) für eine Datei.
|
||||
|
||||
Ein Kandidat (String-Inhalt oder Kommentar) kann mehrzeilig sein (Docstrings);
|
||||
deshalb wird für jeden Treffer einzeln die Zeile anhand seiner eigenen Position
|
||||
berechnet, nicht anhand des Kandidaten-Anfangs.
|
||||
"""
|
||||
quelltext = pfad.read_text(encoding="utf-8", errors="replace")
|
||||
funde = []
|
||||
for offset, text in _kandidaten(quelltext):
|
||||
for treffer in _STAMM_PATTERN.finditer(text):
|
||||
zeile = quelltext.count("\n", 0, offset + treffer.start()) + 1
|
||||
zeilen_text = text.splitlines()[treffer.string.count("\n", 0, treffer.start())] \
|
||||
if "\n" in text else text
|
||||
kontext = zeilen_text.strip()
|
||||
if len(kontext) > 90:
|
||||
kontext = kontext[:87] + "..."
|
||||
funde.append((zeile, treffer.group(0), kontext))
|
||||
return funde
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("repos", nargs="*", help="Kurzname(n) aus workspace.toml (Standard: alle mit Paketnamen)")
|
||||
args = ap.parse_args()
|
||||
|
||||
with MANIFEST.open("rb") as f:
|
||||
manifest = tomllib.load(f)
|
||||
alle_repos = [r for r in manifest["repos"] if r.get("paket")]
|
||||
|
||||
if args.repos:
|
||||
gesucht = {r.lower() for r in args.repos}
|
||||
repos = [
|
||||
r for r in alle_repos
|
||||
if r["name"].lower() in gesucht
|
||||
or r["name"].lower().replace("plugin_sn_", "") in gesucht
|
||||
or r["paket"].lower() in gesucht
|
||||
]
|
||||
unbekannt = gesucht - {
|
||||
n for r in repos for n in (r["name"].lower(), r["name"].lower().replace("plugin_sn_", ""), r["paket"].lower())
|
||||
}
|
||||
if unbekannt:
|
||||
print(f"Unbekannte Repo-Angabe: {', '.join(sorted(unbekannt))}", file=sys.stderr)
|
||||
return 2
|
||||
else:
|
||||
repos = alle_repos
|
||||
|
||||
gesamt_funde = 0
|
||||
for repo in repos:
|
||||
basis = ROOT / repo["name"]
|
||||
if not basis.is_dir():
|
||||
print(f"[{repo['paket']}] Ordner fehlt ({basis}) - übersprungen")
|
||||
continue
|
||||
repo_funde = 0
|
||||
for py_datei in sorted(basis.rglob("*.py")):
|
||||
if any(teil in (".git", "__pycache__") for teil in py_datei.parts):
|
||||
continue
|
||||
relativ = py_datei.relative_to(ROOT).as_posix()
|
||||
for zeile, stamm, kontext in pruefe_datei(py_datei):
|
||||
if (relativ, zeile) in ALLOWLIST:
|
||||
continue
|
||||
print(f"{relativ}:{zeile}: '{stamm}' in: {kontext}")
|
||||
repo_funde += 1
|
||||
status = "GRÜN" if repo_funde == 0 else "ROT"
|
||||
print(f"[{repo['paket']}] {status}: {repo_funde} Fund/e")
|
||||
gesamt_funde += repo_funde
|
||||
|
||||
print(f"\nGesamt: {gesamt_funde} Fund/e in {len(repos)} Repo(s).")
|
||||
return 1 if gesamt_funde else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user