From a9f15f5879f6f43a779af8ba5f20c957050aaac2 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Fri, 9 Feb 2024 19:26:13 -0800 Subject: [PATCH] Link Django CSS to components (CSS Modules) --- requirements/pkg-deps.txt | 1 + src/reactpy_django/css_modules.py | 124 ++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/reactpy_django/css_modules.py diff --git a/requirements/pkg-deps.txt b/requirements/pkg-deps.txt index c6102c18..e49f12d9 100644 --- a/requirements/pkg-deps.txt +++ b/requirements/pkg-deps.txt @@ -7,3 +7,4 @@ dill >=0.3.5 orjson >=3.6.0 nest_asyncio >=1.5.0 typing_extensions +tinycss2 >=1.0.2 diff --git a/src/reactpy_django/css_modules.py b/src/reactpy_django/css_modules.py new file mode 100644 index 00000000..e9263399 --- /dev/null +++ b/src/reactpy_django/css_modules.py @@ -0,0 +1,124 @@ +import re +from dataclasses import dataclass, field +from typing import Any, Literal, TypedDict + +import tinycss2 +from tinycss2.ast import QualifiedRule + +file_text = open("example.css", "r").read() + + +SquareBracketValues = TypedDict( + "SquareBracketValues", + {"key": str, "operator": str | None, "value": str | list | None}, +) + + +@dataclass +class CssRule: + """Represents a CSS selector. + Examples of the parser's targets are shown below as docstrings.""" + + tag: str | Literal["*"] | None = None + """div, span""" + id: str | None = None + """#id1, #id2""" + classes: list[str] = field(default_factory=list) + """.class1, .class2""" + attributes: list[SquareBracketValues] = field(default_factory=list) + """[key=value], [key^=value]""" + styles: dict[str, Any] = field(default_factory=dict) + """color: red;""" + psuedo_functions: list[str] = field(default_factory=list) + """:not(), :nth-child()""" + psuedo_elements: list[str] = field(default_factory=list) + """:hover, :focus""" + psuedo_classes: list[str] = field(default_factory=list) + """::before, ::after""" + + next: list["CssRule"] = field(default_factory=list) + """#current-selector #next-selector""" + next_operator: str = " " + """>, +, ~""" + + +@dataclass +class CssToPython: + """Performs a best-effort conversion of a CSS file into a list of Python dictionaries.""" + + content: str + + def convert(self) -> list[CssRule]: + """Converts the CSS file into a list of CSS rule dictionaries.""" + self.parsed: list[QualifiedRule] = tinycss2.parse_stylesheet( + self.content, + skip_comments=True, + skip_whitespace=True, + ) + self.selectors: list[CssRule] = [] + + for style in self.parsed: + if style.type != "qualified-rule": + continue + selector = CssRule() + + # Determine what CSS rules are defined in the selector + computed_rules = tinycss2.parse_declaration_list( + style.content, skip_comments=True, skip_whitespace=True + ) + for rule in computed_rules: + selector.styles[rule.name] = self._parse_rule_value(rule.value) + + # Parse HTML tag name from the selector + if style.prelude[0].type == "ident": + selector.tag = style.prelude[0].value + elif style.prelude[0].type == "literal" and style.prelude[0].value == "*": + selector.tag = "*" + + # Parse all other attributes from the selector + print(style.prelude) + for count, token in enumerate(style.prelude): + if token.type == "hash": + selector.id = selector.id or token.value + elif token.type == "literal" and token.value == ".": + selector.classes.append(style.prelude[count + 1].value) + elif token.type == "[] block" and len(token.content) == 1: + selector.attributes.append( + { + "key": token.content[0].value, + "operator": None, + "value": None, + } + ) + elif token.type == "[] block" and len(token.content) == 3: + selector.attributes.append( + { + "key": token.content[0].value, + "operator": token.content[1].value, + "value": token.content[2].value, + } + ) + # TODO: Once we reach an operator or whitespace, recursively parse the children then break + # TODO: Split comma operators into separate selectors + + self.selectors.append(selector) + + return self.selectors + + @staticmethod + def _flatten_whitespace(string: str) -> str: + return re.sub(r"\s+", " ", string).strip() + + def _parse_rule_value(self, rule): + """Parses a single TinyCSS rule and returns Python a data type.""" + rule = tinycss2.serialize(rule) + rule = self._flatten_whitespace(rule) + if rule.isnumeric(): + return int(rule) + else: + return rule + + +styles = CssToPython(file_text).convert() +for style in styles: + print(style) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy