Coverage for phml\core\compile\convert.py: 100%
67 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:07 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:07 -0600
1"""Helper methods for converting an ast to phml, html, and json."""
3from copy import deepcopy
4from json import dumps
5from typing import Optional
7from phml.nodes import AST, All_Nodes, Element, Position, Root
8from phml.utils import find_all, remove_nodes, visit_children
9from phml.virtual_python import VirtualPython
11from .util import apply_conditions, apply_python, replace_components
14def phml(ast: AST, indent: int = 0) -> str:
15 """Compile a given phml ast to a phml string with a certain indent amount."""
16 return __to_html(ast, indent)
19def html(
20 ast: AST,
21 components: Optional[dict[str, dict[str, list | All_Nodes]]] = None,
22 indent: int = 0,
23 **kwargs,
24) -> str:
25 """Compile a given phml ast to a html string with a certain indent amount.
27 Can provide components that replace certain elements in the ast tree along with additional
28 kwargs that are exposed to executed python blocks.
30 Args:
31 ast (AST): The phml ast to compile
32 components (dict[str, dict[str, list | All_Nodes]] | None): key value pairs of element name
33 and the replacement mapping. The replacement mapping holds reference to a components python,
34 script, and style elements along with the root replacement node.
35 indent (int): The offset amount to every indent
36 **kwargs (Any): Additional information that will be exposed to executed python blocks.
37 """
38 components = components or {}
39 src = deepcopy(ast)
41 # 1. Search for all python elements and get source info.
42 # - Remove when done
43 virtual_python = VirtualPython()
45 for python_block in find_all(src, {"tag": "python"}):
46 if len(python_block.children) == 1:
47 if python_block.children[0].type == "text":
48 virtual_python += VirtualPython(python_block.children[0].value)
50 remove_nodes(src, ["element", {"tag": "python"}])
52 # 2. Replace specific element node with given replacement components
53 replace_components(src, components, virtual_python, **kwargs)
55 for python_block in find_all(src, {"tag": "python"}):
56 if len(python_block.children) == 1:
57 if python_block.children[0].type == "text":
58 virtual_python += VirtualPython(python_block.children[0].value)
60 remove_nodes(src, ["element", {"tag": "python"}])
62 # 3. Search each element and find py-if, py-elif, py-else, and py-for
63 # - Execute those statements
65 apply_conditions(src, virtual_python, **kwargs)
67 # 4. Search for python blocks and process them.
69 apply_python(src, virtual_python, **kwargs)
71 return __to_html(src, indent)
74def json(ast: AST, indent: int = 0) -> str:
75 """Compile a given phml ast to a json string with a certain indent amount."""
77 def compile_children(node: Root | Element) -> dict:
78 data = {"type": node.type}
80 if node.type == "root":
81 if node.parent is not None:
82 raise Exception("Root nodes must only occur as the root of an ast/tree.")
84 for attr in vars(node):
85 if attr not in ["parent", "children"]:
86 value = getattr(node, attr)
87 if isinstance(value, Position):
88 data[attr] = value.as_dict()
89 else:
90 data[attr] = value
92 if hasattr(node, "children"):
93 data["children"] = []
94 for child in visit_children(node):
95 data["children"].append(compile_children(child))
97 return data
99 data = compile_children(ast.tree)
100 return dumps(data, indent=indent)
103def __to_html(ast: AST, offset: int = 0) -> str:
104 def compile_children(node: All_Nodes, indent: int = 0) -> list[str]:
105 data = []
106 if node.type == "element":
107 if node.startend:
108 data.append(" " * indent + node.start_tag())
109 else:
110 if (
111 len(node.children) == 1
112 and node.children[0].type == "text"
113 and node.children[0].num_lines == 1
114 ):
115 data.append(
116 "".join(
117 [
118 " " * indent + node.start_tag(),
119 node.children[0].stringify(
120 indent + offset if node.children[0].num_lines > 1 else 0
121 ),
122 node.end_tag(),
123 ]
124 )
125 )
126 else:
127 data.append(" " * indent + node.start_tag())
128 for child in visit_children(node):
129 if child.type == "element":
130 data.extend(compile_children(child, indent + offset))
131 else:
132 data.append(child.stringify(indent + offset))
133 data.append(" " * indent + node.end_tag())
134 elif node.type == "root":
135 for child in visit_children(node):
136 data.extend(compile_children(child))
137 else:
138 data.append(node.stringify(indent + offset))
139 return data
141 data = compile_children(ast.tree)
143 return "\n".join(data)