mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
specificity
This commit is contained in:
@@ -20,7 +20,7 @@ class BoxProperty:
|
||||
DEFAULT = ("", Style())
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self.internal_name = f"_{name}"
|
||||
self.internal_name = f"_rule_{name}"
|
||||
_type, edge = name.split("_")
|
||||
self._type = _type
|
||||
self.edge = edge
|
||||
@@ -134,7 +134,7 @@ class StyleProperty:
|
||||
DEFAULT_STYLE = Style()
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._internal_name = f"_{name}"
|
||||
self._internal_name = f"_rule_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
|
||||
return getattr(obj, self._internal_name) or self.DEFAULT_STYLE
|
||||
@@ -154,7 +154,7 @@ class StyleProperty:
|
||||
|
||||
class SpacingProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._internal_name = f"_{name}"
|
||||
self._internal_name = f"_rule_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Spacing:
|
||||
return getattr(obj, self._internal_name) or NULL_SPACING
|
||||
@@ -169,29 +169,29 @@ class DocksProperty:
|
||||
def __get__(
|
||||
self, obj: Styles, objtype: type[Styles] | None = None
|
||||
) -> tuple[str, ...]:
|
||||
return obj._docks or ()
|
||||
return obj._rule_docks or ()
|
||||
|
||||
def __set__(self, obj: Styles, docks: str | Iterable[str]) -> tuple[str, ...]:
|
||||
if isinstance(docks, str):
|
||||
_docks = tuple(name.lower().strip() for name in docks.split(" "))
|
||||
else:
|
||||
_docks = tuple(docks)
|
||||
obj._docks = _docks
|
||||
obj._rule_docks = _docks
|
||||
return _docks
|
||||
|
||||
|
||||
class DockGroupProperty:
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
||||
return obj._dock_group or ""
|
||||
return obj._rule_dock_group or ""
|
||||
|
||||
def __set__(self, obj: Styles, spacing: str | None) -> str | None:
|
||||
obj._dock_group = spacing
|
||||
obj._rule_dock_group = spacing
|
||||
return spacing
|
||||
|
||||
|
||||
class OffsetProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._internal_name = f"_{name}"
|
||||
self._internal_name = f"_rule_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Offset:
|
||||
return getattr(obj, self._internal_name) or Offset()
|
||||
@@ -204,12 +204,12 @@ class OffsetProperty:
|
||||
|
||||
class DockEdgeProperty:
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
||||
return obj._dock_edge or ""
|
||||
return obj._rule_dock_edge or ""
|
||||
|
||||
def __set__(self, obj: Styles, edge: str | None) -> str | None:
|
||||
if isinstance(edge, str) and edge not in VALID_EDGE:
|
||||
raise ValueError(f"dock edge must be one of {friendly_list(VALID_EDGE)}")
|
||||
obj._dock_edge = edge
|
||||
obj._rule_dock_edge = edge
|
||||
return edge
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ class StringProperty:
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._name = name
|
||||
self._internal_name = f"_{name}"
|
||||
self._internal_name = f"_rule_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
|
||||
return getattr(obj, self._internal_name, None) or self._default
|
||||
@@ -253,7 +253,7 @@ class StringProperty:
|
||||
class NameProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._name = name
|
||||
self._internal_name = f"_{name}"
|
||||
self._internal_name = f"_rule_{name}"
|
||||
|
||||
def __get__(self, obj: Styles, objtype: type[Styles] | None) -> str:
|
||||
return getattr(obj, self._internal_name, None) or ""
|
||||
@@ -268,7 +268,7 @@ class NameProperty:
|
||||
class NameListProperty:
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
self._name = name
|
||||
self._internal_name = f"_{name}"
|
||||
self._internal_name = f"_rule_{name}"
|
||||
|
||||
def __get__(
|
||||
self, obj: Styles, objtype: type[Styles] | None = None
|
||||
|
||||
@@ -4,7 +4,7 @@ from rich import print
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
from typing import Iterable
|
||||
|
||||
from .styles import Styles
|
||||
from .tokenize import Token
|
||||
@@ -35,6 +35,7 @@ class Selector:
|
||||
combinator: CombinatorType = CombinatorType.SAME
|
||||
selector: SelectorType = SelectorType.TYPE
|
||||
pseudo_classes: list[str] = field(default_factory=list)
|
||||
specificity: tuple[int, int, int] = field(default_factory=lambda: (0, 0, 0))
|
||||
|
||||
@property
|
||||
def css(self) -> str:
|
||||
@@ -58,9 +59,26 @@ class Declaration:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class SelectorSet:
|
||||
selectors: list[Selector] = field(default_factory=list)
|
||||
specificity: tuple[int, int, int] = (0, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def from_selectors(cls, selectors: list[list[Selector]]) -> Iterable[SelectorSet]:
|
||||
for selector_list in selectors:
|
||||
id_total = class_total = type_total = 0
|
||||
for selector in selector_list:
|
||||
_id, _class, _type = selector.specificity
|
||||
id_total += _id
|
||||
class_total += _class
|
||||
type_total += _type
|
||||
yield SelectorSet(selector_list, (id_total, class_total, type_total))
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuleSet:
|
||||
selectors: list[list[Selector]] = field(default_factory=list)
|
||||
selector_set: list[SelectorSet] = field(default_factory=list)
|
||||
styles: Styles = field(default_factory=Styles)
|
||||
|
||||
@classmethod
|
||||
@@ -77,7 +95,8 @@ class RuleSet:
|
||||
@property
|
||||
def css(self) -> str:
|
||||
selectors = ", ".join(
|
||||
self.selector_to_css(selector) for selector in self.selectors
|
||||
self.selector_to_css(selector_set.selectors)
|
||||
for selector_set in self.selector_set
|
||||
)
|
||||
declarations = "\n".join(f" {line}" for line in self.styles.css_lines)
|
||||
css = f"{selectors} {{\n{declarations}\n}}"
|
||||
|
||||
@@ -6,19 +6,26 @@ from typing import Iterator, Iterable
|
||||
|
||||
from .tokenize import tokenize, Token
|
||||
|
||||
from .model import Declaration, RuleSet, Selector, CombinatorType, SelectorType
|
||||
from .model import (
|
||||
Declaration,
|
||||
RuleSet,
|
||||
Selector,
|
||||
CombinatorType,
|
||||
SelectorSet,
|
||||
SelectorType,
|
||||
)
|
||||
from ._styles_builder import StylesBuilder
|
||||
|
||||
|
||||
SELECTOR_MAP = {
|
||||
"selector": SelectorType.TYPE,
|
||||
"selector_start": SelectorType.TYPE,
|
||||
"selector_class": SelectorType.CLASS,
|
||||
"selector_start_class": SelectorType.CLASS,
|
||||
"selector_id": SelectorType.ID,
|
||||
"selector_start_id": SelectorType.ID,
|
||||
"selector_universal": SelectorType.UNIVERSAL,
|
||||
"selector_start_universal": SelectorType.UNIVERSAL,
|
||||
SELECTOR_MAP: dict[str, tuple[SelectorType, tuple[int, int, int]]] = {
|
||||
"selector": (SelectorType.TYPE, (0, 0, 1)),
|
||||
"selector_start": (SelectorType.TYPE, (0, 0, 1)),
|
||||
"selector_class": (SelectorType.CLASS, (0, 1, 0)),
|
||||
"selector_start_class": (SelectorType.CLASS, (0, 1, 0)),
|
||||
"selector_id": (SelectorType.ID, (1, 0, 0)),
|
||||
"selector_start_id": (SelectorType.ID, (1, 0, 0)),
|
||||
"selector_universal": (SelectorType.UNIVERSAL, (0, 0, 0)),
|
||||
"selector_start_universal": (SelectorType.UNIVERSAL, (0, 0, 0)),
|
||||
}
|
||||
|
||||
|
||||
@@ -45,11 +52,15 @@ def parse_rule_set(tokens: Iterator[Token], token: Token) -> Iterable[RuleSet]:
|
||||
elif token.name == "declaration_set_start":
|
||||
break
|
||||
else:
|
||||
_selector, specificity = get_selector(
|
||||
token.name, (SelectorType.TYPE, (0, 0, 0))
|
||||
)
|
||||
selectors.append(
|
||||
Selector(
|
||||
name=token.value.lstrip(".#"),
|
||||
combinator=combinator,
|
||||
selector=get_selector(token.name, SelectorType.TYPE),
|
||||
selector=_selector,
|
||||
specificity=specificity,
|
||||
)
|
||||
)
|
||||
combinator = CombinatorType.SAME
|
||||
@@ -79,7 +90,9 @@ def parse_rule_set(tokens: Iterator[Token], token: Token) -> Iterable[RuleSet]:
|
||||
if declaration.tokens:
|
||||
styles_builder.add_declaration(declaration)
|
||||
|
||||
rule_set = RuleSet(rule_selectors, styles_builder.styles)
|
||||
rule_set = RuleSet(
|
||||
list(SelectorSet.from_selectors(rule_selectors)), styles_builder.styles
|
||||
)
|
||||
yield rule_set
|
||||
|
||||
|
||||
@@ -96,7 +109,7 @@ def parse(css: str) -> Iterable[RuleSet]:
|
||||
|
||||
if __name__ == "__main__":
|
||||
test = """
|
||||
.foo.bar baz:focus, #egg {
|
||||
.foo.bar baz:focus, #egg .foo.baz {
|
||||
/* ignore me, I'm a comment */
|
||||
display: block;
|
||||
visibility: visible;
|
||||
|
||||
@@ -35,36 +35,36 @@ from .types import Display, Visibility
|
||||
@dataclass
|
||||
class Styles:
|
||||
|
||||
_display: Display | None = None
|
||||
_visibility: Visibility | None = None
|
||||
_layout: str | None = None
|
||||
_rule_display: Display | None = None
|
||||
_rule_visibility: Visibility | None = None
|
||||
_rule_layout: str | None = None
|
||||
|
||||
_text: Style | None = None
|
||||
_rule_text: Style | None = None
|
||||
|
||||
_padding: Spacing | None = None
|
||||
_margin: Spacing | None = None
|
||||
_offset: Offset | None = None
|
||||
_rule_padding: Spacing | None = None
|
||||
_rule_margin: Spacing | None = None
|
||||
_rule_offset: Offset | None = None
|
||||
|
||||
_border_top: tuple[str, Style] | None = None
|
||||
_border_right: tuple[str, Style] | None = None
|
||||
_border_bottom: tuple[str, Style] | None = None
|
||||
_border_left: tuple[str, Style] | None = None
|
||||
_rule_border_top: tuple[str, Style] | None = None
|
||||
_rule_border_right: tuple[str, Style] | None = None
|
||||
_rule_border_bottom: tuple[str, Style] | None = None
|
||||
_rule_border_left: tuple[str, Style] | None = None
|
||||
|
||||
_outline_top: tuple[str, Style] | None = None
|
||||
_outline_right: tuple[str, Style] | None = None
|
||||
_outline_bottom: tuple[str, Style] | None = None
|
||||
_outline_left: tuple[str, Style] | None = None
|
||||
_rule_outline_top: tuple[str, Style] | None = None
|
||||
_rule_outline_right: tuple[str, Style] | None = None
|
||||
_rule_outline_bottom: tuple[str, Style] | None = None
|
||||
_rule_outline_left: tuple[str, Style] | None = None
|
||||
|
||||
_size: int | None = None
|
||||
_fraction: int | None = None
|
||||
_min_size: int | None = None
|
||||
_rule_size: int | None = None
|
||||
_rule_fraction: int | None = None
|
||||
_rule_min_size: int | None = None
|
||||
|
||||
_dock_group: str | None = None
|
||||
_dock_edge: str | None = None
|
||||
_docks: tuple[str, ...] | None = None
|
||||
_rule_dock_group: str | None = None
|
||||
_rule_dock_edge: str | None = None
|
||||
_rule_docks: tuple[str, ...] | None = None
|
||||
|
||||
_layers: str | None = None
|
||||
_layer: tuple[str, ...] | None = None
|
||||
_rule_layers: str | None = None
|
||||
_rule_layer: tuple[str, ...] | None = None
|
||||
|
||||
important: set[str] = field(default_factory=set)
|
||||
|
||||
@@ -111,6 +111,19 @@ class Styles:
|
||||
"""Check if an outline is present."""
|
||||
return any(edge for edge, _style in self.outline)
|
||||
|
||||
def extract_rules(
|
||||
self, specificity: tuple[int, int, int]
|
||||
) -> dict[str, tuple[object, tuple[int, int, int, int]]]:
|
||||
is_important = self.important.__contains__
|
||||
return {
|
||||
rule_name: (
|
||||
getattr(self, rule_name),
|
||||
(int(is_important(rule_name)), *specificity),
|
||||
)
|
||||
for rule_name in RULE_NAMES
|
||||
if getattr(self, f"_rule_{rule_name}") is not None
|
||||
}
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
if self.has_border:
|
||||
yield "border", self.border
|
||||
@@ -142,73 +155,73 @@ class Styles:
|
||||
else:
|
||||
append(f"{name}: {value};")
|
||||
|
||||
if self._display is not None:
|
||||
append_declaration("display", self._display)
|
||||
if self._visibility is not None:
|
||||
append_declaration("visibility", self._visibility)
|
||||
if self._text is not None:
|
||||
append_declaration("text", str(self._text))
|
||||
if self._padding is not None:
|
||||
append_declaration("padding", self._padding.packed)
|
||||
if self._margin is not None:
|
||||
append_declaration("margin", self._margin.packed)
|
||||
if self._rule_display is not None:
|
||||
append_declaration("display", self._rule_display)
|
||||
if self._rule_visibility is not None:
|
||||
append_declaration("visibility", self._rule_visibility)
|
||||
if self._rule_text is not None:
|
||||
append_declaration("text", str(self._rule_text))
|
||||
if self._rule_padding is not None:
|
||||
append_declaration("padding", self._rule_padding.packed)
|
||||
if self._rule_margin is not None:
|
||||
append_declaration("margin", self._rule_margin.packed)
|
||||
|
||||
if (
|
||||
self._border_top is not None
|
||||
and self._border_top == self._border_right
|
||||
and self._border_right == self._border_bottom
|
||||
and self._border_bottom == self._border_left
|
||||
self._rule_border_top is not None
|
||||
and self._rule_border_top == self._rule_border_right
|
||||
and self._rule_border_right == self._rule_border_bottom
|
||||
and self._rule_border_bottom == self._rule_border_left
|
||||
):
|
||||
_type, style = self._border_top
|
||||
_type, style = self._rule_border_top
|
||||
append_declaration("border", f"{_type} {style}")
|
||||
else:
|
||||
if self._border_top is not None:
|
||||
_type, style = self._border_top
|
||||
if self._rule_border_top is not None:
|
||||
_type, style = self._rule_border_top
|
||||
append_declaration("border-top", f"{_type} {style}")
|
||||
if self._border_right is not None:
|
||||
_type, style = self._border_right
|
||||
if self._rule_border_right is not None:
|
||||
_type, style = self._rule_border_right
|
||||
append_declaration("border-right", f"{_type} {style}")
|
||||
if self._border_bottom is not None:
|
||||
_type, style = self._border_bottom
|
||||
if self._rule_border_bottom is not None:
|
||||
_type, style = self._rule_border_bottom
|
||||
append_declaration("border-bottom", f"{_type} {style}")
|
||||
if self._border_left is not None:
|
||||
_type, style = self._border_left
|
||||
if self._rule_border_left is not None:
|
||||
_type, style = self._rule_border_left
|
||||
append_declaration("border-left", f"{_type} {style}")
|
||||
|
||||
if (
|
||||
self._outline_top is not None
|
||||
and self._outline_top == self._outline_right
|
||||
and self._outline_right == self._outline_bottom
|
||||
and self._outline_bottom == self._outline_left
|
||||
self._rule_outline_top is not None
|
||||
and self._rule_outline_top == self._rule_outline_right
|
||||
and self._rule_outline_right == self._rule_outline_bottom
|
||||
and self._rule_outline_bottom == self._rule_outline_left
|
||||
):
|
||||
_type, style = self._outline_top
|
||||
_type, style = self._rule_outline_top
|
||||
append_declaration("outline", f"{_type} {style}")
|
||||
else:
|
||||
if self._outline_top is not None:
|
||||
_type, style = self._outline_top
|
||||
if self._rule_outline_top is not None:
|
||||
_type, style = self._rule_outline_top
|
||||
append_declaration("outline-top", f"{_type} {style}")
|
||||
if self._outline_right is not None:
|
||||
_type, style = self._outline_right
|
||||
if self._rule_outline_right is not None:
|
||||
_type, style = self._rule_outline_right
|
||||
append_declaration("outline-right", f"{_type} {style}")
|
||||
if self._outline_bottom is not None:
|
||||
_type, style = self._outline_bottom
|
||||
if self._rule_outline_bottom is not None:
|
||||
_type, style = self._rule_outline_bottom
|
||||
append_declaration("outline-bottom", f"{_type} {style}")
|
||||
if self._outline_left is not None:
|
||||
_type, style = self._outline_left
|
||||
if self._rule_outline_left is not None:
|
||||
_type, style = self._rule_outline_left
|
||||
append_declaration("outline-left", f"{_type} {style}")
|
||||
|
||||
if self.offset:
|
||||
x, y = self.offset
|
||||
append_declaration("offset", f"{x} {y}")
|
||||
if self._dock_group:
|
||||
append_declaration("dock-group", self._dock_group)
|
||||
if self._docks:
|
||||
append_declaration("docks", " ".join(self._docks))
|
||||
if self._dock_edge:
|
||||
append_declaration("dock-edge", self._dock_edge)
|
||||
if self._layers is not None:
|
||||
if self._rule_dock_group:
|
||||
append_declaration("dock-group", self._rule_dock_group)
|
||||
if self._rule_docks:
|
||||
append_declaration("docks", " ".join(self._rule_docks))
|
||||
if self._rule_dock_edge:
|
||||
append_declaration("dock-edge", self._rule_dock_edge)
|
||||
if self._rule_layers is not None:
|
||||
append_declaration("layers", " ".join(self.layers))
|
||||
if self._layer is not None:
|
||||
if self._rule_layer is not None:
|
||||
append_declaration("layer", self.layer)
|
||||
|
||||
lines.sort()
|
||||
@@ -219,6 +232,8 @@ class Styles:
|
||||
return "\n".join(self.css_lines)
|
||||
|
||||
|
||||
RULE_NAMES = {name[6:] for name in dir(Styles) if name.startswith("_rule_")}
|
||||
|
||||
if __name__ == "__main__":
|
||||
styles = Styles()
|
||||
|
||||
@@ -235,3 +250,7 @@ if __name__ == "__main__":
|
||||
|
||||
print(styles)
|
||||
print(styles.css)
|
||||
print(dir(styles))
|
||||
print(RULE_NAMES)
|
||||
|
||||
print(styles.extract_rules((0, 1, 0)))
|
||||
|
||||
Reference in New Issue
Block a user