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 markdown 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 31MARKDOWN = Markdown(extensions=["codehilite", "tables", "fenced_code"]) 32 33def process_loops(node: Root | Element, virtual_python: VirtualPython, **kwargs): 34 """Expands all `<For />` tags giving their children context for each iteration.""" 35 36 for_loops = [ 37 loop 38 for loop in visit_children(node) 39 if check(loop, {"tag": "For"}) 40 ] 41 42 kwargs.update(virtual_python.context) 43 44 for loop in for_loops: 45 each = loop.get(":each", loop.get("each")) 46 if each is not None and each.strip() != "": 47 children = run_phml_for(loop, **kwargs) 48 replace_node(node, loop, children) 49 else: 50 replace_node(node, loop, None) 51 52def process_markdown(node: Root | Element, virtual_python: VirtualPython, **kwargs): 53 """Replace the `<Markdown />` element with it's `src` attributes parsed markdown 54 string.""" 55 56 from phml import PHML 57 58 md_elems: list[Element] = [ 59 loop 60 for loop in visit_children(node) 61 if check(loop, {"tag": "Markdown"}) 62 ] 63 64 kwargs.update(virtual_python.context) 65 context = build_locals(node, **kwargs) 66 67 # Don't escape the html values from context for html tags in markdown strings 68 kwargs["safe_vars"] = True 69 70 for elem in md_elems: 71 markdown = MARKDOWN 72 rendered_html = [] 73 74 # If extras are provided then add them as extensions. Don't allow configs 75 # and only allow extensions built into the markdown package. 76 if "extra" in elem: 77 markdown = deepcopy(MARKDOWN) 78 markdown.registerExtensions( 79 [ 80 extra.strip() 81 for extra in elem["extra"].split(" ") 82 if extra.strip() != "" 83 ], 84 {} 85 ) 86 elif ":extra" in elem: 87 extra_list = get_python_result(elem[":extra"], **context) 88 if not isinstance(extra_list, list): 89 raise TypeError("Expected extra's to be a list of strings") 90 markdown = deepcopy(MARKDOWN) 91 markdown.registerExtensions(extra_list, {}) 92 93 # Append the rendered markdown from the children, src, and referenced 94 # file in that order 95 if ( 96 not elem.startend 97 and len(elem.children) == 1 98 and isinstance(elem.children[0], Text) 99 ): 100 html = markdown.reset().convert(elem.children[0].normalized()) 101 rendered_html.append(html) 102 103 if ":src" in elem or "src" in elem: 104 if ":src" in elem: 105 src = str( 106 get_python_result( 107 elem[":src"], 108 **context 109 ) 110 ) 111 else: 112 src = elem["src"] 113 html = markdown.reset().convert(src) 114 rendered_html.append(html) 115 116 if "file" in elem: 117 with open(elem["file"], "r", encoding="utf-8") as md_file: 118 html = markdown.reset().convert(md_file.read()) 119 120 rendered_html.append(html) 121 122 # Replace node with rendered nodes of the markdown. Remove node if no 123 # markdown was provided 124 if len(rendered_html) > 0: 125 replace_node( 126 node, 127 elem, 128 PHML().parse('\n'.join(rendered_html)).ast.tree.children 129 ) 130 else: 131 replace_node( 132 node, 133 elem, 134 None 135 ) 136 137 138def process_html( 139 node: Root | Element, 140 virtual_python: VirtualPython, 141 **kwargs 142): 143 """Replace the `<HTML />` element with it's `src` attributes html string. 144 """ 145 146 from phml import PHML 147 148 html_elems: list[Element] = [ 149 elem 150 for elem in visit_children(node) 151 if check(elem, {"tag": "HTML"}) 152 ] 153 154 kwargs.update(virtual_python.context) 155 context = build_locals(node, **kwargs) 156 157 # Don't escape the html values from context 158 kwargs["safe_vars"] = True 159 160 for elem in html_elems: 161 if not elem.startend: 162 raise TypeError( 163 f"<HTML /> elements are not allowed to have children \ 164elements: {elem.position}" 165 ) 166 167 if ":src" in elem or "src" in elem: 168 if ":src" in elem: 169 src = str(get_python_result(elem[":src"], **context)) 170 else: 171 src = str(elem["src"]) 172 173 ast = PHML().parse(src).ast 174 print(ast.tree) 175 # sanatize(ast) 176 177 replace_node(node, elem, ast.tree.children) 178 else: 179 print("REMOVING HTML NODE") 180 replace_node(node, elem, None) 181 182def build_locals(child, **kwargs) -> dict: 183 """Build a dictionary of local variables from a nodes inherited locals and 184 the passed kwargs. 185 """ 186 from phml.utilities import path # pylint: disable=import-outside-toplevel 187 188 clocals = {**kwargs} 189 190 # Inherit locals from top down 191 for parent in path(child): 192 if parent.type == "element": 193 clocals.update(parent.context) 194 195 if hasattr(child, "context"): 196 clocals.update(child.context) 197 return clocals 198 199def run_phml_for(node: Element, **kwargs) -> list: 200 """Repeat the nested elements inside the `<For />` elements for the iterations provided by the 201 `:each` attribute. The values from the `:each` attribute are exposed as context for each node in 202 the `<For />` element for each iteration. 203 204 Args: 205 node (Element): The `<For />` element that is to be used. 206 """ 207 clocals = build_locals(node) 208 209 # Format for loop condition 210 for_loop = sub(r"for |:\s*$", "", node.get(":each", node.get("each"))).strip() 211 212 # Get local var names from for loop condition 213 items = match(r"(for )?(.*)(?<= )in(?= )(.+)", for_loop) 214 215 new_locals = [ 216 item.strip() 217 for item in sub( 218 r"\s+", 219 " ", 220 items.group(2), 221 ).split(",") 222 ] 223 224 items.group(3) 225 226 # Formatter for key value pairs 227 key_value = "\"{key}\": {key}" 228 229 # Set children position to 0 since all copies are generated 230 children = node.children 231 for child in children: 232 child.position = None 233 234 def children_with_context(context: dict): 235 new_children = [] 236 for child in children: 237 new_child = deepcopy(child) 238 if check(new_child, "element"): 239 new_child.context.update(context) 240 new_children.append(new_child) 241 return new_children 242 243 expression = for_loop # original expression 244 245 for_loop = f'''\ 246new_children = [] 247for {for_loop}: 248 new_children.extend( 249 children_with_context( 250 {{{", ".join([f"{key_value.format(key=key)}" for key in new_locals])}}} 251 ) 252 ) 253''' 254 255 # Construct locals for dynamic for loops execution 256 local_env = { 257 "local_vals": clocals, 258 } 259 260 try: 261 # Execute dynamic for loop 262 exec( # pylint: disable=exec-used 263 for_loop, 264 { 265 **kwargs, 266 **globals(), 267 **clocals, 268 "children_with_context": children_with_context 269 }, 270 local_env, 271 ) 272 except Exception: # pylint: disable=broad-except 273 SAIML.print(f"\\[[@Fred]*Error[@]\\] Failed to execute loop expression \ 274[@Fblue]@for[@]=[@Fgreen]'[@]{expression}[@Fgreen]'[@]") 275 print_exc() 276 277 # Return the new complete list of children after generation 278 return local_env["new_children"] 279 280RESERVED = { 281 "For": process_loops, 282 "Markdown": process_markdown, 283 "HTML": process_html 284}
RESERVED = {'For': <function process_loops>, 'Markdown': <function process_markdown>, 'HTML': <function process_html>}