phml.core.formats.compile.reserved
1from __future__ import annotations 2 3from copy import deepcopy 4from re import match, sub 5from traceback import print_exc 6 7from saimll import SAIML 8from markdown2 import markdown 9 10from phml.core.nodes import Root, Element, Text 11from phml.core.virtual_python import VirtualPython, get_python_result 12from phml.utilities import ( 13 visit_children, 14 check, 15 replace_node, 16 sanatize 17) 18 19__all__ = [ 20 "RESERVED" 21] 22 23EXTRAS = [ 24 "cuddled-lists", 25 "fenced-code-blocks", 26 "header-ids", 27 "footnotes", 28 "strike", 29] 30 31def process_loops(node: Root | Element, virtual_python: VirtualPython, **kwargs): 32 """Expands all `<For />` tags giving their children context for each iteration.""" 33 34 for_loops = [ 35 loop 36 for loop in visit_children(node) 37 if check(loop, {"tag": "For", ":each": True}) 38 ] 39 40 kwargs.update(virtual_python.context) 41 42 for loop in for_loops: 43 if loop[":each"].strip() != "": 44 children = run_phml_for(loop, **kwargs) 45 replace_node(node, loop, children) 46 else: 47 replace_node(node, loop, None) 48 49def process_markdown(node: Root | Element, virtual_python: VirtualPython, **kwargs): 50 """Replace the `<Markdown />` element with it's `src` attributes parsed markdown string.""" 51 52 from phml import PHML 53 54 md_elems: list[Element] = [ 55 loop 56 for loop in visit_children(node) 57 if check(loop, {"tag": "Markdown"}) 58 ] 59 60 kwargs.update(virtual_python.context) 61 context = build_locals(node, **kwargs) 62 63 # Don't escape the html values from context for html tags in markdown strings 64 kwargs["safe_vars"] = True 65 66 for elem in md_elems: 67 if elem.startend and ":src" in elem or "src" in elem: 68 if ":src" in elem: 69 src = str(get_python_result(elem[":src"], **context)) 70 else: 71 src = str(elem["src"]) 72 73 html = markdown(src, extras=EXTRAS) 74 replace_node(node, elem, PHML().parse(html).ast.tree.children) 75 elif not elem.startend and len(elem.children) == 1 and isinstance(elem.children[0], Text): 76 html = markdown(elem.children[0].normalized(), extras=EXTRAS) 77 replace_node(node, elem, PHML().parse(html).ast.tree.children) 78 else: 79 replace_node(node, elem, None) 80 81def process_html(node: Root | Element, virtual_python: VirtualPython, **kwargs): 82 """Replace the `<HTML />` element with it's `src` attributes html string.""" 83 84 from phml import PHML 85 86 md_elems: list[Element] = [ 87 loop 88 for loop in visit_children(node) 89 if check(loop, {"tag": "HTML"}) 90 ] 91 92 kwargs.update(virtual_python.context) 93 context = build_locals(node, **kwargs) 94 95 # Don't escape the html values from context 96 kwargs["safe_vars"] = True 97 98 for elem in md_elems: 99 if not elem.startend: 100 raise Exception(f"<HTML /> elements are not allowed to have children elements: {elem.position}") 101 102 if ":src" in elem or "src" in elem: 103 if ":src" in elem: 104 src = str(get_python_result(elem[":src"], **context)) 105 else: 106 src = str(elem["src"]) 107 108 ast = PHML().parse(src).ast 109 # sanatize(ast) 110 111 replace_node(node, elem, ast.tree.children) 112 113def build_locals(child, **kwargs) -> dict: 114 """Build a dictionary of local variables from a nodes inherited locals and 115 the passed kwargs. 116 """ 117 from phml.utilities import path # pylint: disable=import-outside-toplevel 118 119 clocals = {**kwargs} 120 121 # Inherit locals from top down 122 for parent in path(child): 123 if parent.type == "element": 124 clocals.update(parent.context) 125 126 clocals.update(child.context) 127 return clocals 128 129def run_phml_for(node: Element, **kwargs) -> list: 130 """Repeat the nested elements inside the `<For />` elements for the iterations provided by the 131 `:each` attribute. The values from the `:each` attribute are exposed as context for each node in 132 the `<For />` element for each iteration. 133 134 Args: 135 node (Element): The `<For />` element that is to be used. 136 """ 137 clocals = build_locals(node) 138 139 # Format for loop condition 140 for_loop = sub(r"for |:\s*$", "", node[":each"]).strip() 141 142 # Get local var names from for loop condition 143 items = match(r"(for )?(.*)(?<= )in(?= )(.+)", for_loop) 144 145 new_locals = [ 146 item.strip() 147 for item in sub( 148 r"\s+", 149 " ", 150 items.group(2), 151 ).split(",") 152 ] 153 154 source = items.group(3) 155 156 # Formatter for key value pairs 157 key_value = "\"{key}\": {key}" 158 159 # Set children position to 0 since all copies are generated 160 children = node.children 161 for child in children: 162 child.position = None 163 164 def children_with_context(context: dict): 165 new_children = [] 166 for child in children: 167 new_child = deepcopy(child) 168 if check(new_child, "element"): 169 new_child.context.update(context) 170 new_children.append(new_child) 171 return new_children 172 173 expression = for_loop # original expression 174 175 for_loop = f'''\ 176new_children = [] 177for {for_loop}: 178 new_children.extend( 179 children_with_context( 180 {{{", ".join([f"{key_value.format(key=key)}" for key in new_locals])}}} 181 ) 182 ) 183''' 184 185 # Construct locals for dynamic for loops execution 186 local_env = { 187 "local_vals": clocals, 188 } 189 190 try: 191 # Execute dynamic for loop 192 exec( # pylint: disable=exec-used 193 for_loop, 194 { 195 **kwargs, 196 **globals(), 197 **clocals, 198 "children_with_context": children_with_context 199 }, 200 local_env, 201 ) 202 except Exception: # pylint: disable=broad-except 203 SAIML.print(f"\\[[@Fred]*Error[@]\\] Failed to execute loop expression \ 204[@Fblue]@for[@]=[@Fgreen]'[@]{expression}[@Fgreen]'[@]") 205 print_exc() 206 207 # Return the new complete list of children after generation 208 return local_env["new_children"] 209 210RESERVED = { 211 "For": process_loops, 212 "Markdown": process_markdown, 213 "HTML": process_html 214}
RESERVED = {'For': <function process_loops>, 'Markdown': <function process_markdown>, 'HTML': <function process_html>}