formatting

This commit is contained in:
Will McGugan
2021-10-29 17:29:43 +01:00
parent ff0dd3e4fc
commit d8728fddc0
9 changed files with 182 additions and 60 deletions

View File

@@ -28,4 +28,4 @@ class BasicApp(App):
await self.view.mount(widget1=Placeholder(), widget2=Placeholder())
SmoothApp.run(log="textual.log")
BasicApp.run(log="textual.log")

View File

@@ -7,14 +7,14 @@ from typing import Any, Callable, ClassVar, Type, TypeVar
import warnings
from rich.control import Control
from rich.highlighter import ReprHighlighter
import rich.repr
from rich.screen import Screen
from rich.console import Console, RenderableType
from rich.measure import Measurement
from rich.pretty import Pretty
from rich.traceback import Traceback
from rich.tree import Tree
from . import events
from . import actions
@@ -137,21 +137,6 @@ class App(DOMNode):
def view(self) -> DockView:
return self._view_stack[-1]
@property
def tree(self) -> Tree:
highlighter = ReprHighlighter()
tree = Tree(highlighter(repr(self)))
def add_children(tree, node):
for child in node.children:
branch = tree.add(Pretty(child))
if tree.children:
add_children(branch, child)
branch = tree.add(Pretty(self.view))
add_children(branch, self.view)
return tree
@property
def css_type(self) -> str:
return "app"

View File

