mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 09:17:46 +01:00
Add config validator location (#1490)
* show validation source location for id * show validation source location for lambda * refactor lambda #line position * account content offset on made lambdas * lint * remove redundant check
This commit is contained in:
parent
de3377132d
commit
28e39f7f76
5 changed files with 30 additions and 31 deletions
|
@ -268,6 +268,8 @@ class Config(OrderedDict):
|
||||||
data = data[item_index]
|
data = data[item_index]
|
||||||
except (KeyError, IndexError, TypeError):
|
except (KeyError, IndexError, TypeError):
|
||||||
return doc_range
|
return doc_range
|
||||||
|
if isinstance(data, core.ID):
|
||||||
|
data = data.id
|
||||||
if isinstance(data, ESPHomeDataBase) and data.esp_range is not None:
|
if isinstance(data, ESPHomeDataBase) and data.esp_range is not None:
|
||||||
doc_range = data.esp_range
|
doc_range = data.esp_range
|
||||||
|
|
||||||
|
@ -700,6 +702,8 @@ def line_info(obj, highlight=True):
|
||||||
"""Display line config source."""
|
"""Display line config source."""
|
||||||
if not highlight:
|
if not highlight:
|
||||||
return None
|
return None
|
||||||
|
if isinstance(obj, core.ID):
|
||||||
|
obj = obj.id
|
||||||
if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None:
|
if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None:
|
||||||
mark = obj.esp_range.start_mark
|
mark = obj.esp_range.start_mark
|
||||||
source = "[source {}:{}]".format(mark.document, mark.line + 1)
|
source = "[source {}:{}]".format(mark.document, mark.line + 1)
|
||||||
|
|
|
@ -17,10 +17,10 @@ from esphome.const import ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TO
|
||||||
CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \
|
CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \
|
||||||
CONF_TYPE, CONF_PACKAGES
|
CONF_TYPE, CONF_PACKAGES
|
||||||
from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \
|
from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \
|
||||||
TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes, DocumentLocation
|
TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes
|
||||||
from esphome.helpers import list_starts_with, add_class_to_obj
|
from esphome.helpers import list_starts_with, add_class_to_obj
|
||||||
from esphome.voluptuous_schema import _Schema
|
from esphome.voluptuous_schema import _Schema
|
||||||
from esphome.yaml_util import ESPHomeDataBase
|
from esphome.yaml_util import make_data_base
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -983,11 +983,7 @@ LAMBDA_ENTITY_ID_PROG = re.compile(r'id\(\s*([a-zA-Z0-9_]+\.[.a-zA-Z0-9_]+)\s*\)
|
||||||
def lambda_(value):
|
def lambda_(value):
|
||||||
"""Coerce this configuration option to a lambda."""
|
"""Coerce this configuration option to a lambda."""
|
||||||
if not isinstance(value, Lambda):
|
if not isinstance(value, Lambda):
|
||||||
start_mark = None
|
value = make_data_base(Lambda(string_strict(value)), value)
|
||||||
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
|
||||||
start_mark = DocumentLocation.copy(value.esp_range.start_mark)
|
|
||||||
start_mark.line += value.content_offset
|
|
||||||
value = Lambda(string_strict(value), start_mark)
|
|
||||||
entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value)
|
entity_id_parts = re.split(LAMBDA_ENTITY_ID_PROG, value.value)
|
||||||
if len(entity_id_parts) != 1:
|
if len(entity_id_parts) != 1:
|
||||||
entity_ids = ' '.join("'{}'".format(entity_id_parts[i])
|
entity_ids = ' '.join("'{}'".format(entity_id_parts[i])
|
||||||
|
@ -1182,8 +1178,8 @@ class OnlyWith(Optional):
|
||||||
# pylint: disable=unsupported-membership-test
|
# pylint: disable=unsupported-membership-test
|
||||||
if (self._component in CORE.raw_config or
|
if (self._component in CORE.raw_config or
|
||||||
(CONF_PACKAGES in CORE.raw_config and
|
(CONF_PACKAGES in CORE.raw_config and
|
||||||
self._component in
|
self._component in
|
||||||
{list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()})):
|
{list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()})):
|
||||||
return self._default
|
return self._default
|
||||||
return vol.UNDEFINED
|
return vol.UNDEFINED
|
||||||
|
|
||||||
|
|
|
@ -227,7 +227,7 @@ LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)')
|
||||||
|
|
||||||
|
|
||||||
class Lambda:
|
class Lambda:
|
||||||
def __init__(self, value, start_mark=None):
|
def __init__(self, value):
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
if isinstance(value, Lambda):
|
if isinstance(value, Lambda):
|
||||||
self._value = value._value
|
self._value = value._value
|
||||||
|
@ -235,7 +235,6 @@ class Lambda:
|
||||||
self._value = value
|
self._value = value
|
||||||
self._parts = None
|
self._parts = None
|
||||||
self._requires_ids = None
|
self._requires_ids = None
|
||||||
self._source_location = start_mark
|
|
||||||
|
|
||||||
# https://stackoverflow.com/a/241506/229052
|
# https://stackoverflow.com/a/241506/229052
|
||||||
def comment_remover(self, text):
|
def comment_remover(self, text):
|
||||||
|
@ -278,10 +277,6 @@ class Lambda:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'Lambda<{self.value}>'
|
return f'Lambda<{self.value}>'
|
||||||
|
|
||||||
@property
|
|
||||||
def source_location(self):
|
|
||||||
return self._source_location
|
|
||||||
|
|
||||||
|
|
||||||
class ID:
|
class ID:
|
||||||
def __init__(self, id, is_declaration=False, type=None, is_manual=None):
|
def __init__(self, id, is_declaration=False, type=None, is_manual=None):
|
||||||
|
@ -339,14 +334,6 @@ class DocumentLocation:
|
||||||
mark.column
|
mark.column
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def copy(cls, location):
|
|
||||||
return cls(
|
|
||||||
location.document,
|
|
||||||
location.line,
|
|
||||||
location.column
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.document} {self.line}:{self.column}'
|
return f'{self.document} {self.line}:{self.column}'
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import abc
|
||||||
import inspect
|
import inspect
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
from esphome.yaml_util import ESPHomeDataBase
|
||||||
|
|
||||||
# pylint: disable=unused-import, wrong-import-order
|
# pylint: disable=unused-import, wrong-import-order
|
||||||
from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence
|
from typing import Any, Generator, List, Optional, Tuple, Type, Union, Sequence
|
||||||
|
@ -560,7 +561,13 @@ def process_lambda(
|
||||||
else:
|
else:
|
||||||
parts[i * 3 + 1] = var
|
parts[i * 3 + 1] = var
|
||||||
parts[i * 3 + 2] = ''
|
parts[i * 3 + 2] = ''
|
||||||
yield LambdaExpression(parts, parameters, capture, return_type, value.source_location)
|
|
||||||
|
if isinstance(value, ESPHomeDataBase) and value.esp_range is not None:
|
||||||
|
location = value.esp_range.start_mark
|
||||||
|
location.line += value.content_offset
|
||||||
|
else:
|
||||||
|
location = None
|
||||||
|
yield LambdaExpression(parts, parameters, capture, return_type, location)
|
||||||
|
|
||||||
|
|
||||||
def is_template(value):
|
def is_template(value):
|
||||||
|
|
|
@ -12,7 +12,7 @@ import yaml.constructor
|
||||||
from esphome import core
|
from esphome import core
|
||||||
from esphome.config_helpers import read_config_file
|
from esphome.config_helpers import read_config_file
|
||||||
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, \
|
from esphome.core import EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, \
|
||||||
DocumentRange, DocumentLocation
|
DocumentRange
|
||||||
from esphome.helpers import add_class_to_obj
|
from esphome.helpers import add_class_to_obj
|
||||||
from esphome.util import OrderedDict, filter_yaml_files
|
from esphome.util import OrderedDict, filter_yaml_files
|
||||||
|
|
||||||
|
@ -42,14 +42,22 @@ class ESPHomeDataBase:
|
||||||
if node.style is not None and node.style in '|>':
|
if node.style is not None and node.style in '|>':
|
||||||
self._content_offset = 1
|
self._content_offset = 1
|
||||||
|
|
||||||
|
def from_database(self, database):
|
||||||
|
# pylint: disable=attribute-defined-outside-init
|
||||||
|
self._esp_range = database.esp_range
|
||||||
|
self._content_offset = database.content_offset
|
||||||
|
|
||||||
|
|
||||||
class ESPForceValue:
|
class ESPForceValue:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def make_data_base(value):
|
def make_data_base(value, from_database: ESPHomeDataBase = None):
|
||||||
try:
|
try:
|
||||||
return add_class_to_obj(value, ESPHomeDataBase)
|
value = add_class_to_obj(value, ESPHomeDataBase)
|
||||||
|
if from_database is not None:
|
||||||
|
value.from_database(from_database)
|
||||||
|
return value
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Adding class failed, ignore error
|
# Adding class failed, ignore error
|
||||||
return value
|
return value
|
||||||
|
@ -265,10 +273,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_lambda(self, node):
|
def construct_lambda(self, node):
|
||||||
start_mark = DocumentLocation.from_mark(node.start_mark)
|
return Lambda(str(node.value))
|
||||||
if node.style is not None and node.style in '|>':
|
|
||||||
start_mark.line += 1
|
|
||||||
return Lambda(str(node.value), start_mark)
|
|
||||||
|
|
||||||
@_add_data_ref
|
@_add_data_ref
|
||||||
def construct_force(self, node):
|
def construct_force(self, node):
|
||||||
|
|
Loading…
Reference in a new issue