Coverage for phml\builder.py: 99%
69 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 13:50 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 13:50 -0600
1"""phml.utils.builder
3This module serves as a utility to make building elements and ast's easier.
4"""
6from __future__ import annotations
8from typing import Optional
10from phml.nodes import All_Nodes, Comment, DocType, Element, Root, Text
12__all__ = ["p"]
15def __process_children(node, children: list[str | list | int | All_Nodes]):
16 for child in children:
17 if isinstance(child, str):
18 node.children.append(Text(child, node))
19 elif isinstance(child, (float, int)):
20 node.children.append(Text(str(child), node))
21 elif isinstance(child, All_Nodes):
22 child.parent = node
23 node.children.append(child)
24 elif isinstance(child, list):
25 for nested_child in child:
26 if isinstance(nested_child, str):
27 node.children.append(Text(nested_child, node))
28 elif isinstance(nested_child, (float, int)):
29 node.children.append(Text(str(nested_child), node))
30 elif isinstance(nested_child, All_Nodes):
31 nested_child.parent = node
32 node.children.append(nested_child)
33 else:
34 raise TypeError(
35 f"Unkown type <{type(nested_child).__name__}> in {child}:\
36 {nested_child}"
37 )
40def p( # pylint: disable=[invalid-name,keyword-arg-before-vararg]
41 selector: Optional[str] = None,
42 *args: str | list | int | All_Nodes,
43):
44 """Generic factory for creating phml nodes."""
46 # Get all children | non dict objects
47 children = [child for child in args if isinstance(child, (str, list, int, All_Nodes))]
49 # Get all properties | dict objects
50 props = [prop for prop in args if isinstance(prop, dict)]
52 if selector is not None:
53 # Is a comment
54 if isinstance(selector, str) and selector.startswith("<!--"):
55 return Comment(selector.replace("<!--", "").replace("-->", ""))
57 # Is a text node
58 if (
59 isinstance(selector, str)
60 and (len(selector.split(" ")) > 1 or len(selector.split("\n")) > 1)
61 and len(args) == 0
62 ):
63 return Text(selector)
65 if not isinstance(selector, str):
66 args = [selector, *args]
67 selector = None
69 children = [child for child in args if isinstance(child, (str, list, int, All_Nodes))]
70 return parse_root(children)
71 return parse_node(selector, props, children)
73 return parse_root(children)
76def parse_root(children: list):
77 """From the given information return a built root node."""
79 node = Root()
80 __process_children(node, children)
81 return node
84def parse_node(selector: str, props: dict, children: list):
85 """From the provided selector, props, and children build an element node."""
86 from phml.utils import parse_specifiers # pylint: disable=import-outside-toplevel
88 node = parse_specifiers(selector)
89 if not isinstance(node[0], dict) or len(node[0]["attributes"]) > 0:
90 raise TypeError("Selector must be of the format `tag?[#id]?[.classes...]?`")
92 node = node[0]
94 node["tag"] = "div" if node["tag"] == "*" else node["tag"]
96 if node["tag"].lower() == "doctype":
97 str_children = [child for child in children if isinstance(child, str)]
98 if len(str_children) > 0:
99 return DocType(str_children[0])
100 return DocType()
102 if node["tag"].lower().strip() == "text":
103 return Text(
104 " ".join([str(child) for child in children if isinstance(child, (str, int, float))])
105 )
107 if node["tag"].lower().strip() == "comment":
108 return Comment(
109 " ".join([str(child) for child in children if isinstance(child, (str, int, float))])
110 )
112 properties = {}
113 for prop in props:
114 properties.update(prop)
116 if len(node["classList"]) > 0:
117 properties["class"] = "" if "class" not in properties else properties["class"]
118 properties["class"] += " ".join(node["classList"])
119 if node["id"] is not None:
120 properties["id"] = node["id"]
122 element = Element(
123 node["tag"],
124 properties=properties,
125 startend=len(children) == 0,
126 )
128 __process_children(element, children)
129 return element