@@ -134,8 +134,8 @@ class StylesBuilder:
def process_border(self, name: str, tokens: list[Token]) -> None:
border = self._parse_border("border", tokens)
styles = self.styles
styles._border_top = styles._border_right = border
styles._border_bottom = styles._border_left = border
styles._rule_border_top = styles._rule_border_right = border
styles._rule_border_bottom = styles._rule_border_left = border
def process_border_top(self, name: str, tokens: list[Token]) -> None:
self._process_border("top", name, tokens)
@@ -156,8 +156,8 @@ class StylesBuilder:
def process_outline(self, name: str, tokens: list[Token]) -> None:
border = self._parse_border("outline", tokens)
styles = self.styles
styles._outline_top = styles._outline_right = border
styles._outline_bottom = styles._outline_left = border
styles._rule_outline_top = styles._rule_outline_right = border
styles._rule_outline_bottom = styles._rule_outline_left = border
def process_outline_top(self, name: str, tokens: list[Token]) -> None:
self._process_outline("top", name, tokens)
@@ -182,7 +182,7 @@ class StylesBuilder:
self.error(name, token1, f"expected a number (found {token1.value!r})")
if token2.name != "number":
self.error(name, token2, f"expected a number (found {token1.value!r})")
self.styles._offset = Offset(
self.styles._rule_offset = Offset(
int(float(token1.value)), int(float(token2.value))
)
@@ -194,7 +194,7 @@ class StylesBuilder:
else:
x = int(float(tokens[0].value))
y = self.styles.offset.y
self.styles._offset = Offset(x, y)
self.styles._rule_offset = Offset(x, y)
def process_offset_y(self, name: str, tokens: list[Token]) -> None:
if not tokens:
@@ -204,18 +204,21 @@ class StylesBuilder:
else:
y = int(float(tokens[0].value))
x = self.styles.offset.x
self.styles._offset = Offset(x, y)
self.styles._rule_offset = Offset(x, y)
def process_text(self, name: str, tokens: list[Token]) -> None:
style_definition = " ".join(token.value for token in tokens)
style = Style.parse(style_definition)
self.styles._text = style
self.styles._rule_text = style
def process_text_color(self, name: str, tokens: list[Token]) -> None:
for token in tokens:
if token.name in ("color", "token"):
try:
self.styles._text += Style(color=Color.parse(token.value))
new_style = (self.styles._rule_text or Style()) + Style(
color=Color.parse(token.value)
)
self.styles._rule_text = new_style
except Exception as error:
self.error(
name, token, f"failed to parse color {token.value!r}; {error}"
@@ -229,7 +232,10 @@ class StylesBuilder:
for token in tokens:
if token.name in ("color", "token"):
try:
self.styles._text += Style(bgcolor=Color.parse(token.value))
new_style = (self.styles._rule_text or Style()) + Style(
bgcolor=Color.parse(token.value)
)
self.styles._rule_text = new_style
except Exception as error:
self.error(
name, token, f"failed to parse color {token.value!r}; {error}"

View File

@@ -1,11 +1,14 @@
from __future__ import annotations
from typing import Callable
from rich import print
from dataclasses import dataclass, field
from enum import Enum
from typing import Iterable
from ..dom import DOMNode
from .styles import Styles
from .tokenize import Token
from .types import Specificity3
@@ -30,13 +33,18 @@ class Location:
column: tuple[int, int]
def _default_check(node: DOMNode) -> bool | None:
return True
@dataclass
class Selector:
name: str
combinator: CombinatorType = CombinatorType.SAME
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 = ""
@property
def css(self) -> str:
@@ -50,6 +58,42 @@ class Selector:
else:
return f"#{self.name}{psuedo_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 check(self, node: DOMNode) -> bool | None:
return self._checks[self.type](node)
def _check_universal(self, node: DOMNode) -> bool | None:
return True
def _check_type(self, node: DOMNode) -> bool | None:
if node.css_type != self._name_lower:
return False
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
return False
return True
def _check_class(self, node: DOMNode) -> bool | None:
if not node.has_class(self._name_lower):
return False
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
return False
return True
def _check_id(self, node: DOMNode) -> bool | None:
if not node.id == self._name_lower:
return False
if self.pseudo_classes and not node.has_psuedo_class(*self.pseudo_classes):
return False
return True
@dataclass
class Declaration:

View File

@@ -34,7 +34,7 @@ def parse_rule_set(tokens: Iterator[Token], token: Token) -> Iterable[RuleSet]:
rule_set = RuleSet()
get_selector = SELECTOR_MAP.get
combinator = CombinatorType.SAME
combinator = CombinatorType.DESCENDENT
selectors: list[Selector] = []
rule_selectors: list[list[Selector]] = []
styles_builder = StylesBuilder()
@@ -109,6 +109,11 @@ def parse(css: str) -> Iterable[RuleSet]:
if __name__ == "__main__":
test = """
App View {
text: red;
}
.foo.bar baz:focus, #egg .foo.baz {
/* ignore me, I'm a comment */
display: block;

View File

@@ -250,7 +250,5 @@ if __name__ == "__main__":
print(styles)
print(styles.css)
print(dir(styles))
print(RULE_NAMES)
print(styles.extract_rules((0, 1, 0)))

View File

@@ -45,11 +45,13 @@ class Stylesheet:
styles: list[tuple[Specificity3, Styles]] = []
for rule in self.rules:
print(rule)
self.apply_rule(rule, node)
def apply_rule(self, rule: RuleSet, node: DOMNode) -> None:
for selector_set in rule.selector_set:
self.check_selectors(selector_set.selectors, node)
if self.check_selectors(selector_set.selectors, node):
print(rule.css)
def check_selectors(self, selectors: list[Selector], node: DOMNode) -> bool:
node_path = node.css_path
@@ -60,19 +62,80 @@ class Stylesheet:
return False
node, siblings = node_siblings
for selector in selectors:
if selector.type == SelectorType.UNIVERSAL:
continue
elif selector.type == SelectorType.TYPE:
while node.css_type != selector.name:
node_siblings = next(nodes, None)
if node_siblings is None:
SAME = CombinatorType.SAME
DESCENDENT = CombinatorType.DESCENDENT
CHILD = CombinatorType.CHILD
try:
for selector in selectors:
if selector.combinator == SAME:
if not selector.check(node):
return False
node, siblings = node_siblings
elif selector.type == SelectorType.CLASS:
while node.css_type != selector.name:
node_siblings = next(nodes, None)
if node_siblings is None:
elif selector.combinator == DESCENDENT:
while True:
node, siblings = next(nodes)
if selector.check(node):
break
elif selector.combinator == CHILD:
node, siblings = next(nodes)
if not selector.check(node):
return False
node, siblings = node_siblings
except StopIteration:
return False
return True
if __name__ == "__main__":
class Widget(DOMNode):
pass
class View(DOMNode):
pass
class App(DOMNode):
pass
app = App()
main_view = View(id="main")
help_view = View(id="help")
app.add_child(main_view)
app.add_child(help_view)
widget1 = Widget(id="widget1")
widget2 = Widget(id="widget2")
sidebar = Widget()
sidebar.add_class("float")
helpbar = Widget()
helpbar.add_class("float")
main_view.add_child(widget1)
main_view.add_child(widget2)
main_view.add_child(sidebar)
help = Widget(id="markdown")
help_view.add_child(help)
help_view.add_child(helpbar)
from rich import print
print(app.tree)
CSS = """
App > View {
text: red;
}
Widget.float {
}
"""
stylesheet = Stylesheet()
stylesheet.parse(CSS)
stylesheet.apply(sidebar)

View File

@@ -1,6 +1,9 @@
from __future__ import annotations
from rich.highlighter import ReprHighlighter
import rich.repr
from rich.pretty import Pretty
from rich.tree import Tree
from .css.styles import Styles
from .message_pump import MessagePump
@@ -41,21 +44,44 @@ class DOMNode(MessagePump):
def classes(self) -> frozenset[str]:
return frozenset(self._classes)
@property
def psuedo_classes(self) -> set[str]:
"""Get a set of all psuedo classes"""
return set()
@property
def css_type(self) -> str:
return self.__class__.__name__.lower()
@property
def css_path(self) -> list[tuple[DOMNode, list[DOMNode]]]:
result: list[tuple[DOMNode, list[DOMNode]]] = []
result: list[tuple[DOMNode, list[DOMNode]]] = [(self, self.children[:])]
append = result.append
node: DOMNode = self
while isinstance(node._parent, DOMNode):
append((node, node.children[:]))
node = node._parent
append((node, node.children[:]))
return result[::-1]
@property
def tree(self) -> Tree:
highlighter = ReprHighlighter()
tree = Tree(highlighter(repr(self)))
def add_children(tree, node):
for child in node.children:
branch = tree.add(Pretty(child))
if tree.children:
add_children(branch, child)
add_children(tree, self)
return tree
def add_child(self, node: DOMNode) -> None:
self.children._append(node)
node.set_parent(self)
def has_class(self, *class_names: str) -> bool:
return self._classes.issuperset(class_names)
@@ -71,12 +97,7 @@ class DOMNode(MessagePump):
"""Toggle class names"""
self._classes.symmetric_difference_update(class_names)
def has_psuedo_class(self, class_name: str) -> bool:
def has_psuedo_class(self, *class_names: str) -> bool:
"""Check for psuedo class (such as hover, focus etc)"""
classes = self.get_psuedo_classes()
has_psuedo_class = class_name in classes
return has_psuedo_class
def get_psuedo_classes(self) -> set[str]:
"""Get a set of all psuedo classes"""
return set()
has_psuedo_classes = self.psuedo_classes.issuperset(class_names)
return has_psuedo_classes

View File

@@ -123,16 +123,16 @@ class Widget(DOMNode):
widget (Widget): Widget
"""
self.app.register(widget, self)
self.registry._append(widget)
self.children._append(widget)
return widget
def get_child(self, name: str | None = None, id: str | None = None) -> Widget:
if name is not None:
for widget in self.registry:
for widget in self.children:
if widget.name == name:
return widget
if id is not None:
for widget in self.registry:
for widget in self.children:
if widget.id == id:
return widget
raise errors.MissingWidget(f"Widget named {name!r} was not found in {self}")