Module textual.css.model
Expand source code
from __future__ import annotations
import rich.repr
from dataclasses import dataclass, field
from enum import Enum
from typing import Iterable, TYPE_CHECKING
from .styles import Styles
from .tokenize import Token
from .types import Specificity3
if TYPE_CHECKING:
from ..dom import DOMNode
class SelectorType(Enum):
UNIVERSAL = 1
TYPE = 2
CLASS = 3
ID = 4
class CombinatorType(Enum):
SAME = 1
DESCENDENT = 2
CHILD = 3
@dataclass
class Selector:
"""Represents a CSS selector.
Some examples of selectors:
*
Header.title
App > Content
"""
name: str
combinator: CombinatorType = CombinatorType.DESCENDENT
type: SelectorType = SelectorType.TYPE
pseudo_classes: list[str] = field(default_factory=list)
specificity: Specificity3 = field(default_factory=lambda: (0, 0, 0))
_name_lower: str = field(default="", repr=False)
advance: int = 1
@property
def css(self) -> str:
"""Rebuilds the selector as it would appear in CSS."""
pseudo_suffix = "".join(f":{name}" for name in self.pseudo_classes)
if self.type == SelectorType.UNIVERSAL:
return "*"
elif self.type == SelectorType.TYPE:
return f"{self.name}{pseudo_suffix}"
elif self.type == SelectorType.CLASS:
return f".{self.name}{pseudo_suffix}"
else:
return f"#{self.name}{pseudo_suffix}"
def __post_init__(self) -> None:
self._name_lower = self.name.lower()
self._checks = {
SelectorType.UNIVERSAL: self._check_universal,
SelectorType.TYPE: self._check_type,
SelectorType.CLASS: self._check_class,
SelectorType.ID: self._check_id,
}
def _add_pseudo_class(self, pseudo_class: str) -> None:
"""Adds a pseudo class and updates specificity.
Args:
pseudo_class (str): Name of pseudo class.
"""
self.pseudo_classes.append(pseudo_class)
specificity1, specificity2, specificity3 = self.specificity
self.specificity = (specificity1, specificity2 + 1, specificity3)
def check(self, node: DOMNode) -> bool:
"""Check if a given node matches the selector.
Args:
node (DOMNode): A DOM node.
Returns:
bool: True if the selector matches, otherwise False.
"""
return self._checks[self.type](node)
def _check_universal(self, node: DOMNode) -> bool:
return node.has_pseudo_class(*self.pseudo_classes)
def _check_type(self, node: DOMNode) -> bool:
if self._name_lower not in node._css_type_names:
return False
if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes):
return False
return True
def _check_class(self, node: DOMNode) -> bool:
if not node.has_class(self._name_lower):
return False
if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes):
return False
return True
def _check_id(self, node: DOMNode) -> bool:
if not node.id == self._name_lower:
return False
if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes):
return False
return True
@dataclass
class Declaration:
token: Token
name: str
tokens: list[Token] = field(default_factory=list)
@rich.repr.auto(angular=True)
@dataclass
class SelectorSet:
selectors: list[Selector] = field(default_factory=list)
specificity: Specificity3 = (0, 0, 0)
def __post_init__(self) -> None:
SAME = CombinatorType.SAME
for selector, next_selector in zip(self.selectors, self.selectors[1:]):
selector.advance = int(next_selector.combinator != SAME)
@property
def css(self) -> str:
return RuleSet._selector_to_css(self.selectors)
def __rich_repr__(self) -> rich.repr.Result:
selectors = RuleSet._selector_to_css(self.selectors)
yield selectors
yield None, self.specificity
@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:
selector_set: list[SelectorSet] = field(default_factory=list)
styles: Styles = field(default_factory=Styles)
errors: list[tuple[Token, str]] = field(default_factory=list)
is_default_rules: bool = False
tie_breaker: int = 0
selector_names: set[str] = field(default_factory=set)
def __hash__(self):
return id(self)
@classmethod
def _selector_to_css(cls, selectors: list[Selector]) -> str:
tokens: list[str] = []
for selector in selectors:
if selector.combinator == CombinatorType.DESCENDENT:
tokens.append(" ")
elif selector.combinator == CombinatorType.CHILD:
tokens.append(" > ")
tokens.append(selector.css)
return "".join(tokens).strip()
@property
def selectors(self):
return ", ".join(
self._selector_to_css(selector_set.selectors)
for selector_set in self.selector_set
)
@property
def css(self) -> str:
"""Generate the CSS this RuleSet
Returns:
str: A string containing CSS code.
"""
declarations = "\n".join(f" {line}" for line in self.styles.css_lines)
css = f"{self.selectors} {{\n{declarations}\n}}"
return css
def _post_parse(self) -> None:
"""Called after the RuleSet is parsed."""
# Build a set of the class names that have been updated
class_type = SelectorType.CLASS
id_type = SelectorType.ID
type_type = SelectorType.TYPE
universal_type = SelectorType.UNIVERSAL
update_selectors = self.selector_names.update
for selector_set in self.selector_set:
update_selectors(
"*"
for selector in selector_set.selectors
if selector.type == universal_type
)
update_selectors(
selector.name
for selector in selector_set.selectors
if selector.type == type_type
)
update_selectors(
f".{selector.name}"
for selector in selector_set.selectors
if selector.type == class_type
)
update_selectors(
f"#{selector.name}"
for selector in selector_set.selectors
if selector.type == id_type
)
update_selectors(
f":{pseudo_class}"
for selector in selector_set.selectors
for pseudo_class in selector.pseudo_classes
)
Classes
class CombinatorType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code
class CombinatorType(Enum): SAME = 1 DESCENDENT = 2 CHILD = 3
Ancestors
- enum.Enum
Class variables
var CHILD
var DESCENDENT
var SAME
class Declaration (token: Token, name: str, tokens: list[Token] = <factory>)
-
Declaration(token: 'Token', name: 'str', tokens: 'list[Token]' =
) Expand source code
class Declaration: token: Token name: str tokens: list[Token] = field(default_factory=list)
Class variables
var name : str
var token : Token
var tokens : list[Token]
class RuleSet (selector_set: list[SelectorSet] = <factory>, styles: Styles = <factory>, errors: list[tuple[Token, str]] = <factory>, is_default_rules: bool = False, tie_breaker: int = 0, selector_names: set[str] = <factory>)
-
RuleSet(selector_set: 'list[SelectorSet]' =
, styles: 'Styles' = , errors: 'list[tuple[Token, str]]' = , is_default_rules: 'bool' = False, tie_breaker: 'int' = 0, selector_names: 'set[str]' = ) Expand source code
class RuleSet: selector_set: list[SelectorSet] = field(default_factory=list) styles: Styles = field(default_factory=Styles) errors: list[tuple[Token, str]] = field(default_factory=list) is_default_rules: bool = False tie_breaker: int = 0 selector_names: set[str] = field(default_factory=set) def __hash__(self): return id(self) @classmethod def _selector_to_css(cls, selectors: list[Selector]) -> str: tokens: list[str] = [] for selector in selectors: if selector.combinator == CombinatorType.DESCENDENT: tokens.append(" ") elif selector.combinator == CombinatorType.CHILD: tokens.append(" > ") tokens.append(selector.css) return "".join(tokens).strip() @property def selectors(self): return ", ".join( self._selector_to_css(selector_set.selectors) for selector_set in self.selector_set ) @property def css(self) -> str: """Generate the CSS this RuleSet Returns: str: A string containing CSS code. """ declarations = "\n".join(f" {line}" for line in self.styles.css_lines) css = f"{self.selectors} {{\n{declarations}\n}}" return css def _post_parse(self) -> None: """Called after the RuleSet is parsed.""" # Build a set of the class names that have been updated class_type = SelectorType.CLASS id_type = SelectorType.ID type_type = SelectorType.TYPE universal_type = SelectorType.UNIVERSAL update_selectors = self.selector_names.update for selector_set in self.selector_set: update_selectors( "*" for selector in selector_set.selectors if selector.type == universal_type ) update_selectors( selector.name for selector in selector_set.selectors if selector.type == type_type ) update_selectors( f".{selector.name}" for selector in selector_set.selectors if selector.type == class_type ) update_selectors( f"#{selector.name}" for selector in selector_set.selectors if selector.type == id_type ) update_selectors( f":{pseudo_class}" for selector in selector_set.selectors for pseudo_class in selector.pseudo_classes )
Class variables
var errors : list[tuple[Token, str]]
var is_default_rules : bool
var selector_names : set[str]
var selector_set : list[SelectorSet]
var styles : Styles
var tie_breaker : int
Instance variables
var css : str
-
Generate the CSS this RuleSet
Returns
str
- A string containing CSS code.
Expand source code
@property def css(self) -> str: """Generate the CSS this RuleSet Returns: str: A string containing CSS code. """ declarations = "\n".join(f" {line}" for line in self.styles.css_lines) css = f"{self.selectors} {{\n{declarations}\n}}" return css
var selectors
-
Expand source code
@property def selectors(self): return ", ".join( self._selector_to_css(selector_set.selectors) for selector_set in self.selector_set )
class Selector (name: str, combinator: CombinatorType = CombinatorType.DESCENDENT, type: SelectorType = SelectorType.TYPE, pseudo_classes: list[str] = <factory>, specificity: Specificity3 = <factory>, advance: int = 1)
-
Represents a CSS selector.
Some examples of selectors:
* Header.title App > Content
Expand source code
class Selector: """Represents a CSS selector. Some examples of selectors: * Header.title App > Content """ name: str combinator: CombinatorType = CombinatorType.DESCENDENT type: SelectorType = SelectorType.TYPE pseudo_classes: list[str] = field(default_factory=list) specificity: Specificity3 = field(default_factory=lambda: (0, 0, 0)) _name_lower: str = field(default="", repr=False) advance: int = 1 @property def css(self) -> str: """Rebuilds the selector as it would appear in CSS.""" pseudo_suffix = "".join(f":{name}" for name in self.pseudo_classes) if self.type == SelectorType.UNIVERSAL: return "*" elif self.type == SelectorType.TYPE: return f"{self.name}{pseudo_suffix}" elif self.type == SelectorType.CLASS: return f".{self.name}{pseudo_suffix}" else: return f"#{self.name}{pseudo_suffix}" def __post_init__(self) -> None: self._name_lower = self.name.lower() self._checks = { SelectorType.UNIVERSAL: self._check_universal, SelectorType.TYPE: self._check_type, SelectorType.CLASS: self._check_class, SelectorType.ID: self._check_id, } def _add_pseudo_class(self, pseudo_class: str) -> None: """Adds a pseudo class and updates specificity. Args: pseudo_class (str): Name of pseudo class. """ self.pseudo_classes.append(pseudo_class) specificity1, specificity2, specificity3 = self.specificity self.specificity = (specificity1, specificity2 + 1, specificity3) def check(self, node: DOMNode) -> bool: """Check if a given node matches the selector. Args: node (DOMNode): A DOM node. Returns: bool: True if the selector matches, otherwise False. """ return self._checks[self.type](node) def _check_universal(self, node: DOMNode) -> bool: return node.has_pseudo_class(*self.pseudo_classes) def _check_type(self, node: DOMNode) -> bool: if self._name_lower not in node._css_type_names: return False if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes): return False return True def _check_class(self, node: DOMNode) -> bool: if not node.has_class(self._name_lower): return False if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes): return False return True def _check_id(self, node: DOMNode) -> bool: if not node.id == self._name_lower: return False if self.pseudo_classes and not node.has_pseudo_class(*self.pseudo_classes): return False return True
Class variables
var advance : int
var combinator : CombinatorType
var name : str
var pseudo_classes : list[str]
var specificity : Specificity3
var type : SelectorType
Instance variables
var css : str
-
Rebuilds the selector as it would appear in CSS.
Expand source code
@property def css(self) -> str: """Rebuilds the selector as it would appear in CSS.""" pseudo_suffix = "".join(f":{name}" for name in self.pseudo_classes) if self.type == SelectorType.UNIVERSAL: return "*" elif self.type == SelectorType.TYPE: return f"{self.name}{pseudo_suffix}" elif self.type == SelectorType.CLASS: return f".{self.name}{pseudo_suffix}" else: return f"#{self.name}{pseudo_suffix}"
Methods
def check(self, node: DOMNode) ‑> bool
-
Check if a given node matches the selector.
Args
node
:DOMNode
- A DOM node.
Returns
bool
- True if the selector matches, otherwise False.
Expand source code
def check(self, node: DOMNode) -> bool: """Check if a given node matches the selector. Args: node (DOMNode): A DOM node. Returns: bool: True if the selector matches, otherwise False. """ return self._checks[self.type](node)
class SelectorSet (selectors: list[Selector] = <factory>, specificity: Specificity3 = (0, 0, 0))
-
SelectorSet(selectors: 'list[Selector]' =
, specificity: 'Specificity3' = (0, 0, 0)) Expand source code
class SelectorSet: selectors: list[Selector] = field(default_factory=list) specificity: Specificity3 = (0, 0, 0) def __post_init__(self) -> None: SAME = CombinatorType.SAME for selector, next_selector in zip(self.selectors, self.selectors[1:]): selector.advance = int(next_selector.combinator != SAME) @property def css(self) -> str: return RuleSet._selector_to_css(self.selectors) def __rich_repr__(self) -> rich.repr.Result: selectors = RuleSet._selector_to_css(self.selectors) yield selectors yield None, self.specificity @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))
Class variables
var selectors : list[Selector]
var specificity : Specificity3
Static methods
def from_selectors(selectors: list[list[Selector]]) ‑> Iterable[SelectorSet]
-
Expand source code
@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))
Instance variables
var css : str
-
Expand source code
@property def css(self) -> str: return RuleSet._selector_to_css(self.selectors)
class SelectorType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
An enumeration.
Expand source code
class SelectorType(Enum): UNIVERSAL = 1 TYPE = 2 CLASS = 3 ID = 4
Ancestors
- enum.Enum
Class variables
var CLASS
var ID
var TYPE
var UNIVERSAL