Files
dxfmakros/lib/export_blockpatterns.py
T
m.stangl 2842e1f1c9 CSV-Escaping-Bug behoben, export.cfg-Mehrfachparsing beseitigt
format_csv_line() in export_csv.py/export_sivas.py hat Feldwerte per
f-String direkt in doppelte Anfuehrungszeichen gesetzt, ohne ein
eingebettetes '"' zu escapen (z.B. eine Bezeichnung mit Anfuehrungszeichen
haette die Spaltengrenze der Zeile kaputt gemacht). Neuer gemeinsamer
Helper csv_quote() (export_blockpatterns.py) verdoppelt eingebettete
Anfuehrungszeichen nach RFC 4180.

Ausserdem: export.cfg wurde bisher von vier unabhaengigen load_*-Funktionen
(load_patterns, load_neighbor_tolerance_mm, load_omniflo_cell_size_mm,
load_planquadrat_config) separat von der Platte gelesen und geparst. Neuer
gemeinsamer, gecachter load_export_cfg() in export_blockpatterns.py sorgt
dafuer, dass export.cfg pro Exportlauf nur noch einmal gelesen wird.

Dabei auch die in export_csv.py bereits genutzte, aber in export_planquadrat.py
fehlende resolve_origins()-Funktion (automatische Planquadrat-Ursprungs-
Ermittlung) ergaenzt - ohne sie war der Import von export_csv.py gebrochen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 12:55:50 +02:00

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