mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'css' into inline-styles-view
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
/* CSS file for basic.py */
|
||||
|
||||
$primary: #20639b;
|
||||
|
||||
App > View {
|
||||
layout: dock;
|
||||
docks: side=left/1;
|
||||
text: on #20639b;
|
||||
text: on $primary;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
@@ -26,7 +28,7 @@ App > View {
|
||||
}
|
||||
|
||||
#content {
|
||||
text: white on #20639b;
|
||||
text: white on $primary;
|
||||
border-bottom: hkey #0f2b41;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/* CSS file for basic.py */
|
||||
|
||||
App > View {
|
||||
docks: side=left/1;
|
||||
text: on #20639b;
|
||||
}
|
||||
|
||||
Widget:hover {
|
||||
outline: heavy;
|
||||
text: bold !important;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
text: #09312e on #3caea3;
|
||||
dock: side;
|
||||
width: 30;
|
||||
offset-x: -100%;
|
||||
transition: offset 500ms in_out_cubic;
|
||||
border-right: outer #09312e;
|
||||
}
|
||||
|
||||
#sidebar.-active {
|
||||
offset-x: 0;
|
||||
}
|
||||
|
||||
#header {
|
||||
text: white on #173f5f;
|
||||
height: 3;
|
||||
border: hkey;
|
||||
}
|
||||
|
||||
#header.-visible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#content {
|
||||
text: white on #20639b;
|
||||
border-bottom: hkey #0f2b41;
|
||||
}
|
||||
|
||||
#footer {
|
||||
text: #3a3009 on #f6d55c;
|
||||
height: 3;
|
||||
}
|
||||
@@ -1,23 +1,32 @@
|
||||
from rich.console import RenderableType
|
||||
from rich.panel import Panel
|
||||
|
||||
from textual.app import App
|
||||
from textual.widget import Widget
|
||||
|
||||
|
||||
class PanelWidget(Widget):
|
||||
def render(self) -> RenderableType:
|
||||
return Panel("hello world!", title="Title")
|
||||
|
||||
|
||||
class BasicApp(App):
|
||||
"""A basic app demonstrating CSS"""
|
||||
"""Sandbox application used for testing/development by Textual developers"""
|
||||
|
||||
def on_load(self):
|
||||
"""Bind keys here."""
|
||||
self.bind("tab", "toggle_class('#sidebar', '-active')")
|
||||
self.bind("a", "toggle_class('#header', '-visible')")
|
||||
self.bind("c", "toggle_class('#content', '-content-visible')")
|
||||
|
||||
def on_mount(self):
|
||||
"""Build layout here."""
|
||||
self.mount(
|
||||
header=Widget(),
|
||||
content=Widget(),
|
||||
content=PanelWidget(),
|
||||
footer=Widget(),
|
||||
sidebar=Widget(),
|
||||
)
|
||||
|
||||
|
||||
BasicApp.run(css_file="dev_sandbox.css", watch_css=True, log="textual.log")
|
||||
BasicApp.run(css_file="dev_sandbox.scss", watch_css=True, log="textual.log")
|
||||
|
||||
58
examples/dev_sandbox.scss
Normal file
58
examples/dev_sandbox.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
/* CSS file for dev_sandbox.py */
|
||||
|
||||
$text: #f0f0f0;
|
||||
$primary: #021720;
|
||||
$secondary:#95d52a;
|
||||
$background: #262626;
|
||||
|
||||
$primary-style: $text on $background;
|
||||
$animation-speed: 500ms;
|
||||
$animation: offset $animation-speed in_out_cubic;
|
||||
|
||||
App > View {
|
||||
docks: side=left/1;
|
||||
text: on $background;
|
||||
}
|
||||
|
||||
Widget:hover {
|
||||
outline: heavy;
|
||||
text: bold !important;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
text: $primary-style;
|
||||
dock: side;
|
||||
width: 30;
|
||||
offset-x: -100%;
|
||||
transition: $animation;
|
||||
border-right: outer $secondary;
|
||||
}
|
||||
|
||||
#sidebar.-active {
|
||||
offset-x: 0;
|
||||
}
|
||||
|
||||
#header {
|
||||
text: $text on $primary;
|
||||
height: 3;
|
||||
border-bottom: hkey $secondary;
|
||||
}
|
||||
|
||||
#header.-visible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#content {
|
||||
text: $text on $background;
|
||||
offset-y: -3;
|
||||
}
|
||||
|
||||
#content.-content-visible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#footer {
|
||||
text: $text on $primary;
|
||||
height: 3;
|
||||
border-top: hkey $secondary;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ from .transition import Transition
|
||||
from .types import Edge, Display, Visibility
|
||||
from .._duration import _duration_as_seconds
|
||||
from .._easing import EASING
|
||||
from .._loop import loop_last
|
||||
from ..geometry import Spacing, SpacingDimensions
|
||||
|
||||
|
||||
@@ -66,7 +67,7 @@ class StylesBuilder:
|
||||
|
||||
def process_display(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||
for token in tokens:
|
||||
name, value, _, _, location = token
|
||||
name, value, _, _, location, _ = token
|
||||
|
||||
if name == "token":
|
||||
value = value.lower()
|
||||
@@ -109,7 +110,7 @@ class StylesBuilder:
|
||||
self, name: str, tokens: list[Token], important: bool
|
||||
) -> None:
|
||||
for token in tokens:
|
||||
name, value, _, _, location = token
|
||||
name, value, _, _, location, _ = token
|
||||
if name == "token":
|
||||
value = value.lower()
|
||||
if value in VALID_VISIBILITY:
|
||||
@@ -127,7 +128,7 @@ class StylesBuilder:
|
||||
space: list[int] = []
|
||||
append = space.append
|
||||
for token in tokens:
|
||||
(token_name, value, _, _, location) = token
|
||||
token_name, value, _, _, location, _ = token
|
||||
if token_name in ("number", "scalar"):
|
||||
try:
|
||||
append(int(value))
|
||||
@@ -153,7 +154,7 @@ class StylesBuilder:
|
||||
style_tokens: list[str] = []
|
||||
append = style_tokens.append
|
||||
for token in tokens:
|
||||
token_name, value, _, _, _ = token
|
||||
token_name, value, _, _, _, _ = token
|
||||
if token_name == "token":
|
||||
if value in VALID_BORDER:
|
||||
border_type = value
|
||||
@@ -299,15 +300,26 @@ class StylesBuilder:
|
||||
|
||||
def process_text(self, name: str, tokens: list[Token], important: bool) -> None:
|
||||
style_definition = " ".join(token.value for token in tokens)
|
||||
|
||||
# If every token in the value is a referenced by the same variable,
|
||||
# we can display the variable name before the style definition.
|
||||
# TODO: Factor this out to apply it to other properties too.
|
||||
unique_references = {t.referenced_by for t in tokens if t.referenced_by}
|
||||
if tokens and tokens[0].referenced_by and len(unique_references) == 1:
|
||||
variable_prefix = f"${tokens[0].referenced_by.name}="
|
||||
else:
|
||||
variable_prefix = ""
|
||||
|
||||
try:
|
||||
style = Style.parse(style_definition)
|
||||
self.styles.text = style
|
||||
except Exception as error:
|
||||
self.error(name, tokens[0], f"failed to parse style; {error}")
|
||||
message = f"property 'text' has invalid value {variable_prefix}{style_definition!r}; {error}"
|
||||
self.error(name, tokens[0], message)
|
||||
if important:
|
||||
self.styles.important.update(
|
||||
{"text_style", "text_background", "text_color"}
|
||||
)
|
||||
self.styles.text = style
|
||||
|
||||
def process_text_color(
|
||||
self, name: str, tokens: list[Token], important: bool
|
||||
|
||||
@@ -9,6 +9,10 @@ class DeclarationError(Exception):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class UnresolvedVariableError(NameError):
|
||||
pass
|
||||
|
||||
|
||||
class StyleTypeError(TypeError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich import print
|
||||
|
||||
from collections import defaultdict
|
||||
from functools import lru_cache
|
||||
from typing import Iterator, Iterable
|
||||
from typing import Iterator, Iterable, Optional
|
||||
|
||||
from .styles import Styles
|
||||
from .tokenize import tokenize, tokenize_declarations, Token
|
||||
from .tokenizer import EOFError
|
||||
from rich import print
|
||||
from rich.cells import cell_len
|
||||
|
||||
from textual.css.errors import UnresolvedVariableError
|
||||
from ._styles_builder import StylesBuilder, DeclarationError
|
||||
from .model import (
|
||||
Declaration,
|
||||
RuleSet,
|
||||
@@ -17,8 +17,9 @@ from .model import (
|
||||
SelectorSet,
|
||||
SelectorType,
|
||||
)
|
||||
from ._styles_builder import StylesBuilder, DeclarationError
|
||||
|
||||
from .styles import Styles
|
||||
from .tokenize import tokenize, tokenize_declarations, Token
|
||||
from .tokenizer import EOFError, ReferencedBy
|
||||
|
||||
SELECTOR_MAP: dict[str, tuple[SelectorType, tuple[int, int, int]]] = {
|
||||
"selector": (SelectorType.TYPE, (0, 0, 1)),
|
||||
@@ -34,7 +35,6 @@ SELECTOR_MAP: dict[str, tuple[SelectorType, tuple[int, int, int]]] = {
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
|
||||
|
||||
tokens = iter(tokenize(css_selectors, ""))
|
||||
|
||||
get_selector = SELECTOR_MAP.get
|
||||
@@ -81,7 +81,6 @@ def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
|
||||
|
||||
|
||||
def parse_rule_set(tokens: Iterator[Token], token: Token) -> Iterable[RuleSet]:
|
||||
|
||||
get_selector = SELECTOR_MAP.get
|
||||
combinator: CombinatorType | None = CombinatorType.DESCENDENT
|
||||
selectors: list[Selector] = []
|
||||
@@ -205,9 +204,111 @@ def parse_declarations(css: str, path: str) -> Styles:
|
||||
return styles_builder.styles
|
||||
|
||||
|
||||
def parse(css: str, path: str) -> Iterable[RuleSet]:
|
||||
def _unresolved(
|
||||
variable_name: str, location: tuple[int, int]
|
||||
) -> UnresolvedVariableError:
|
||||
line_idx, col_idx = location
|
||||
return UnresolvedVariableError(
|
||||
f"reference to undefined variable '${variable_name}' at line {line_idx + 1}, column {col_idx + 1}."
|
||||
)
|
||||
|
||||
tokens = iter(tokenize(css, path))
|
||||
|
||||
def substitute_references(tokens: Iterator[Token]) -> Iterable[Token]:
|
||||
"""Replace variable references with values by substituting variable reference
|
||||
tokens with the tokens representing their values.
|
||||
|
||||
Args:
|
||||
tokens (Iterator[Token]): Iterator of Tokens which may contain tokens
|
||||
with the name "variable_ref".
|
||||
|
||||
Returns:
|
||||
Iterable[Token]: Yields Tokens such that any variable references (tokens where
|
||||
token.name == "variable_ref") have been replaced with the tokens representing
|
||||
the value. In other words, an Iterable of Tokens similar to the original input,
|
||||
but with variables resolved. Substituted tokens will have their referenced_by
|
||||
attribute populated with information about where the tokens are being substituted to.
|
||||
"""
|
||||
variables: dict[str, list[Token]] = defaultdict(list)
|
||||
while tokens:
|
||||
token = next(tokens, None)
|
||||
if token is None:
|
||||
break
|
||||
if token.name == "variable_name":
|
||||
variable_name = token.value[1:-1] # Trim the $ and the :, i.e. "$x:" -> "x"
|
||||
yield token
|
||||
|
||||
while True:
|
||||
token = next(tokens, None)
|
||||
if token.name == "whitespace":
|
||||
yield token
|
||||
else:
|
||||
break
|
||||
|
||||
# Store the tokens for any variable definitions, and substitute
|
||||
# any variable references we encounter with them.
|
||||
while True:
|
||||
if not token:
|
||||
break
|
||||
elif token.name == "whitespace":
|
||||
variables[variable_name].append(token)
|
||||
yield token
|
||||
elif token.name == "variable_value_end":
|
||||
yield token
|
||||
break
|
||||
# For variables referring to other variables
|
||||
elif token.name == "variable_ref":
|
||||
ref_name = token.value[1:]
|
||||
if ref_name in variables:
|
||||
variable_tokens = variables[variable_name]
|
||||
reference_tokens = variables[ref_name]
|
||||
variable_tokens.extend(reference_tokens)
|
||||
ref_location = token.location
|
||||
ref_length = len(token.value)
|
||||
for _token in reference_tokens:
|
||||
yield _token.with_reference(
|
||||
ReferencedBy(
|
||||
name=ref_name,
|
||||
location=ref_location,
|
||||
length=ref_length,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise _unresolved(
|
||||
variable_name=ref_name, location=token.location
|
||||
)
|
||||
else:
|
||||
variables[variable_name].append(token)
|
||||
yield token
|
||||
token = next(tokens, None)
|
||||
elif token.name == "variable_ref":
|
||||
variable_name = token.value[1:] # Trim the $, so $x -> x
|
||||
if variable_name in variables:
|
||||
variable_tokens = variables[variable_name]
|
||||
ref_location = token.location
|
||||
ref_length = len(token.value)
|
||||
for token in variable_tokens:
|
||||
yield token.with_reference(
|
||||
ReferencedBy(
|
||||
name=variable_name,
|
||||
location=ref_location,
|
||||
length=ref_length,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise _unresolved(variable_name=variable_name, location=token.location)
|
||||
else:
|
||||
yield token
|
||||
|
||||
|
||||
def parse(css: str, path: str) -> Iterable[RuleSet]:
|
||||
"""Parse CSS by tokenizing it, performing variable substitution,
|
||||
and generating rule sets from it.
|
||||
|
||||
Args:
|
||||
css (str): The input CSS
|
||||
path (str): Path to the CSS
|
||||
"""
|
||||
tokens = iter(substitute_references(tokenize(css, path)))
|
||||
while True:
|
||||
token = next(tokens, None)
|
||||
if token is None:
|
||||
@@ -216,42 +317,6 @@ def parse(css: str, path: str) -> Iterable[RuleSet]:
|
||||
yield from parse_rule_set(tokens, token)
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# test = """
|
||||
|
||||
# App View {
|
||||
# text: red;
|
||||
# }
|
||||
|
||||
# .foo.bar baz:focus, #egg .foo.baz {
|
||||
# /* ignore me, I'm a comment */
|
||||
# display: block;
|
||||
# visibility: visible;
|
||||
# border: solid green !important;
|
||||
# outline: red;
|
||||
# padding: 1 2;
|
||||
# margin: 5;
|
||||
# text: bold red on magenta
|
||||
# text-color: green;
|
||||
# text-background: white
|
||||
# docks: foo bar bar
|
||||
# dock-group: foo
|
||||
# dock-edge: top
|
||||
# offset-x: 4
|
||||
# offset-y: 5
|
||||
# }"""
|
||||
|
||||
# from .stylesheet import Stylesheet
|
||||
|
||||
# print(test)
|
||||
# print()
|
||||
# stylesheet = Stylesheet()
|
||||
# stylesheet.parse(test)
|
||||
# print(stylesheet)
|
||||
# print()
|
||||
# print(stylesheet.css)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(parse_selectors("Foo > Bar.baz { foo: bar"))
|
||||
|
||||
|
||||
@@ -36,10 +36,7 @@ from .scalar import Scalar, ScalarOffset, Unit
|
||||
from .scalar_animation import ScalarAnimation
|
||||
from .transition import Transition
|
||||
from .types import Display, Edge, Visibility
|
||||
|
||||
|
||||
from .types import Specificity3, Specificity4
|
||||
from .. import log
|
||||
from .._animator import Animation, EasingFunction
|
||||
from ..geometry import Spacing, SpacingDimensions
|
||||
from .._box import BoxType
|
||||
@@ -50,7 +47,6 @@ if sys.version_info >= (3, 8):
|
||||
else:
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..layout import Layout
|
||||
from ..dom import DOMNode
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from operator import itemgetter
|
||||
import os
|
||||
from typing import cast, Iterable
|
||||
|
||||
|
||||
import rich.repr
|
||||
from rich.highlighter import ReprHighlighter
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich.console import Group, RenderableType
|
||||
from rich.highlighter import ReprHighlighter
|
||||
from rich.padding import Padding
|
||||
from rich.panel import Panel
|
||||
from rich.syntax import Syntax
|
||||
from rich.text import Text
|
||||
|
||||
|
||||
from textual._loop import loop_last
|
||||
from .errors import StylesheetError
|
||||
from .match import _check_selectors
|
||||
from .model import RuleSet
|
||||
@@ -19,7 +23,6 @@ from .parse import parse
|
||||
from .styles import RulesMap
|
||||
from .types import Specificity3, Specificity4
|
||||
from ..dom import DOMNode
|
||||
from .. import log
|
||||
|
||||
|
||||
class StylesheetParseError(Exception):
|
||||
@@ -35,11 +38,17 @@ class StylesheetErrors:
|
||||
self.stylesheet = stylesheet
|
||||
|
||||
@classmethod
|
||||
def _get_snippet(cls, code: str, line_no: int, col_no: int, length: int) -> Panel:
|
||||
lines = Text(code, style="dim").split()
|
||||
lines[line_no].stylize("bold not dim", col_no, col_no + length - 1)
|
||||
text = Text("\n").join(lines[max(0, line_no - 1) : line_no + 2])
|
||||
return Panel(text, border_style="red")
|
||||
def _get_snippet(cls, code: str, line_no: int) -> Panel:
|
||||
syntax = Syntax(
|
||||
code,
|
||||
lexer="scss",
|
||||
theme="ansi_light",
|
||||
line_numbers=True,
|
||||
indent_guides=True,
|
||||
line_range=(max(0, line_no - 2), line_no + 1),
|
||||
highlight_lines={line_no},
|
||||
)
|
||||
return Panel(syntax, border_style="red")
|
||||
|
||||
def __rich__(self) -> RenderableType:
|
||||
highlighter = ReprHighlighter()
|
||||
@@ -47,13 +56,30 @@ class StylesheetErrors:
|
||||
append = errors.append
|
||||
for rule in self.stylesheet.rules:
|
||||
for token, message in rule.errors:
|
||||
line_no, col_no = token.location
|
||||
append("")
|
||||
append(Text(" Error in stylesheet:", style="bold red"))
|
||||
|
||||
append(highlighter(f"{token.path or '<unknown>'}:{line_no}"))
|
||||
append(
|
||||
self._get_snippet(token.code, line_no, col_no, len(token.value) + 1)
|
||||
)
|
||||
append(highlighter(Text(message, "red")))
|
||||
if token.referenced_by:
|
||||
line_idx, col_idx = token.referenced_by.location
|
||||
line_no, col_no = line_idx + 1, col_idx + 1
|
||||
append(
|
||||
highlighter(f" {token.path or '<unknown>'}:{line_no}:{col_no}")
|
||||
)
|
||||
append(self._get_snippet(token.code, line_no))
|
||||
else:
|
||||
line_idx, col_idx = token.location
|
||||
line_no, col_no = line_idx + 1, col_idx + 1
|
||||
append(
|
||||
highlighter(f" {token.path or '<unknown>'}:{line_no}:{col_no}")
|
||||
)
|
||||
append(self._get_snippet(token.code, line_no))
|
||||
|
||||
final_message = ""
|
||||
for is_last, message_part in loop_last(message.split(";")):
|
||||
end = "" if is_last else "\n"
|
||||
final_message += f"• {message_part.strip()};{end}"
|
||||
|
||||
append(Padding(highlighter(Text(final_message, "red")), pad=(0, 1)))
|
||||
append("")
|
||||
return Group(*errors)
|
||||
|
||||
@@ -167,7 +193,6 @@ class Stylesheet:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from rich.traceback import install
|
||||
|
||||
install(show_locals=True)
|
||||
|
||||
@@ -14,9 +14,9 @@ COLOR = r"\#[0-9a-fA-F]{6}|color\([0-9]{1,3}\)|rgb\(\d{1,3}\,\s?\d{1,3}\,\s?\d{1
|
||||
KEY_VALUE = r"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\-\/]+"
|
||||
TOKEN = "[a-zA-Z_-]+"
|
||||
STRING = r"\".*?\""
|
||||
VARIABLE_REF = r"\$[a-zA-Z0-9_-]+"
|
||||
VARIABLE_REF = r"\$[a-zA-Z0-9_\-]+"
|
||||
|
||||
# Values permitted in declarations.
|
||||
# Values permitted in variable and rule declarations.
|
||||
DECLARATION_VALUES = {
|
||||
"scalar": SCALAR,
|
||||
"duration": DURATION,
|
||||
@@ -38,19 +38,16 @@ expect_root_scope = Expect(
|
||||
selector_start_class=r"\.[a-zA-Z_\-][a-zA-Z0-9_\-]*",
|
||||
selector_start_universal=r"\*",
|
||||
selector_start=r"[a-zA-Z_\-]+",
|
||||
variable_name=f"{VARIABLE_REF}:",
|
||||
variable_name=rf"{VARIABLE_REF}:",
|
||||
).expect_eof(True)
|
||||
|
||||
# After a variable declaration e.g. "$warning-text: TOKENS;"
|
||||
# for tokenizing variable value ------^~~~~~~^
|
||||
expect_variable_value = Expect(
|
||||
comment_start=COMMENT_START,
|
||||
whitespace=r"\s+",
|
||||
variable_value=rf"[^;\n{COMMENT_START}]+",
|
||||
)
|
||||
|
||||
expect_variable_value_end = Expect(
|
||||
expect_variable_name_continue = Expect(
|
||||
variable_value_end=r"\n|;",
|
||||
whitespace=r"\s+",
|
||||
comment_start=COMMENT_START,
|
||||
**DECLARATION_VALUES,
|
||||
).expect_eof(True)
|
||||
|
||||
expect_comment_end = Expect(
|
||||
@@ -72,8 +69,8 @@ expect_selector_continue = Expect(
|
||||
declaration_set_start=r"\{",
|
||||
)
|
||||
|
||||
# A declaration e.g. "text: red;"
|
||||
# ^---^
|
||||
# A rule declaration e.g. "text: red;"
|
||||
# ^---^
|
||||
expect_declaration = Expect(
|
||||
whitespace=r"\s+",
|
||||
comment_start=COMMENT_START,
|
||||
@@ -88,8 +85,8 @@ expect_declaration_solo = Expect(
|
||||
declaration_set_end=r"\}",
|
||||
).expect_eof(True)
|
||||
|
||||
# The value(s)/content from a declaration e.g. "text: red;"
|
||||
# ^---^
|
||||
# The value(s)/content from a rule declaration e.g. "text: red;"
|
||||
# ^---^
|
||||
expect_declaration_content = Expect(
|
||||
declaration_end=r"\n|;",
|
||||
whitespace=r"\s+",
|
||||
@@ -115,8 +112,7 @@ class TokenizerState:
|
||||
|
||||
EXPECT = expect_root_scope
|
||||
STATE_MAP = {
|
||||
"variable_name": expect_variable_value,
|
||||
"variable_value": expect_variable_value_end,
|
||||
"variable_name": expect_variable_name_continue,
|
||||
"variable_value_end": expect_root_scope,
|
||||
"selector_start": expect_selector_continue,
|
||||
"selector_start_id": expect_selector_continue,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NamedTuple
|
||||
import re
|
||||
from typing import NamedTuple
|
||||
|
||||
from rich import print
|
||||
import rich.repr
|
||||
from rich.cells import cell_len
|
||||
|
||||
|
||||
class EOFError(Exception):
|
||||
@@ -39,6 +39,12 @@ class Expect:
|
||||
yield from zip(self.names, self.regexes)
|
||||
|
||||
|
||||
class ReferencedBy(NamedTuple):
|
||||
name: str
|
||||
location: tuple[int, int]
|
||||
length: int
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class Token(NamedTuple):
|
||||
name: str
|
||||
@@ -46,6 +52,23 @@ class Token(NamedTuple):
|
||||
path: str
|
||||
code: str
|
||||
location: tuple[int, int]
|
||||
referenced_by: ReferencedBy | None
|
||||
|
||||
def with_reference(self, by: ReferencedBy | None) -> "Token":
|
||||
"""Return a copy of the Token, with reference information attached.
|
||||
This is used for variable substitution, where a variable reference
|
||||
can refer to tokens which were defined elsewhere. With the additional
|
||||
ReferencedBy data attached, we can track where the token we are referring
|
||||
to is used.
|
||||
"""
|
||||
return Token(
|
||||
name=self.name,
|
||||
value=self.value,
|
||||
path=self.path,
|
||||
code=self.code,
|
||||
location=self.location,
|
||||
referenced_by=by,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
@@ -55,6 +78,7 @@ class Token(NamedTuple):
|
||||
yield "value", self.value
|
||||
yield "path", self.path
|
||||
yield "location", self.location
|
||||
yield "referenced_by", self.referenced_by
|
||||
|
||||
|
||||
class Tokenizer:
|
||||
@@ -70,7 +94,7 @@ class Tokenizer:
|
||||
col_no = self.col_no
|
||||
if line_no >= len(self.lines):
|
||||
if expect._expect_eof:
|
||||
return Token("eof", "", self.path, self.code, (line_no, col_no))
|
||||
return Token("eof", "", self.path, self.code, (line_no, col_no), None)
|
||||
else:
|
||||
raise EOFError()
|
||||
line = self.lines[line_no]
|
||||
@@ -88,7 +112,9 @@ class Tokenizer:
|
||||
if value is not None:
|
||||
break
|
||||
|
||||
token = Token(name, value, self.path, self.code, (line_no, col_no))
|
||||
token = Token(
|
||||
name, value, self.path, self.code, (line_no, col_no), referenced_by=None
|
||||
)
|
||||
col_no += len(value)
|
||||
if col_no >= len(line):
|
||||
line_no += 1
|
||||
|
||||
@@ -1,12 +1,178 @@
|
||||
import pytest
|
||||
from rich.color import Color, ColorType
|
||||
|
||||
from textual.css.errors import UnresolvedVariableError
|
||||
from textual.css.parse import substitute_references
|
||||
from textual.css.scalar import Scalar, Unit
|
||||
from textual.css.stylesheet import Stylesheet, StylesheetParseError
|
||||
from textual.css.tokenize import tokenize
|
||||
from textual.css.tokenizer import Token, ReferencedBy
|
||||
from textual.css.transition import Transition
|
||||
from textual.layouts.dock import DockLayout
|
||||
|
||||
|
||||
class TestVariableReferenceSubstitution:
|
||||
def test_simple_reference(self):
|
||||
css = "$x: 1; #some-widget{border: $x;}"
|
||||
variables = substitute_references(tokenize(css, ""))
|
||||
assert list(variables) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 6), referenced_by=None),
|
||||
Token(name='selector_start_id', value='#some-widget', path='', code=css, location=(0, 7),
|
||||
referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 19), referenced_by=None),
|
||||
Token(name='declaration_name', value='border:', path='', code=css, location=(0, 20), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 27), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 4),
|
||||
referenced_by=ReferencedBy(name='x', location=(0, 28), length=2)),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 30), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 31), referenced_by=None)
|
||||
]
|
||||
|
||||
def test_simple_reference_no_whitespace(self):
|
||||
css = "$x:1; #some-widget{border: $x;}"
|
||||
variables = substitute_references(tokenize(css, ""))
|
||||
assert list(variables) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='selector_start_id', value='#some-widget', path='', code=css, location=(0, 6),
|
||||
referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 18), referenced_by=None),
|
||||
Token(name='declaration_name', value='border:', path='', code=css, location=(0, 19), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 26), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 3),
|
||||
referenced_by=ReferencedBy(name='x', location=(0, 27), length=2)),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 29), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 30), referenced_by=None)
|
||||
]
|
||||
|
||||
def test_undefined_variable(self):
|
||||
css = ".thing { border: $not-defined; }"
|
||||
with pytest.raises(UnresolvedVariableError):
|
||||
list(substitute_references(tokenize(css, "")))
|
||||
|
||||
def test_transitive_reference(self):
|
||||
css = "$x: 1\n$y: $x\n.thing { border: $y }"
|
||||
assert list(substitute_references(tokenize(css, ""))) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 4),
|
||||
referenced_by=ReferencedBy(name='x', location=(1, 4), length=2)),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(1, 6), referenced_by=None),
|
||||
Token(name='selector_start_class', value='.thing', path='', code=css, location=(2, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 6), referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(2, 7), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 8), referenced_by=None),
|
||||
Token(name='declaration_name', value='border:', path='', code=css, location=(2, 9), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 16), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 4),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 19), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(2, 20), referenced_by=None)
|
||||
]
|
||||
|
||||
def test_multi_value_variable(self):
|
||||
css = "$x: 2 4\n$y: 6 $x 2\n.thing { border: $y }"
|
||||
assert list(substitute_references(tokenize(css, ""))) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='number', value='2', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='number', value='4', path='', code=css, location=(0, 6), referenced_by=None),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 7), referenced_by=None),
|
||||
Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_by=None),
|
||||
Token(name='number', value='6', path='', code=css, location=(1, 4), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 5), referenced_by=None),
|
||||
Token(name='number', value='2', path='', code=css, location=(0, 4),
|
||||
referenced_by=ReferencedBy(name='x', location=(1, 6), length=2)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5),
|
||||
referenced_by=ReferencedBy(name='x', location=(1, 6), length=2)),
|
||||
Token(name='number', value='4', path='', code=css, location=(0, 6),
|
||||
referenced_by=ReferencedBy(name='x', location=(1, 6), length=2)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 8), referenced_by=None),
|
||||
Token(name='number', value='2', path='', code=css, location=(1, 9), referenced_by=None),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(1, 10), referenced_by=None),
|
||||
Token(name='selector_start_class', value='.thing', path='', code=css, location=(2, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 6), referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(2, 7), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 8), referenced_by=None),
|
||||
Token(name='declaration_name', value='border:', path='', code=css, location=(2, 9), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 16), referenced_by=None),
|
||||
Token(name='number', value='6', path='', code=css, location=(1, 4),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 5),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='number', value='2', path='', code=css, location=(0, 4),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='number', value='4', path='', code=css, location=(0, 6),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 8),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='number', value='2', path='', code=css, location=(1, 9),
|
||||
referenced_by=ReferencedBy(name='y', location=(2, 17), length=2)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(2, 19), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(2, 20), referenced_by=None)
|
||||
]
|
||||
|
||||
def test_variable_used_inside_property_value(self):
|
||||
css = "$x: red\n.thing { border: on $x; }"
|
||||
assert list(substitute_references(tokenize(css, ""))) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='token', value='red', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 7), referenced_by=None),
|
||||
Token(name='selector_start_class', value='.thing', path='', code=css, location=(1, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 6), referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(1, 7), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 8), referenced_by=None),
|
||||
Token(name='declaration_name', value='border:', path='', code=css, location=(1, 9), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 16), referenced_by=None),
|
||||
Token(name='token', value='on', path='', code=css, location=(1, 17), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 19), referenced_by=None),
|
||||
Token(name='token', value='red', path='', code=css, location=(0, 4),
|
||||
referenced_by=ReferencedBy(name='x', location=(1, 20), length=2)),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(1, 22), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 23), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(1, 24), referenced_by=None)
|
||||
]
|
||||
|
||||
def test_variable_definition_eof(self):
|
||||
css = "$x: 1"
|
||||
assert list(substitute_references(tokenize(css, ""))) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_by=None)
|
||||
]
|
||||
|
||||
def test_variable_reference_whitespace_trimming(self):
|
||||
css = "$x: 123;.thing{border: $x}"
|
||||
assert list(substitute_references(tokenize(css, ""))) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='number', value='123', path='', code=css, location=(0, 7), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 10), referenced_by=None),
|
||||
Token(name='selector_start_class', value='.thing', path='', code=css, location=(0, 11), referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 17), referenced_by=None),
|
||||
Token(name='declaration_name', value='border:', path='', code=css, location=(0, 18), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 25), referenced_by=None),
|
||||
Token(name='number', value='123', path='', code=css, location=(0, 7),
|
||||
referenced_by=ReferencedBy(name='x', location=(0, 26), length=2)),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 28), referenced_by=None)
|
||||
]
|
||||
|
||||
|
||||
class TestParseLayout:
|
||||
def test_valid_layout_name(self):
|
||||
css = "#some-widget { layout: dock; }"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import pytest
|
||||
|
||||
import textual.css.tokenizer
|
||||
from textual.css.tokenize import tokenize
|
||||
from textual.css.tokenizer import Token, TokenizeError
|
||||
|
||||
@@ -21,114 +20,140 @@ VALID_VARIABLE_NAMES = [
|
||||
def test_variable_declaration_valid_names(name):
|
||||
css = f"${name}: black on red;"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(
|
||||
name="variable_name", value=f"${name}:", path="", code=css, location=(0, 0)
|
||||
),
|
||||
Token(name="whitespace", value=" ", path="", code=css, location=(0, 14)),
|
||||
Token(name="variable_value", value="black on red", path="", code=css, location=(0, 15)),
|
||||
Token(name="variable_value_end", value=";", path="", code=css, location=(0, 27)),
|
||||
Token(name='variable_name', value=f'${name}:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 14), referenced_by=None),
|
||||
Token(name='token', value='black', path='', code=css, location=(0, 15), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 20), referenced_by=None),
|
||||
Token(name='token', value='on', path='', code=css, location=(0, 21), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 23), referenced_by=None),
|
||||
Token(name='token', value='red', path='', code=css, location=(0, 24), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 27), referenced_by=None),
|
||||
]
|
||||
|
||||
|
||||
def test_variable_declaration_multiple_values():
|
||||
css = "$x: 2vw\t4% 6s red;"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='scalar', value='2vw', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='whitespace', value='\t', path='', code=css, location=(0, 7), referenced_by=None),
|
||||
Token(name='scalar', value='4%', path='', code=css, location=(0, 8), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 10), referenced_by=None),
|
||||
Token(name='duration', value='6s', path='', code=css, location=(0, 11), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 13), referenced_by=None),
|
||||
Token(name='token', value='red', path='', code=css, location=(0, 15), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 18), referenced_by=None),
|
||||
]
|
||||
|
||||
|
||||
def test_variable_declaration_comment_ignored():
|
||||
css = "$x: red; /* comment */"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3)),
|
||||
Token(name='variable_value', value='red', path='', code=css, location=(0, 4)),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 7)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 8)),
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='token', value='red', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 7), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 8), referenced_by=None),
|
||||
]
|
||||
|
||||
|
||||
def test_variable_declaration_comment_interspersed_raises():
|
||||
def test_variable_declaration_comment_interspersed_ignored():
|
||||
css = "$x: re/* comment */d;"
|
||||
with pytest.raises(TokenizeError):
|
||||
assert list(tokenize(css, ""))
|
||||
|
||||
|
||||
def test_variable_declaration_invalid_value_eof():
|
||||
css = "$x:\n"
|
||||
with pytest.raises(textual.css.tokenizer.EOFError):
|
||||
list(tokenize(css, ""))
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='token', value='re', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='token', value='d', path='', code=css, location=(0, 19), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 20), referenced_by=None),
|
||||
]
|
||||
|
||||
|
||||
def test_variable_declaration_no_semicolon():
|
||||
css = "$x: 1\n$y: 2"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name="variable_name", value="$x:", code=css, path="", location=(0, 0)),
|
||||
Token(name="whitespace", value=" ", code=css, path="", location=(0, 3)),
|
||||
Token(name="variable_value", value="1", code=css, path="", location=(0, 4)),
|
||||
Token(name="variable_value_end", value="\n", code=css, path="", location=(0, 5)),
|
||||
Token(name="variable_name", value="$y:", code=css, path="", location=(1, 0)),
|
||||
Token(name="whitespace", value=" ", code=css, path="", location=(1, 3)),
|
||||
Token(name="variable_value", value="2", code=css, path="", location=(1, 4)),
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='variable_name', value='$y:', path='', code=css, location=(1, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(1, 3), referenced_by=None),
|
||||
Token(name='number', value='2', path='', code=css, location=(1, 4), referenced_by=None),
|
||||
]
|
||||
|
||||
|
||||
def test_variable_declaration_invalid_value():
|
||||
css = "$x:(@$12x)"
|
||||
with pytest.raises(TokenizeError):
|
||||
list(tokenize(css, ""))
|
||||
|
||||
|
||||
def test_variables_declarations_amongst_rulesets():
|
||||
css = "$x:1; .thing{text:red;} $y:2;"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0)),
|
||||
Token(name='variable_value', value='1', path='', code=css, location=(0, 3)),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 4)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5)),
|
||||
Token(name='selector_start_class', value='.thing', path='', code=css, location=(0, 6)),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 12)),
|
||||
Token(name='declaration_name', value='text:', path='', code=css, location=(0, 13)),
|
||||
Token(name='token', value='red', path='', code=css, location=(0, 18)),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 21)),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 22)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 23)),
|
||||
Token(name='variable_name', value='$y:', path='', code=css, location=(0, 24)),
|
||||
Token(name='variable_value', value='2', path='', code=css, location=(0, 27)),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 28)),
|
||||
tokens = list(tokenize(css, ""))
|
||||
assert tokens == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='number', value='1', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='selector_start_class', value='.thing', path='', code=css, location=(0, 6), referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 12), referenced_by=None),
|
||||
Token(name='declaration_name', value='text:', path='', code=css, location=(0, 13), referenced_by=None),
|
||||
Token(name='token', value='red', path='', code=css, location=(0, 18), referenced_by=None),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 21), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 22), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 23), referenced_by=None),
|
||||
Token(name='variable_name', value='$y:', path='', code=css, location=(0, 24), referenced_by=None),
|
||||
Token(name='number', value='2', path='', code=css, location=(0, 27), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 28), referenced_by=None),
|
||||
]
|
||||
|
||||
|
||||
def test_variables_reference_in_rule_declaration_value():
|
||||
css = ".warn{text: $warning;}"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='selector_start_class', value='.warn', path='', code=css, location=(0, 0)),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5)),
|
||||
Token(name='declaration_name', value='text:', path='', code=css, location=(0, 6)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 11)),
|
||||
Token(name='variable_ref', value='$warning', path='', code=css, location=(0, 12)),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 20)),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 21)),
|
||||
Token(name='selector_start_class', value='.warn', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='declaration_name', value='text:', path='', code=css, location=(0, 6), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 11), referenced_by=None),
|
||||
Token(name='variable_ref', value='$warning', path='', code=css, location=(0, 12), referenced_by=None),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 20), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 21), referenced_by=None),
|
||||
]
|
||||
|
||||
|
||||
def test_variables_reference_in_rule_declaration_value_multiple():
|
||||
css = ".card{padding: $pad-y $pad-x;}"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='selector_start_class', value='.card', path='', code=css, location=(0, 0)),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5)),
|
||||
Token(name='declaration_name', value='padding:', path='', code=css, location=(0, 6)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 14)),
|
||||
Token(name='variable_ref', value='$pad-y', path='', code=css, location=(0, 15)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 21)),
|
||||
Token(name='variable_ref', value='$pad-x', path='', code=css, location=(0, 22)),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 28)),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 29)),
|
||||
Token(name='selector_start_class', value='.card', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='declaration_set_start', value='{', path='', code=css, location=(0, 5), referenced_by=None),
|
||||
Token(name='declaration_name', value='padding:', path='', code=css, location=(0, 6), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 14), referenced_by=None),
|
||||
Token(name='variable_ref', value='$pad-y', path='', code=css, location=(0, 15), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 21), referenced_by=None),
|
||||
Token(name='variable_ref', value='$pad-x', path='', code=css, location=(0, 22), referenced_by=None),
|
||||
Token(name='declaration_end', value=';', path='', code=css, location=(0, 28), referenced_by=None),
|
||||
Token(name='declaration_set_end', value='}', path='', code=css, location=(0, 29), referenced_by=None)
|
||||
]
|
||||
|
||||
|
||||
def test_variables_reference_in_variable_declaration():
|
||||
css = "$x: $y;"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3)),
|
||||
Token(name='variable_value', value='$y', path='', code=css, location=(0, 4)),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 6)),
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='variable_ref', value='$y', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='variable_value_end', value=';', path='', code=css, location=(0, 6), referenced_by=None)
|
||||
]
|
||||
|
||||
|
||||
def test_variable_references_in_variable_declaration_multiple():
|
||||
css = "$x: $y $z\n"
|
||||
assert list(tokenize(css, "")) == [
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0)),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3)),
|
||||
Token(name='variable_value', value='$y $z', path='', code=css, location=(0, 4)),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 10)),
|
||||
Token(name='variable_name', value='$x:', path='', code=css, location=(0, 0), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 3), referenced_by=None),
|
||||
Token(name='variable_ref', value='$y', path='', code=css, location=(0, 4), referenced_by=None),
|
||||
Token(name='whitespace', value=' ', path='', code=css, location=(0, 6), referenced_by=None),
|
||||
Token(name='variable_ref', value='$z', path='', code=css, location=(0, 8), referenced_by=None),
|
||||
Token(name='variable_value_end', value='\n', path='', code=css, location=(0, 10), referenced_by=None)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user