Files
dxfmakros/lib/export_blockpatterns.py
T
m.stangl b6b71c5997 safe_float/safe_int Hilfsfunktionen: doppelten try/except-Code konsolidiert
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>
2026-07-23 12:55:50 +02:00

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('"', '""')