forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathicons.py
196 lines (163 loc) · 6.08 KB
/
icons.py
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
"""Validate integration icon translation files."""
from __future__ import annotations
from typing import Any
import orjson
import voluptuous as vol
from voluptuous.humanize import humanize_error
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.icon import convert_shorthand_service_icon
from .model import Config, Integration
from .translations import translation_key_validator
def icon_value_validator(value: Any) -> str:
"""Validate that the icon is a valid icon."""
value = cv.string_with_no_html(value)
if not value.startswith("mdi:"):
raise vol.Invalid(
"The icon needs to be a valid icon from Material Design Icons and start with `mdi:`"
)
return str(value)
def require_default_icon_validator(value: dict) -> dict:
"""Validate that a default icon is set."""
if "_" not in value:
raise vol.Invalid(
"An entity component needs to have a default icon defined with `_`"
)
return value
def ensure_not_same_as_default(value: dict) -> dict:
"""Validate an icon isn't the same as its default icon."""
for translation_key, section in value.items():
if (default := section.get("default")) and (states := section.get("state")):
for state, icon in states.items():
if icon == default:
raise vol.Invalid(
f"The icon for state `{translation_key}.{state}` is the"
" same as the default icon and thus can be removed"
)
return value
DATA_ENTRY_ICONS_SCHEMA = vol.Schema(
{
"step": {
str: {
"sections": {
str: icon_value_validator,
}
}
}
}
)
CORE_SERVICE_ICONS_SCHEMA = cv.schema_with_slug_keys(
vol.Schema(
{
vol.Optional("service"): icon_value_validator,
vol.Optional("sections"): cv.schema_with_slug_keys(
icon_value_validator, slug_validator=translation_key_validator
),
}
),
slug_validator=translation_key_validator,
)
CUSTOM_INTEGRATION_SERVICE_ICONS_SCHEMA = cv.schema_with_slug_keys(
vol.All(
convert_shorthand_service_icon,
vol.Schema(
{
vol.Optional("service"): icon_value_validator,
vol.Optional("sections"): cv.schema_with_slug_keys(
icon_value_validator, slug_validator=translation_key_validator
),
}
),
),
slug_validator=translation_key_validator,
)
def icon_schema(
core_integration: bool, integration_type: str, no_entity_platform: bool
) -> vol.Schema:
"""Create an icon schema."""
state_validator = cv.schema_with_slug_keys(
icon_value_validator,
slug_validator=translation_key_validator,
)
def icon_schema_slug(marker: type[vol.Marker]) -> dict[vol.Marker, Any]:
return {
marker("default"): icon_value_validator,
vol.Optional("state"): state_validator,
vol.Optional("state_attributes"): vol.All(
cv.schema_with_slug_keys(
{
marker("default"): icon_value_validator,
marker("state"): state_validator,
},
slug_validator=translation_key_validator,
),
ensure_not_same_as_default,
),
}
schema = vol.Schema(
{
vol.Optional("config"): DATA_ENTRY_ICONS_SCHEMA,
vol.Optional("issues"): vol.Schema(
{str: {"fix_flow": DATA_ENTRY_ICONS_SCHEMA}}
),
vol.Optional("options"): DATA_ENTRY_ICONS_SCHEMA,
vol.Optional("services"): CORE_SERVICE_ICONS_SCHEMA
if core_integration
else CUSTOM_INTEGRATION_SERVICE_ICONS_SCHEMA,
}
)
if integration_type in ("entity", "helper", "system"):
if integration_type != "entity" or no_entity_platform:
field = vol.Optional("entity_component")
else:
field = vol.Required("entity_component")
schema = schema.extend(
{
field: vol.All(
cv.schema_with_slug_keys(
icon_schema_slug(vol.Required),
slug_validator=vol.Any("_", cv.slug),
),
require_default_icon_validator,
ensure_not_same_as_default,
)
}
)
if integration_type not in ("entity", "system"):
schema = schema.extend(
{
vol.Optional("entity"): vol.All(
cv.schema_with_slug_keys(
cv.schema_with_slug_keys(
icon_schema_slug(vol.Optional),
slug_validator=translation_key_validator,
),
slug_validator=cv.slug,
),
ensure_not_same_as_default,
)
}
)
return schema
def validate_icon_file(config: Config, integration: Integration) -> None:
"""Validate icon file for integration."""
icons_file = integration.path / "icons.json"
if not icons_file.is_file():
return
name = str(icons_file.relative_to(integration.path))
try:
icons = orjson.loads(icons_file.read_text())
except ValueError as err:
integration.add_error("icons", f"Invalid JSON in {name}: {err}")
return
no_entity_platform = integration.domain in ("notify", "image_processing")
schema = icon_schema(
integration.core, integration.integration_type, no_entity_platform
)
try:
schema(icons)
except vol.Invalid as err:
integration.add_error("icons", f"Invalid {name}: {humanize_error(icons, err)}")
def validate(integrations: dict[str, Integration], config: Config) -> None:
"""Handle JSON files inside integrations."""
for integration in integrations.values():
validate_icon_file(config, integration)