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