b6b71c5997
export_csv.py und export_sivas.py enthielten je ca. 10 nahezu identische try/except (ValueError, TypeError)-Bloecke fuer "String zu float/int mit Fallback". Neue gemeinsame Helfer safe_float()/safe_int() (export_blockpatterns.py) ersetzen diese Bloecke; dabei auch zwei bislang ungeschuetzte int()/float()-Aufrufe in export_sivas.py (Kreisel-Zaehler) abgesichert, die bei fehlerhaften Attributwerten sonst abgestuerzt waeren. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
100 lines
3.7 KiB
Python
100 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
export_blockpatterns.py - Liest Blockname-Muster aus cfg/export.cfg.
|
|
|
|
Einzige Quelle fuer die Blockname-Klassifikation, die sowohl von
|
|
Lisp/export.lsp (csv:collect-export-blocks, ueber export:pattern) als auch
|
|
von den Python-Export-Skripten (export_csv.py, export_sivas.py) genutzt
|
|
wird. Vermeidet, dass Blockname-Praefixe/-Wildcards in mehreren Dateien
|
|
unabhaengig voneinander gepflegt werden und dabei auseinanderlaufen.
|
|
|
|
cfg/export.cfg deklariert fuer Lisp kombinierte Kategorien (z.B.
|
|
pattern_kreisel deckt Kreisel UND Eckrad ab, weil Lisp beim Sammeln der
|
|
Bloecke nur "gehoert das ueberhaupt zum Export" wissen muss). Python
|
|
braucht dagegen feinere Kategorien (Kreisel und Eckrad sind unterschied-
|
|
liche TeileArt-Zeilen) - dafuer gibt es zusaetzliche, feinere Keys
|
|
(pattern_eckrad, pattern_strecke_compound, pattern_strecke_module), die
|
|
inhaltlich Teilmengen der groben Lisp-Kategorien sind.
|
|
"""
|
|
|
|
import configparser
|
|
import fnmatch
|
|
import functools
|
|
import os
|
|
|
|
|
|
def cfg_path_from_env(default_dir=None):
|
|
"""Pfad zu export.cfg ermitteln: DXFM_CFG env var, sonst <repo>/cfg."""
|
|
dxfm_cfg = os.environ.get("DXFM_CFG")
|
|
if dxfm_cfg:
|
|
return os.path.join(dxfm_cfg, "export.cfg")
|
|
base = default_dir or os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
return os.path.join(base, "cfg", "export.cfg")
|
|
|
|
|
|
@functools.lru_cache(maxsize=None)
|
|
def load_export_cfg(cfg_path):
|
|
"""Liest und parst export.cfg genau einmal je Pfad (Prozess-Cache).
|
|
|
|
Gemeinsam genutzt von export_blockpatterns/export_neighbors/
|
|
export_planquadrat, damit export.cfg pro Exportlauf nur einmal von der
|
|
Platte gelesen/geparst wird statt bei jedem der bisher unabhaengigen
|
|
load_*-Aufrufe erneut. inline_comment_prefixes=("#",) ist noetig, da
|
|
export.cfg Werte wie "numerisch # zahlen, von links nach rechts" mit
|
|
Inline-Kommentaren nutzt (siehe [Planquadrate_*]-Sektionen).
|
|
cfg_path muss bereits aufgeloest sein (siehe cfg_path_from_env) - der
|
|
Cache ist sonst nicht treffsicher (ein "None"-Schluessel wuerde nie den
|
|
tatsaechlichen Pfad abbilden).
|
|
"""
|
|
parser = configparser.ConfigParser(inline_comment_prefixes=("#",))
|
|
parser.read(cfg_path, encoding="utf-8")
|
|
return parser
|
|
|
|
|
|
def load_patterns(cfg_path=None):
|
|
"""Liest [blockpattern] aus export.cfg.
|
|
|
|
Rueckgabe: dict key -> list[str] (Wildcard-Muster, wcmatch-kompatibel:
|
|
"*" = beliebig viele Zeichen). Bei fehlender Datei/Sektion: leeres dict.
|
|
"""
|
|
if cfg_path is None:
|
|
cfg_path = cfg_path_from_env()
|
|
parser = load_export_cfg(cfg_path)
|
|
patterns = {}
|
|
if parser.has_section("blockpattern"):
|
|
for key, value in parser.items("blockpattern"):
|
|
patterns[key] = [p.strip() for p in value.split(",") if p.strip()]
|
|
return patterns
|
|
|
|
|
|
def safe_float(value, default=0.0):
|
|
"""Wandelt value in float um; bei fehlendem/ungueltigem Wert Fallback default."""
|
|
try:
|
|
return float(value)
|
|
except (TypeError, ValueError):
|
|
return default
|
|
|
|
|
|
def safe_int(value, default=0):
|
|
"""Wandelt value in int um; bei fehlendem/ungueltigem Wert Fallback default."""
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return default
|
|
|
|
|
|
def matches_any(bname, patterns):
|
|
"""True, wenn bname (case-insensitiv) zu mindestens einem Muster passt."""
|
|
upper = bname.upper()
|
|
return any(fnmatch.fnmatchcase(upper, p.upper()) for p in patterns)
|
|
|
|
|
|
def csv_quote(value):
|
|
"""Escapt einen Wert fuer ein doppelt-angefuehrtes, semikolon-getrenntes CSV-Feld.
|
|
|
|
Verdoppelt eingebettete Anfuehrungszeichen (RFC 4180), damit z.B. eine
|
|
Bezeichnung mit einem '"' das Feldende nicht vortaeuscht.
|
|
"""
|
|
return str(value).replace('"', '""')
|