File size: 3,284 Bytes
7fb79e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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),
    )