fixes for variable css errors

This commit is contained in:
Will McGugan
2022-09-14 09:56:11 +01:00
parent fbec608198
commit 1a43c16c32
8 changed files with 61 additions and 56 deletions

View File

@@ -28,7 +28,7 @@ import rich.repr
from rich.console import Console, RenderableType from rich.console import Console, RenderableType
from rich.measure import Measurement from rich.measure import Measurement
from rich.protocol import is_renderable from rich.protocol import is_renderable
from rich.segment import Segments from rich.segment import Segment, Segments
from rich.traceback import Traceback from rich.traceback import Traceback
from . import ( from . import (
@@ -1016,10 +1016,11 @@ class App(Generic[ReturnType], DOMNode):
is_renderable(renderable) for renderable in renderables is_renderable(renderable) for renderable in renderables
), "Can only call panic with strings or Rich renderables" ), "Can only call panic with strings or Rich renderables"
pre_rendered = [ def render(renderable: RenderableType) -> list[Segment]:
Segments(self.console.render(renderable, self.console.options)) segments = list(self.console.render(renderable, self.console.options))
for renderable in renderables return segments
]
pre_rendered = [Segments(render(renderable)) for renderable in renderables]
self._exit_renderables.extend(pre_rendered) self._exit_renderables.extend(pre_rendered)
self._close_messages_no_wait() self._close_messages_no_wait()

View File

@@ -53,7 +53,7 @@ class BorderApp(App):
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
self.text.styles.border = ( self.text.styles.border = (
event.button.id, event.button.id,
self.stylesheet.variables["secondary"], self.stylesheet._variables["secondary"],
) )
self.bell() self.bell()

View File

