Source code for ClearMap.config.utils

"""
A set of low level utilities for configuration file parsing and handling.

.. warning::

    No heavy imports here. This is used by the early_boot which sets temp_dir.
    If using modules outside the standard library, import them locally inside functions.

"""

import re

from ClearMap.Utils.exceptions import ConfigParsingError


[docs] def configobj_to_dict(obj): import configobj # avoids hard dependency if not used """Recursively convert ConfigObj/Section → plain dict/list/scalars.""" if isinstance(obj, configobj.Section): # iterate in insertion order (ConfigObj preserves it) return {k: configobj_to_dict(v) for k, v in obj.items()} elif isinstance(obj, dict): return {k: configobj_to_dict(v) for k, v in obj.items()} elif isinstance(obj, (list, tuple)): return [configobj_to_dict(x) for x in obj] else: return obj
def _handle_configobj_failed_parse(err, cfg_path): # Extract line number if available msg = str(err) line_no = None m = re.search(r'line\s+(\d+)', msg) if m: try: line_no = int(m.group(1)) except ValueError: line_no = None # Best-effort: show offending line for context context = '' if line_no is not None and cfg_path.exists(): try: with cfg_path.open('r', encoding='utf-8', errors='replace') as fh: lines = fh.readlines() if 1 <= line_no <= len(lines): context = f'\n→ line {line_no}: {lines[line_no - 1].rstrip()}' except Exception: pass raise ConfigParsingError( f'Could not parse config file "{cfg_path}". {msg}{context}' ) from err
[docs] def parse_flat_yaml(text: str) -> dict: out = {} for raw in text.splitlines(): line = raw.strip() if not line or line.startswith("#"): # skip comments / empty lines continue if ":" not in line: # skip malformed lines continue k, v = line.split(":", 1) k = k.strip() v = v.strip() if v.lower() == "null": # None out[k] = None elif v.lower() in ("true", "false"): # bool out[k] = (v.lower() == "true") elif ((v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'"))): # string out[k] = v[1:-1] else: # try int, then float, else raw string try: out[k] = int(v) except ValueError: try: out[k] = float(v) except ValueError: if '[' in v or '{' in v: # likely a list or dict raise ValueError(f"Unsupported complex value in flat YAML: {v}") else: out[k] = v # Assume string return out
[docs] def write_flat_yaml(cfg_path, data: dict): lines = [] for k, v in data.items(): if v is None: lines.append(f"{k}: null") elif isinstance(v, bool): lines.append(f"{k}: {'true' if v else 'false'}") elif isinstance(v, (int, float)): lines.append(f"{k}: {v}") elif isinstance(v, str): lines.append(f'{k}: "{v}"') else: raise ValueError(f"Unsupported value type in flat YAML: {type(v)}") content = "\n".join(lines) + "\n" with cfg_path.open('w', encoding='utf-8') as fh: fh.write(content)