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(
|
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
|
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:
|
if "$" not in value:
|
||||||
return value
|
return
|
||||||
|
|
||||||
orig_value = value
|
last_end = 0
|
||||||
|
for match in cv.VARIABLE_PROG.finditer(value):
|
||||||
i = 0
|
name = match.group(1)
|
||||||
while True:
|
start, end = match.span(0)
|
||||||
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)
|
|
||||||
if name.startswith("{") and name.endswith("}"):
|
if name.startswith("{") and name.endswith("}"):
|
||||||
name = name[1:-1]
|
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 name not in substitutions:
|
||||||
if not ignore_missing and "password" not in path:
|
if not ignore_missing and "password" not in path:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Found '%s' (see %s) which looks like a substitution, but '%s' was "
|
"Found '%s' (see %s) which looks like a substitution, but '%s' was "
|
||||||
"not declared",
|
"not declared",
|
||||||
orig_value,
|
value,
|
||||||
"->".join(str(x) for x in path),
|
"->".join(str(x) for x in path),
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
i = j
|
substituted += value[start_from : start_from + ignored_chars + length]
|
||||||
|
start_from += ignored_chars + length
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sub = substitutions[name]
|
sub = substitutions[name]
|
||||||
tail = value[j:]
|
if isinstance(sub, str):
|
||||||
value = value[:i] + sub
|
substituted += value[start_from : start_from + ignored_chars] + sub
|
||||||
i = len(value)
|
start_from += ignored_chars + length
|
||||||
value += tail
|
continue
|
||||||
|
|
||||||
# orig_value can also already be a lambda with esp_range info, and only
|
if is_key:
|
||||||
# a plain string is sent in orig_value
|
raise cv.Invalid(
|
||||||
if isinstance(orig_value, ESPHomeDataBase):
|
"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
|
# even though string can get larger or smaller, the range should point
|
||||||
# to original document marks
|
# 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):
|
if isinstance(item, list):
|
||||||
for i, it in enumerate(item):
|
for i, it in enumerate(item):
|
||||||
sub = _substitute_item(substitutions, it, path + [i], ignore_missing)
|
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):
|
elif isinstance(item, dict):
|
||||||
replace_keys = []
|
replace_keys = []
|
||||||
for k, v in item.items():
|
for k, v in item.items():
|
||||||
|
# if we're not in the substitutions section, substitute keys
|
||||||
if path or k != CONF_SUBSTITUTIONS:
|
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:
|
if sub is not None:
|
||||||
replace_keys.append((k, sub))
|
replace_keys.append((k, sub))
|
||||||
sub = _substitute_item(substitutions, v, path + [k], ignore_missing)
|
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))
|
item[new] = merge_config(item.get(old), item.get(new))
|
||||||
del item[old]
|
del item[old]
|
||||||
elif isinstance(item, str):
|
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:
|
if sub != item:
|
||||||
return sub
|
return sub
|
||||||
elif isinstance(item, core.Lambda):
|
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:
|
if sub != item:
|
||||||
item.value = sub
|
item.value = sub
|
||||||
return None
|
return None
|
||||||
|
@ -133,7 +173,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals
|
||||||
sub = validate_substitution_key(key)
|
sub = validate_substitution_key(key)
|
||||||
if sub != key:
|
if sub != key:
|
||||||
replace_keys.append((key, sub))
|
replace_keys.append((key, sub))
|
||||||
substitutions[key] = cv.string_strict(value)
|
substitutions[key] = value
|
||||||
for old, new in replace_keys:
|
for old, new in replace_keys:
|
||||||
substitutions[new] = substitutions[old]
|
substitutions[new] = substitutions[old]
|
||||||
del substitutions[old]
|
del substitutions[old]
|
||||||
|
|
|
@ -279,8 +279,6 @@ class ESPHomeLoaderMixin:
|
||||||
if file is None:
|
if file is None:
|
||||||
raise yaml.MarkedYAMLError("Must include 'file'", node.start_mark)
|
raise yaml.MarkedYAMLError("Must include 'file'", node.start_mark)
|
||||||
vars = fields.get("vars")
|
vars = fields.get("vars")
|
||||||
if vars:
|
|
||||||
vars = {k: str(v) for k, v in vars.items()}
|
|
||||||
return file, vars
|
return file, vars
|
||||||
|
|
||||||
def substitute_vars(config, 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