phml.builder

phml.utilities.builder

This module serves as a utility to make building elements and ast's easier.

  1"""phml.utilities.builder
  2
  3This module serves as a utility to make building elements and ast's easier.
  4"""
  5
  6from __future__ import annotations
  7
  8from typing import Literal as Lit
  9from typing import overload
 10
 11from phml.nodes import AST, Element, Literal, LiteralType, Node, Parent
 12
 13__all__ = ["p"]
 14
 15
 16def __process_children(node, children: list[str | list | int | Node]):
 17    for child in children:
 18        if isinstance(child, (str, float, int)):
 19            if (
 20                isinstance(child, str)
 21                and child.startswith("<!--")
 22                and child.endswith("-->")
 23            ):
 24                child = child.strip()
 25                node.append(
 26                    Literal(LiteralType.Comment, child.lstrip("<!--").rstrip("-->"))
 27                )
 28            else:
 29                node.append(Literal(LiteralType.Text, str(child)))
 30        elif isinstance(child, Node):
 31            node.append(child)
 32        elif isinstance(child, list):
 33            for nested_child in child:
 34                if isinstance(nested_child, (str, float, int)):
 35                    if (
 36                        isinstance(nested_child, str)
 37                        and nested_child.startswith("<!--")
 38                        and nested_child.endswith("-->")
 39                    ):
 40                        nested_child = nested_child.strip()
 41                        node.append(
 42                            Literal(
 43                                LiteralType.Comment,
 44                                nested_child.lstrip("<!--").rstrip("-->"),
 45                            )
 46                        )
 47                    else:
 48                        node.append(Literal(LiteralType.Text, str(nested_child)))
 49                elif isinstance(nested_child, Node):
 50                    node.append(nested_child)
 51                else:
 52                    raise TypeError(
 53                        f"Unkown type <{type(nested_child).__name__}> in {child}:\
 54 {nested_child}",
 55                    )
 56
 57
 58@overload
 59def p(selector: Node | None = None, *args: str | list | dict | int | Node) -> AST:
 60    ...
 61
 62
 63@overload
 64def p(selector: str, *args: str | list | dict | int | Node) -> Element:
 65    ...
 66
 67
 68@overload
 69def p(selector: Lit["text", "comment"], *args: str) -> Literal:
 70    ...
 71
 72
 73def p(  # pylint: disable=[invalid-name,keyword-arg-before-vararg]
 74    selector: str | Node | None = None,
 75    *args: str | list | dict | int | Node | None,
 76) -> Node | AST | Parent:
 77    """Generic factory for creating phml nodes."""
 78
 79    # Get all children | non dict objects
 80    children = [child for child in args if isinstance(child, (str, list, int, Node))]
 81
 82    # Get all properties | dict objects
 83    props = {
 84        key: value
 85        for prop in args
 86        if isinstance(prop, dict)
 87        for key, value in prop.items()
 88    }
 89
 90    if selector is not None:
 91        # Is a comment
 92        # if isinstance(selector, str) and selector.startswith("<!--"):
 93        #     return Literal(LiteralType.Comment, selector.replace("<!--", "").replace("-->", ""))
 94        # Is a text node
 95        # if (
 96        #     isinstance(selector, str)
 97        #     and (len(selector.split(" ")) > 1 or len(selector.split("\n")) > 1)
 98        #     and len(args) == 0
 99        # ):
100        #     return Literal(LiteralType.Text, selector)
101        if not isinstance(selector, str):
102            args = (selector, *args)
103            selector = None
104
105            children = [
106                child for child in args if isinstance(child, (str, list, int, Node))
107            ]
108            return parse_root(children)
109        return parse_node(selector, props, children)
110
111    return parse_root(children)
112
113
114def parse_root(children: list):
115    """From the given information return a built root node."""
116
117    node = AST()
118    __process_children(node, children)
119    return node
120
121
122def parse_node(selector: str, props: dict, children: list):
123    """From the provided selector, props, and children build an element node."""
124    from phml.utilities import (
125        parse_specifiers,  # pylint: disable=import-outside-toplevel
126    )
127
128    node = parse_specifiers(selector)
129    if not isinstance(node[0], dict) or len(node[0]["attributes"]) > 0:
130        raise TypeError("Selector must be of the format `tag?[#id]?[.classes...]?`")
131
132    node = node[0]
133
134    node["tag"] = "div" if node["tag"] == "*" else node["tag"]
135
136    if node["tag"].lower() == "doctype":
137        return Element("doctype", {"html": True})
138
139    if node["tag"].lower().strip() == "text":
140        return Literal(
141            LiteralType.Text,
142            " ".join(
143                [
144                    str(child)
145                    for child in children
146                    if isinstance(child, (str, int, float))
147                ]
148            ),
149        )
150    if node["tag"].lower().strip() == "comment":
151        return Literal(
152            LiteralType.Comment,
153            " ".join(
154                [
155                    str(child)
156                    for child in children
157                    if isinstance(child, (str, int, float))
158                ]
159            ),
160        )
161
162    properties = {**props}
163
164    if len(node["classList"]) > 0:
165        properties["class"] = "" if "class" not in properties else properties["class"]
166        properties["class"] += " ".join(node["classList"])
167    if node["id"] is not None:
168        properties["id"] = node["id"]
169
170    element = Element(
171        node["tag"],
172        attributes=properties,
173        children=[] if len(children) > 0 else None,
174    )
175
176    __process_children(element, children)
177    return element
def p( selector: str | phml.nodes.Node | None = None, *args: str | list | dict | int | phml.nodes.Node | None) -> phml.nodes.Node | phml.nodes.AST | phml.nodes.Parent:
 74def p(  # pylint: disable=[invalid-name,keyword-arg-before-vararg]
 75    selector: str | Node | None = None,
 76    *args: str | list | dict | int | Node | None,
 77) -> Node | AST | Parent:
 78    """Generic factory for creating phml nodes."""
 79
 80    # Get all children | non dict objects
 81    children = [child for child in args if isinstance(child, (str, list, int, Node))]
 82
 83    # Get all properties | dict objects
 84    props = {
 85        key: value
 86        for prop in args
 87        if isinstance(prop, dict)
 88        for key, value in prop.items()
 89    }
 90
 91    if selector is not None:
 92        # Is a comment
 93        # if isinstance(selector, str) and selector.startswith("<!--"):
 94        #     return Literal(LiteralType.Comment, selector.replace("<!--", "").replace("-->", ""))
 95        # Is a text node
 96        # if (
 97        #     isinstance(selector, str)
 98        #     and (len(selector.split(" ")) > 1 or len(selector.split("\n")) > 1)
 99        #     and len(args) == 0
100        # ):
101        #     return Literal(LiteralType.Text, selector)
102        if not isinstance(selector, str):
103            args = (selector, *args)
104            selector = None
105
106            children = [
107                child for child in args if isinstance(child, (str, list, int, Node))
108            ]
109            return parse_root(children)
110        return parse_node(selector, props, children)
111
112    return parse_root(children)

Generic factory for creating phml nodes.