mirror of https://github.com/esphome/esphome.git
Merge 288a23b46c
into c7c0d97a5e
This commit is contained in:
commit
04d462fe4b
|
@ -28,7 +28,7 @@ def validate_substitution_key(value):
|
|||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
validate_substitution_key: cv.string_strict,
|
||||
validate_substitution_key: cv.Any(None, str, int, float, dict, list),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -37,52 +37,87 @@ async def to_code(config):
|
|||
pass
|
||||
|
||||
|
||||
def _expand_substitutions(substitutions, value, path, ignore_missing):
|
||||
def _find_tokens(value):
|
||||
"""
|
||||
Finds substitutable tokens in the form of:
|
||||
```
|
||||
"a variable $a and a variable $abc"
|
||||
```
|
||||
and turns them into pairs of:
|
||||
```
|
||||
('a', (11, 2)), ('abc', (16, 4))
|
||||
```
|
||||
where the first number represents the characters
|
||||
skipped from the last token to the start of the new one
|
||||
and the last number the length of the token
|
||||
(including $ or ${})
|
||||
"""
|
||||
if "$" not in value:
|
||||
return value
|
||||
return
|
||||
|
||||
orig_value = value
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
m = cv.VARIABLE_PROG.search(value, i)
|
||||
if not m:
|
||||
# Nothing more to match. Done
|
||||
break
|
||||
|
||||
i, j = m.span(0)
|
||||
name = m.group(1)
|
||||
last_end = 0
|
||||
for match in cv.VARIABLE_PROG.finditer(value):
|
||||
name = match.group(1)
|
||||
start, end = match.span(0)
|
||||
if name.startswith("{") and name.endswith("}"):
|
||||
name = name[1:-1]
|
||||
|
||||
yield name, (start - last_end, end - start)
|
||||
last_end = end
|
||||
|
||||
|
||||
def _expand_substitutions(substitutions, value, path, ignore_missing, is_key=False):
|
||||
substituted = ""
|
||||
start_from = 0
|
||||
for name, (ignored_chars, length) in _find_tokens(value):
|
||||
if name not in substitutions:
|
||||
if not ignore_missing and "password" not in path:
|
||||
_LOGGER.warning(
|
||||
"Found '%s' (see %s) which looks like a substitution, but '%s' was "
|
||||
"not declared",
|
||||
orig_value,
|
||||
value,
|
||||
"->".join(str(x) for x in path),
|
||||
name,
|
||||
)
|
||||
i = j
|
||||
substituted += value[start_from : start_from + ignored_chars + length]
|
||||
start_from += ignored_chars + length
|
||||
continue
|
||||
|
||||
sub = substitutions[name]
|
||||
tail = value[j:]
|
||||
value = value[:i] + sub
|
||||
i = len(value)
|
||||
value += tail
|
||||
if isinstance(sub, str):
|
||||
substituted += value[start_from : start_from + ignored_chars] + sub
|
||||
start_from += ignored_chars + length
|
||||
continue
|
||||
|
||||
# orig_value can also already be a lambda with esp_range info, and only
|
||||
# a plain string is sent in orig_value
|
||||
if isinstance(orig_value, ESPHomeDataBase):
|
||||
if is_key:
|
||||
raise cv.Invalid(
|
||||
"Key substitution is only allowed for string types, "
|
||||
f"however {name!r} (used in {'->'.join(str(x) for x in path)}) "
|
||||
f"is of type {type(sub)}"
|
||||
)
|
||||
|
||||
if length != len(value):
|
||||
raise cv.Invalid(
|
||||
"String interpolation is only allowed for substitutions with "
|
||||
f"string types, however {name!r} (used in {'->'.join(str(x) for x in path)}) "
|
||||
f"is of type {type(sub)}"
|
||||
)
|
||||
|
||||
return sub
|
||||
|
||||
substituted += value[start_from:]
|
||||
|
||||
# value can also already be a lambda with esp_range info, and only
|
||||
# a plain string is sent in value
|
||||
if isinstance(value, ESPHomeDataBase):
|
||||
# even though string can get larger or smaller, the range should point
|
||||
# to original document marks
|
||||
return make_data_base(value, orig_value)
|
||||
return make_data_base(substituted, value)
|
||||
|
||||
return value
|
||||
return substituted
|
||||
|
||||
|
||||
def _substitute_item(substitutions, item, path, ignore_missing):
|
||||
def _substitute_item(substitutions, item, path, ignore_missing, is_key=False):
|
||||
if isinstance(item, list):
|
||||
for i, it in enumerate(item):
|
||||
sub = _substitute_item(substitutions, it, path + [i], ignore_missing)
|
||||
|
@ -91,8 +126,11 @@ def _substitute_item(substitutions, item, path, ignore_missing):
|
|||
elif isinstance(item, dict):
|
||||
replace_keys = []
|
||||
for k, v in item.items():
|
||||
# if we're not in the substitutions section, substitute keys
|
||||
if path or k != CONF_SUBSTITUTIONS:
|
||||
sub = _substitute_item(substitutions, k, path + [k], ignore_missing)
|
||||
sub = _substitute_item(
|
||||
substitutions, k, path + [k], ignore_missing, is_key=True
|
||||
)
|
||||
if sub is not None:
|
||||
replace_keys.append((k, sub))
|
||||
sub = _substitute_item(substitutions, v, path + [k], ignore_missing)
|
||||
|
@ -102,11 +140,13 @@ def _substitute_item(substitutions, item, path, ignore_missing):
|
|||
item[new] = merge_config(item.get(old), item.get(new))
|
||||
del item[old]
|
||||
elif isinstance(item, str):
|
||||
sub = _expand_substitutions(substitutions, item, path, ignore_missing)
|
||||
sub = _expand_substitutions(substitutions, item, path, ignore_missing, is_key)
|
||||
if sub != item:
|
||||
return sub
|
||||
elif isinstance(item, core.Lambda):
|
||||
sub = _expand_substitutions(substitutions, item.value, path, ignore_missing)
|
||||
sub = _expand_substitutions(
|
||||
substitutions, item.value, path, ignore_missing, is_key
|
||||
)
|
||||
if sub != item:
|
||||
item.value = sub
|
||||
return None
|
||||
|
@ -133,7 +173,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals
|
|||
sub = validate_substitution_key(key)
|
||||
if sub != key:
|
||||
replace_keys.append((key, sub))
|
||||
substitutions[key] = cv.string_strict(value)
|
||||
substitutions[key] = value
|
||||
for old, new in replace_keys:
|
||||
substitutions[new] = substitutions[old]
|
||||
del substitutions[old]
|
||||
|
|
|
@ -279,8 +279,6 @@ class ESPHomeLoaderMixin:
|
|||
if file is None:
|
||||
raise yaml.MarkedYAMLError("Must include 'file'", node.start_mark)
|
||||
vars = fields.get("vars")
|
||||
if vars:
|
||||
vars = {k: str(v) for k, v in vars.items()}
|
||||
return file, vars
|
||||
|
||||
def substitute_vars(config, vars):
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
sensor:
|
||||
- id: $included_sensor_id
|
||||
platform: template
|
||||
name: Inlcuded sensor
|
||||
- $included_sensor
|
|
@ -0,0 +1,33 @@
|
|||
esphome:
|
||||
name: test
|
||||
|
||||
substitutions:
|
||||
switch_def:
|
||||
platform: template
|
||||
name: $name
|
||||
optimistic: true
|
||||
nested_def: $switch_def
|
||||
double_nested_def: $nested_def
|
||||
name: Normal switch
|
||||
valid_interpolation: "1"
|
||||
included_sensor_name: Included sensor 2
|
||||
|
||||
packages:
|
||||
included: !include
|
||||
file: included.yaml
|
||||
vars:
|
||||
included_sensor_id: included_sensor
|
||||
included_sensor:
|
||||
name: $included_sensor_name
|
||||
id: included_sensor2
|
||||
platform: template
|
||||
|
||||
switch:
|
||||
- $double_nested_def
|
||||
- $switch_def
|
||||
- platform: template
|
||||
name: $name
|
||||
optimistic: true
|
||||
- platform: template
|
||||
name: Switch ${name} ${valid_interpolation} ${name} ${name}
|
||||
optimistic: true
|
Loading…
Reference in New Issue