Coverage for phml\builder.py: 100%
60 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-06 16:04 -0500
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-06 16:04 -0500
1"""phml.utilities.builder
3This module serves as a utility to make building elements and ast's easier.
4"""
6from __future__ import annotations
8from typing import Literal as Lit
9from typing import overload
11from phml.nodes import AST, Element, Literal, LiteralType, Node, Parent
13__all__ = ["p"]
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 )
58@overload
59def p(selector: Node | None = None, *args: str | list | dict | int | Node) -> AST:
60 ...
63@overload
64def p(selector: str, *args: str | list | dict | int | Node) -> Element:
65 ...
68@overload
69def p(selector: Lit["text", "comment"], *args: str) -> Literal:
70 ...
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."""
79 # Get all children | non dict objects
80 children = [child for child in args if isinstance(child, (str, list, int, Node))]
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 }
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
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)
111 return parse_root(children)
114def parse_root(children: list):
115 """From the given information return a built root node."""
117 node = AST()
118 __process_children(node, children)
119 return node
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 )
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...]?`")
132 node = node[0]
134 node["tag"] = "div" if node["tag"] == "*" else node["tag"]
136 if node["tag"].lower() == "doctype":
137 return Element("doctype", {"html": True})
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 )
162 properties = {**props}
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"]
170 element = Element(
171 node["tag"],
172 attributes=properties,
173 children=[] if len(children) > 0 else None,
174 )
176 __process_children(element, children)
177 return element