mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 01:07:45 +01:00
Allow substitutions to be valid names (#4726)
This commit is contained in:
parent
77695aa55b
commit
b3ed988119
4 changed files with 69 additions and 14 deletions
|
@ -1,19 +1,14 @@
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import core
|
from esphome import core
|
||||||
from esphome.const import CONF_SUBSTITUTIONS
|
from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS
|
||||||
from esphome.yaml_util import ESPHomeDataBase, make_data_base
|
from esphome.yaml_util import ESPHomeDataBase, make_data_base
|
||||||
from esphome.config_helpers import merge_config
|
from esphome.config_helpers import merge_config
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_substitution_key(value):
|
def validate_substitution_key(value):
|
||||||
value = cv.string(value)
|
value = cv.string(value)
|
||||||
|
@ -42,12 +37,6 @@ async def to_code(config):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=consider-using-f-string
|
|
||||||
VARIABLE_PROG = re.compile(
|
|
||||||
"\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _expand_substitutions(substitutions, value, path, ignore_missing):
|
def _expand_substitutions(substitutions, value, path, ignore_missing):
|
||||||
if "$" not in value:
|
if "$" not in value:
|
||||||
return value
|
return value
|
||||||
|
@ -56,7 +45,7 @@ def _expand_substitutions(substitutions, value, path, ignore_missing):
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
m = VARIABLE_PROG.search(value, i)
|
m = cv.VARIABLE_PROG.search(value, i)
|
||||||
if not m:
|
if not m:
|
||||||
# Nothing more to match. Done
|
# Nothing more to match. Done
|
||||||
break
|
break
|
||||||
|
|
|
@ -53,6 +53,7 @@ from esphome.const import (
|
||||||
KEY_TARGET_PLATFORM,
|
KEY_TARGET_PLATFORM,
|
||||||
TYPE_GIT,
|
TYPE_GIT,
|
||||||
TYPE_LOCAL,
|
TYPE_LOCAL,
|
||||||
|
VALID_SUBSTITUTIONS_CHARACTERS,
|
||||||
)
|
)
|
||||||
from esphome.core import (
|
from esphome.core import (
|
||||||
CORE,
|
CORE,
|
||||||
|
@ -79,6 +80,11 @@ from esphome.yaml_util import make_data_base
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# pylint: disable=consider-using-f-string
|
||||||
|
VARIABLE_PROG = re.compile(
|
||||||
|
"\\$([{0}]+|\\{{[{0}]*\\}})".format(VALID_SUBSTITUTIONS_CHARACTERS)
|
||||||
|
)
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
Schema = _Schema
|
Schema = _Schema
|
||||||
|
@ -265,6 +271,14 @@ def alphanumeric(value):
|
||||||
|
|
||||||
def valid_name(value):
|
def valid_name(value):
|
||||||
value = string_strict(value)
|
value = string_strict(value)
|
||||||
|
|
||||||
|
if CORE.vscode:
|
||||||
|
# If the value is a substitution, it can't be validated until the substitution
|
||||||
|
# is actually made.
|
||||||
|
sub_match = VARIABLE_PROG.search(value)
|
||||||
|
if sub_match:
|
||||||
|
return value
|
||||||
|
|
||||||
for c in value:
|
for c in value:
|
||||||
if c not in ALLOWED_NAME_CHARS:
|
if c not in ALLOWED_NAME_CHARS:
|
||||||
raise Invalid(
|
raise Invalid(
|
||||||
|
@ -447,6 +461,14 @@ def validate_id_name(value):
|
||||||
raise Invalid(
|
raise Invalid(
|
||||||
"Dashes are not supported in IDs, please use underscores instead."
|
"Dashes are not supported in IDs, please use underscores instead."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if CORE.vscode:
|
||||||
|
# If the value is a substitution, it can't be validated until the substitution
|
||||||
|
# is actually made
|
||||||
|
sub_match = VARIABLE_PROG.match(value)
|
||||||
|
if sub_match:
|
||||||
|
return value
|
||||||
|
|
||||||
valid_chars = f"{ascii_letters + digits}_"
|
valid_chars = f"{ascii_letters + digits}_"
|
||||||
for char in value:
|
for char in value:
|
||||||
if char not in valid_chars:
|
if char not in valid_chars:
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
__version__ = "2023.6.0-dev"
|
__version__ = "2023.6.0-dev"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
|
||||||
|
)
|
||||||
|
|
||||||
PLATFORM_ESP32 = "esp32"
|
PLATFORM_ESP32 = "esp32"
|
||||||
PLATFORM_ESP8266 = "esp8266"
|
PLATFORM_ESP8266 = "esp8266"
|
||||||
|
|
|
@ -6,7 +6,7 @@ from hypothesis.strategies import one_of, text, integers, builds
|
||||||
|
|
||||||
from esphome import config_validation
|
from esphome import config_validation
|
||||||
from esphome.config_validation import Invalid
|
from esphome.config_validation import Invalid
|
||||||
from esphome.core import Lambda, HexInt
|
from esphome.core import CORE, Lambda, HexInt
|
||||||
|
|
||||||
|
|
||||||
def test_check_not_templatable__invalid():
|
def test_check_not_templatable__invalid():
|
||||||
|
@ -40,6 +40,47 @@ def test_valid_name__invalid(value):
|
||||||
config_validation.valid_name(value)
|
config_validation.valid_name(value)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", ("${name}", "${NAME}", "$NAME", "${name}_name"))
|
||||||
|
def test_valid_name__substitution_valid(value):
|
||||||
|
CORE.vscode = True
|
||||||
|
actual = config_validation.valid_name(value)
|
||||||
|
assert actual == value
|
||||||
|
|
||||||
|
CORE.vscode = False
|
||||||
|
with pytest.raises(Invalid):
|
||||||
|
actual = config_validation.valid_name(value)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", ("{NAME}", "${A NAME}"))
|
||||||
|
def test_valid_name__substitution_like_invalid(value):
|
||||||
|
with pytest.raises(Invalid):
|
||||||
|
config_validation.valid_name(value)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", ("myid", "anID", "SOME_ID_test", "MYID_99"))
|
||||||
|
def test_validate_id_name__valid(value):
|
||||||
|
actual = config_validation.validate_id_name(value)
|
||||||
|
|
||||||
|
assert actual == value
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", ("id of mine", "id-4", "{name_id}", "id::name"))
|
||||||
|
def test_validate_id_name__invalid(value):
|
||||||
|
with pytest.raises(Invalid):
|
||||||
|
config_validation.validate_id_name(value)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", ("${id}", "${ID}", "${ID}_test_1", "$MYID"))
|
||||||
|
def test_validate_id_name__substitution_valid(value):
|
||||||
|
CORE.vscode = True
|
||||||
|
actual = config_validation.validate_id_name(value)
|
||||||
|
assert actual == value
|
||||||
|
|
||||||
|
CORE.vscode = False
|
||||||
|
with pytest.raises(Invalid):
|
||||||
|
config_validation.validate_id_name(value)
|
||||||
|
|
||||||
|
|
||||||
@given(one_of(integers(), text()))
|
@given(one_of(integers(), text()))
|
||||||
def test_string__valid(value):
|
def test_string__valid(value):
|
||||||
actual = config_validation.string(value)
|
actual = config_validation.string(value)
|
||||||
|
|
Loading…
Reference in a new issue