@@ -2,8 +2,8 @@ from __future__ import annotations
from typing import Iterable from typing import Iterable
import rich.repr
from rich.console import Console, ConsoleOptions, RenderResult from rich.console import Console, ConsoleOptions, RenderResult
from rich.highlighter import ReprHighlighter from rich.highlighter import ReprHighlighter
from rich.markup import render from rich.markup import render
from rich.text import Text from rich.text import Text
@@ -42,6 +42,7 @@ class Example:
yield _markup_and_highlight(f" [dim]e.g. [/][i]{self.markup}[/]") yield _markup_and_highlight(f" [dim]e.g. [/][i]{self.markup}[/]")
@rich.repr.auto
class Bullet: class Bullet:
"""Renderable for a single 'bullet point' containing information and optionally some examples """Renderable for a single 'bullet point' containing information and optionally some examples
pertaining to that information. pertaining to that information.
@@ -59,10 +60,11 @@ class Bullet:
def __rich_console__( def __rich_console__(
self, console: Console, options: ConsoleOptions self, console: Console, options: ConsoleOptions
) -> RenderResult: ) -> RenderResult:
yield _markup_and_highlight(f"{self.markup}") yield _markup_and_highlight(self.markup)
yield from self.examples yield from self.examples
@rich.repr.auto
class HelpText: class HelpText:
"""Renderable for help text - the user is shown this when they """Renderable for help text - the user is shown this when they
encounter a style-related error (e.g. setting a style property to an invalid encounter a style-related error (e.g. setting a style property to an invalid

View File

@@ -1,10 +1,16 @@
from __future__ import annotations from __future__ import annotations
from functools import lru_cache from functools import lru_cache
from typing import cast, Iterable, NoReturn, Sequence from typing import Iterable, NoReturn, Sequence, cast
import rich.repr import rich.repr
from .._border import BorderValue, normalize_border_value
from .._duration import _duration_as_seconds
from .._easing import EASING
from ..color import Color, ColorParseError
from ..geometry import Spacing, SpacingDimensions, clamp
from ..suggestions import get_suggestion
from ._error_tools import friendly_list from ._error_tools import friendly_list
from ._help_renderables import HelpText from ._help_renderables import HelpText
from ._help_text import ( from ._help_text import (
@@ -33,34 +39,28 @@ from .constants import (
VALID_ALIGN_VERTICAL, VALID_ALIGN_VERTICAL,
VALID_BORDER, VALID_BORDER,
VALID_BOX_SIZING, VALID_BOX_SIZING,
VALID_EDGE,
VALID_DISPLAY, VALID_DISPLAY,
VALID_EDGE,
VALID_OVERFLOW, VALID_OVERFLOW,
VALID_VISIBILITY,
VALID_STYLE_FLAGS,
VALID_SCROLLBAR_GUTTER, VALID_SCROLLBAR_GUTTER,
VALID_STYLE_FLAGS,
VALID_TEXT_ALIGN, VALID_TEXT_ALIGN,
VALID_VISIBILITY,
) )
from .errors import DeclarationError, StyleValueError from .errors import DeclarationError, StyleValueError
from .model import Declaration from .model import Declaration
from .scalar import ( from .scalar import (
Scalar, Scalar,
ScalarOffset,
Unit,
ScalarError, ScalarError,
ScalarOffset,
ScalarParseError, ScalarParseError,
Unit,
percentage_string_to_float, percentage_string_to_float,
) )
from .styles import DockGroup, Styles from .styles import Styles
from .tokenize import Token from .tokenize import Token
from .transition import Transition from .transition import Transition
from .types import BoxSizing, Edge, Display, Overflow, Visibility, EdgeType from .types import BoxSizing, Display, Edge, EdgeType, Overflow, Visibility
from .._border import normalize_border_value, BorderValue
from ..color import Color, ColorParseError
from .._duration import _duration_as_seconds
from .._easing import EASING
from ..geometry import Spacing, SpacingDimensions, clamp
from ..suggestions import get_suggestion
def _join_tokens(tokens: Iterable[Token], joiner: str = "") -> str: def _join_tokens(tokens: Iterable[Token], joiner: str = "") -> str:
@@ -434,6 +434,7 @@ class StylesBuilder:
process_padding_left = _process_space_partial process_padding_left = _process_space_partial
def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue: def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
border_type: EdgeType = "solid" border_type: EdgeType = "solid"
border_color = Color(0, 255, 0) border_color = Color(0, 255, 0)
@@ -553,7 +554,7 @@ class StylesBuilder:
self.styles._rules["offset"] = ScalarOffset(x, y) self.styles._rules["offset"] = ScalarOffset(x, y)
def process_layout(self, name: str, tokens: list[Token]) -> None: def process_layout(self, name: str, tokens: list[Token]) -> None:
from ..layouts.factory import get_layout, MissingLayout from ..layouts.factory import MissingLayout, get_layout
if tokens: if tokens:
if len(tokens) != 1: if len(tokens) != 1:
@@ -602,7 +603,6 @@ class StylesBuilder:
if color is not None or alpha is not None: if color is not None or alpha is not None:
if alpha is not None: if alpha is not None:
color = (color or Color(255, 255, 255)).with_alpha(alpha) color = (color or Color(255, 255, 255)).with_alpha(alpha)
self.styles._rules[name] = color self.styles._rules[name] = color

View File

@@ -301,9 +301,7 @@ def substitute_references(
for _token in reference_tokens: for _token in reference_tokens:
yield _token.with_reference( yield _token.with_reference(
ReferencedBy( ReferencedBy(
name=ref_name, ref_name, ref_location, ref_length, token.code
location=ref_location,
length=ref_length,
) )
) )
else: else:
@@ -318,13 +316,10 @@ def substitute_references(
variable_tokens = variables[variable_name] variable_tokens = variables[variable_name]
ref_location = token.location ref_location = token.location
ref_length = len(token.value) ref_length = len(token.value)
for token in variable_tokens: ref_code = token.code
yield token.with_reference( for _token in variable_tokens:
ReferencedBy( yield _token.with_reference(
name=variable_name, ReferencedBy(variable_name, ref_location, ref_length, ref_code)
location=ref_location,
length=ref_length,
)
) )
else: else:
_unresolved(variable_name, variables.keys(), token) _unresolved(variable_name, variables.keys(), token)
@@ -336,6 +331,7 @@ def parse(
css: str, css: str,
path: str | PurePath, path: str | PurePath,
variables: dict[str, str] | None = None, variables: dict[str, str] | None = None,
variable_tokens: dict[str, list[Token]] | None = None,
is_default_rules: bool = False, is_default_rules: bool = False,
tie_breaker: int = 0, tie_breaker: int = 0,
) -> Iterable[RuleSet]: ) -> Iterable[RuleSet]:
@@ -349,7 +345,11 @@ def parse(
is_default_rules (bool): True if the rules we're extracting are is_default_rules (bool): True if the rules we're extracting are
default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS. default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS.
""" """
variable_tokens = tokenize_values(variables or {})
reference_tokens = tokenize_values(variables) if variables is not None else {}
if variable_tokens:
reference_tokens.update(variable_tokens)
tokens = iter(substitute_references(tokenize(css, path), variable_tokens)) tokens = iter(substitute_references(tokenize(css, path), variable_tokens))
while True: while True:
token = next(tokens, None) token = next(tokens, None)

View File

@@ -2,10 +2,9 @@ from __future__ import annotations
import os import os
from collections import defaultdict from collections import defaultdict
from functools import partial
from operator import itemgetter from operator import itemgetter
from pathlib import Path, PurePath from pathlib import Path, PurePath
from typing import Iterable, NamedTuple, cast from typing import Iterable, NamedTuple, Sequence, cast
import rich.repr import rich.repr
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
@@ -39,20 +38,15 @@ class StylesheetParseError(StylesheetError):
class StylesheetErrors: class StylesheetErrors:
def __init__( def __init__(self, rules: list[RuleSet]) -> None:
self, rules: list[RuleSet], variables: dict[str, str] | None = None
) -> None:
self.rules = rules self.rules = rules
self.variables: dict[str, str] = {} self.variables: dict[str, str] = {}
self._css_variables: dict[str, list[Token]] = {}
if variables:
self.set_variables(variables)
@classmethod @classmethod
def _get_snippet(cls, code: str, line_no: int) -> RenderableType: def _get_snippet(cls, code: str, line_no: int) -> RenderableType:
syntax = Syntax( syntax = Syntax(
code, code,
lexer="scss", lexer="sass",
theme="ansi_light", theme="ansi_light",
line_numbers=True, line_numbers=True,
indent_guides=True, indent_guides=True,
@@ -61,11 +55,6 @@ class StylesheetErrors:
) )
return syntax return syntax
def set_variables(self, variable_map: dict[str, str]) -> None:
"""Pre-populate CSS variables."""
self.variables.update(variable_map)
self._css_variables = tokenize_values(self.variables)
def __rich_console__( def __rich_console__(
self, console: Console, options: ConsoleOptions self, console: Console, options: ConsoleOptions
) -> RenderResult: ) -> RenderResult:
@@ -105,7 +94,10 @@ class StylesheetErrors:
title = Text.assemble(Text("Error at ", style="bold red"), path_text) title = Text.assemble(Text("Error at ", style="bold red"), path_text)
yield "" yield ""
yield Panel( yield Panel(
self._get_snippet(token.code, line_no), self._get_snippet(
token.referenced_by.code if token.referenced_by else token.code,
line_no,
),
title=title, title=title,
title_align="left", title_align="left",
border_style="red", border_style="red",
@@ -138,13 +130,20 @@ class Stylesheet:
def __init__(self, *, variables: dict[str, str] | None = None) -> None: def __init__(self, *, variables: dict[str, str] | None = None) -> None:
self._rules: list[RuleSet] = [] self._rules: list[RuleSet] = []
self._rules_map: dict[str, list[RuleSet]] | None = None self._rules_map: dict[str, list[RuleSet]] | None = None
self.variables = variables or {} self._variables = variables or {}
self.__variable_tokens: dict[str, list[Token]] | None = None
self.source: dict[str, CssSource] = {} self.source: dict[str, CssSource] = {}
self._require_parse = False self._require_parse = False
def __rich_repr__(self) -> rich.repr.Result: def __rich_repr__(self) -> rich.repr.Result:
yield list(self.source.keys()) yield list(self.source.keys())
@property
def _variable_tokens(self) -> dict[str, list[Token]]:
if self.__variable_tokens is None:
self.__variable_tokens = tokenize_values(self._variables)
return self.__variable_tokens
@property @property
def rules(self) -> list[RuleSet]: def rules(self) -> list[RuleSet]:
"""List of rule sets. """List of rule sets.
@@ -183,7 +182,7 @@ class Stylesheet:
Returns: Returns:
Stylesheet: New stylesheet. Stylesheet: New stylesheet.
""" """
stylesheet = Stylesheet(variables=self.variables.copy()) stylesheet = Stylesheet(variables=self._variables.copy())
stylesheet.source = self.source.copy() stylesheet.source = self.source.copy()
return stylesheet return stylesheet
@@ -193,7 +192,8 @@ class Stylesheet:
Args: Args:
variables (dict[str, str]): A mapping of name to variable. variables (dict[str, str]): A mapping of name to variable.
""" """
self.variables = variables self._variables = variables
self._variables_tokens = None
def _parse_rules( def _parse_rules(
self, self,
@@ -222,7 +222,7 @@ class Stylesheet:
parse( parse(
css, css,
path, path,
variables=self.variables, variable_tokens=self._variable_tokens,
is_default_rules=is_default_rules, is_default_rules=is_default_rules,
tie_breaker=tie_breaker, tie_breaker=tie_breaker,
) )
@@ -317,7 +317,7 @@ class Stylesheet:
""" """
# Do this in a fresh Stylesheet so if there are errors we don't break self. # Do this in a fresh Stylesheet so if there are errors we don't break self.
stylesheet = Stylesheet(variables=self.variables) stylesheet = Stylesheet(variables=self._variables)
for path, (css, is_defaults, tie_breaker) in self.source.items(): for path, (css, is_defaults, tie_breaker) in self.source.items():
stylesheet.add_source( stylesheet.add_source(
css, path, is_default_css=is_defaults, tie_breaker=tie_breaker css, path, is_default_css=is_defaults, tie_breaker=tie_breaker

View File

@@ -118,6 +118,7 @@ class ReferencedBy(NamedTuple):
name: str name: str
location: tuple[int, int] location: tuple[int, int]
length: int length: int
code: str
@rich.repr.auto @rich.repr.auto
@@ -209,6 +210,7 @@ class Tokenizer:
message, message,
) )
iter_groups = iter(match.groups()) iter_groups = iter(match.groups())
next(iter_groups) next(iter_groups)
for name, value in zip(expect.names, iter_groups): for name, value in zip(expect.names, iter_groups):

View File

@@ -36,7 +36,7 @@ class Button(Widget, can_focus=True):
height: 3; height: 3;
background: $panel; background: $panel;
color: $text; color: $text;
border: none; border: none;
border-top: tall $panel-lighten-2; border-top: tall $panel-lighten-2;
border-bottom: tall $panel-darken-3; border-bottom: tall $panel-darken-3;
content-align: center middle; content-align: center middle;