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>}