esphome/esphomeyaml/components/substitutions.py
2019-01-06 19:38:23 +01:00

136 lines
4.1 KiB
Python

import logging
import re
import voluptuous as vol
from esphomeyaml import core
import esphomeyaml.config_validation as cv
from esphomeyaml.core import EsphomeyamlError
_LOGGER = logging.getLogger(__name__)
CONF_SUBSTITUTIONS = 'substitutions'
VALID_SUBSTITUTIONS_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \
'0123456789_'
def validate_substitution_key(value):
value = cv.string(value)
if not value:
raise vol.Invalid("Substitution key must not be empty")
if value[0] == '$':
value = value[1:]
if value[0].isdigit():
raise vol.Invalid("First character in substitutions cannot be a digit.")
for char in value:
if char not in VALID_SUBSTITUTIONS_CHARACTERS:
raise vol.Invalid(
u"Substitution must only consist of upper/lowercase characters, the underscore "
u"and numbers. The character '{}' cannot be used".format(char))
return value
CONFIG_SCHEMA = vol.Schema({
validate_substitution_key: cv.string_strict,
})
def to_code(config):
pass
VARIABLE_PROG = re.compile('\\$([{0}]+|\\{{[{0}]*\\}})'.format(VALID_SUBSTITUTIONS_CHARACTERS))
def _expand_substitutions(substitutions, value, path):
if u'$' not in value:
return value
orig_value = value
i = 0
while True:
m = 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(u'{') and name.endswith(u'}'):
name = name[1:-1]
if name not in substitutions:
_LOGGER.warning(u"Found '%s' (see %s) which looks like a substitution, but '%s' was "
u"not declared", orig_value, u'->'.join(str(x) for x in path), name)
i = j
continue
sub = substitutions[name]
tail = value[j:]
value = value[:i] + sub
i = len(value)
value += tail
return value
def _substitute_item(substitutions, item, path):
if isinstance(item, list):
for i, it in enumerate(item):
sub = _substitute_item(substitutions, it, path + [i])
if sub is not None:
item[i] = sub
elif isinstance(item, dict):
replace_keys = []
for k, v in item.items():
if path or k != CONF_SUBSTITUTIONS:
sub = _substitute_item(substitutions, k, path + [k])
if sub is not None:
replace_keys.append((k, sub))
sub = _substitute_item(substitutions, v, path + [k])
if sub is not None:
item[k] = sub
for old, new in replace_keys:
item[new] = item[old]
del item[old]
elif isinstance(item, str):
sub = _expand_substitutions(substitutions, item, path)
if sub != item:
return sub
elif isinstance(item, core.Lambda):
sub = _expand_substitutions(substitutions, item.value, path)
if sub != item:
item.value = sub
return None
def do_substitution_pass(config):
if CONF_SUBSTITUTIONS not in config:
return config
substitutions = config[CONF_SUBSTITUTIONS]
if not isinstance(substitutions, dict):
raise EsphomeyamlError(u"Substitutions must be a key to value mapping, got {}"
u"".format(type(substitutions)))
key = ''
try:
replace_keys = []
for key, value in substitutions.items():
sub = validate_substitution_key(key)
if sub != key:
replace_keys.append((key, sub))
substitutions[key] = cv.string_strict(value)
for old, new in replace_keys:
substitutions[new] = substitutions[old]
del substitutions[old]
except vol.Invalid as err:
err.path.append(key)
raise EsphomeyamlError(u"Error while parsing substitutions: {}".format(err))
config[CONF_SUBSTITUTIONS] = substitutions
_substitute_item(substitutions, config, [])
return config