from __future__ import annotations import re from dataclasses import dataclass from typing import Iterable _SPLIT_RE = re.compile(r"[,;\n\r]+") _VALUE_PREFIXES = ( "dynamic_analog", "dynamic_string", "dynamic_level", "dynamic_dict", "dynamic_equipment_dict", ) _INDICATOR_PREFIXES = ( "dynamic_status", "dynamic_asutp_status_indicator", ) def split_visual_names(value: object) -> list[str]: parts = [ part.strip() for part in _SPLIT_RE.split(str(value or "")) if part and part.strip() ] seen: set[str] = set() ordered: list[str] = [] for part in parts: key = part.lower() if key in seen: continue seen.add(key) ordered.append(part) return ordered def _normalized_static_names(known_static_names: Iterable[str] | None = None) -> set[str]: return { str(name or "").strip().lower() for name in (known_static_names or ()) if str(name or "").strip() } def visual_name_role(name: str, *, known_static_names: Iterable[str] | None = None) -> str: normalized = str(name or "").strip().lower() if not normalized: return "other" known_statics = _normalized_static_names(known_static_names) if normalized in known_statics: return "static" if any(normalized.startswith(prefix) for prefix in _INDICATOR_PREFIXES): return "indicator" if any(normalized.startswith(prefix) for prefix in _VALUE_PREFIXES): return "value" if normalized.startswith("dynamic_"): return "equipment_widget" return "static" @dataclass(frozen=True) class VisualSpec: ordered: tuple[str, ...] statics: tuple[str, ...] value_widgets: tuple[str, ...] indicator_widgets: tuple[str, ...] equipment_widgets: tuple[str, ...] @property def widgets(self) -> tuple[str, ...]: return self.value_widgets + self.indicator_widgets + self.equipment_widgets @property def is_composite(self) -> bool: return bool(self.statics) and bool(self.widgets) @property def primary_category(self) -> str: if self.value_widgets: return "value" if self.indicator_widgets: return "indicator" if self.statics or self.equipment_widgets: return "equipment" return "other" def parse_visual_spec(value: object, *, known_static_names: Iterable[str] | None = None) -> VisualSpec: ordered = split_visual_names(value) statics: list[str] = [] value_widgets: list[str] = [] indicator_widgets: list[str] = [] equipment_widgets: list[str] = [] for part in ordered: role = visual_name_role(part, known_static_names=known_static_names) if role == "static": statics.append(part) elif role == "value": value_widgets.append(part) elif role == "indicator": indicator_widgets.append(part) elif role == "equipment_widget": equipment_widgets.append(part) return VisualSpec( ordered=tuple(ordered), statics=tuple(statics), value_widgets=tuple(value_widgets), indicator_widgets=tuple(indicator_widgets), equipment_widgets=tuple(equipment_widgets), )