phml.core.formats

phml.core.formats

A collection of Formats which represent supported file formats. Each format can parse data, either string or dict, into a phml.core.nodes.AST along with compiling a phml.core.nodes.ast into it's corresponding file formats string representation.

 1"""phml.core.formats
 2
 3A collection of Formats which represent supported file formats. Each format can
 4parse data, either string or dict, into a phml.core.nodes.AST along with compiling
 5a phml.core.nodes.ast into it's corresponding file formats string representation.
 6"""
 7from __future__ import annotations
 8
 9from dataclasses import dataclass
10
11from .format import Format
12from .html_format import HTMLFormat
13from .json_format import JSONFormat
14from .phml_format import PHMLFormat
15from .xml_format import XMLFormat
16
17
18@dataclass
19class Formats:
20    """Collection of all built-in file formats."""
21
22    PHML: Format = PHMLFormat  # pylint: disable=invalid-name
23    HTML: Format = HTMLFormat  # pylint: disable=invalid-name
24    JSON: Format = JSONFormat  # pylint: disable=invalid-name
25    XML: Format = XMLFormat  # pylint: disable=invalid-name
26
27    def __iter__(self):
28        return iter(vars(self).values())
@dataclass
class Formats:
19@dataclass
20class Formats:
21    """Collection of all built-in file formats."""
22
23    PHML: Format = PHMLFormat  # pylint: disable=invalid-name
24    HTML: Format = HTMLFormat  # pylint: disable=invalid-name
25    JSON: Format = JSONFormat  # pylint: disable=invalid-name
26    XML: Format = XMLFormat  # pylint: disable=invalid-name
27
28    def __iter__(self):
29        return iter(vars(self).values())

Collection of all built-in file formats.

class Formats.PHML(phml.core.formats.format.Format):
16class PHMLFormat(Format):
17    """Logic for parsing and compiling html files."""
18
19    extension: str = "phml"
20
21    @classmethod
22    def parse(cls, data: str, auto_close: bool = True) -> str:
23        return parse_markup(data, cls.__name__, auto_close)
24
25    @classmethod
26    def compile(
27        cls,
28        ast: AST,
29        components: Optional[dict[str, dict[str, list | NODE]]] = None,
30        **kwargs,
31    ) -> AST:
32        """Compile and process the given ast and return the resulting ast."""
33        return ast
34
35    @classmethod
36    def render(
37        cls,
38        ast: AST,
39        components: Optional[dict[str, dict[str, list | NODE]]] = None,
40        indent: int = 4,
41        **kwargs,
42    ) -> str:
43        indent = indent or 4
44
45        return ASTRenderer(ast, indent).compile()

Logic for parsing and compiling html files.

class Formats.HTML(phml.core.formats.format.Format):
 22class HTMLFormat(Format):
 23    """Logic for parsing and compiling html files."""
 24
 25    extension: list[str] = ["html", "htm"]
 26
 27    @classmethod
 28    def parse(cls, data: str, auto_close: bool = True) -> str:
 29        return parse_markup(data, cls.__name__, auto_close)
 30
 31    @classmethod
 32    def compile(
 33        cls,
 34        ast: AST,
 35        config: Config,
 36        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 37        **kwargs,
 38    ) -> AST:
 39        """Compile and process the given ast and return the resulting ast."""
 40
 41        components = components or {}
 42        src = deepcopy(ast)
 43
 44        # 1. Search for all python elements and get source info.
 45        #    - Remove when done
 46        virtual_python = VirtualPython()
 47
 48        for python_block in find_all(src, {"tag": "python"}):
 49            if (
 50                len(python_block.children) == 1
 51                and python_block.children[0].type == "text"
 52            ):
 53                virtual_python += VirtualPython(
 54                    python_block.children[0].normalized(),
 55                    context={**kwargs}
 56                )
 57
 58        remove_nodes(src, ["element", {"tag": "python"}])
 59
 60        # 2. Replace specific element node with given replacement components
 61        # replace_components(src, components, virtual_python, **kwargs)
 62
 63        # 3. Search each element and find @if, @elif, and @else
 64        #    - Execute those statements
 65
 66        apply_conditions(src, config, virtual_python, components, **kwargs)
 67
 68        for python_block in find_all(src, {"tag": "python"}):
 69            if (
 70                len(python_block.children) == 1
 71                and python_block.children[0].type == "text"
 72            ):
 73                virtual_python += VirtualPython(
 74                    python_block.children[0].normalized(),
 75                    context={**kwargs}
 76                )
 77
 78        remove_nodes(src, ["element", {"tag": "python"}])
 79
 80        # 4. Search for python blocks and process them.
 81
 82        apply_python(src, virtual_python, **kwargs)
 83        remove_nodes(src, {"tag": "slot"})
 84
 85        return src
 86
 87    @classmethod
 88    def render(
 89        cls,
 90        ast: AST,
 91        config: Config,
 92        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 93        indent: int = 4,
 94        **kwargs,
 95    ) -> str:
 96        indent = indent or 4
 97        components = components or {}
 98        src = ast
 99
100        # 1. Search for all python elements and get source info.
101        #    - Remove when done
102        virtual_python = VirtualPython()
103
104        for python_block in find_all(src, {"tag": "python"}):
105            if len(python_block.children) == 1:
106                if python_block.children[0].type == "text":
107                    virtual_python += VirtualPython(python_block.children[0].normalized())
108
109        remove_nodes(src, ["element", {"tag": "python"}])
110
111        # 2. Replace specific element node with given replacement components
112        # replace_components(src, components, virtual_python, **kwargs)
113
114        # 3. Search each element and find @if, @elif, and @else
115        #    - Execute those statements
116
117        apply_conditions(src, config, virtual_python, components, **kwargs)
118
119        for python_block in find_all(src, {"tag": "python"}):
120            if len(python_block.children) == 1:
121                if python_block.children[0].type == "text":
122                    virtual_python += VirtualPython(python_block.children[0].normalized())
123
124        remove_nodes(src, ["element", {"tag": "python"}])
125
126        # 4. Search for python blocks and process them.
127
128        apply_python(src, virtual_python, **kwargs)
129        remove_nodes(src, {"tag": "slot"})
130
131        return ASTRenderer(src, indent).compile()

Logic for parsing and compiling html files.

class Formats.JSON(phml.core.formats.format.Format):
 81class JSONFormat(Format):
 82    """Logic for parsing and compiling html files."""
 83
 84    extension: str = "json"
 85
 86    @classmethod
 87    def parse(cls, data: dict | str) -> str:
 88        if isinstance(data, str):
 89            data = loads(data)
 90
 91        if isinstance(data, dict):
 92            node = construct_tree(data)
 93            if not isinstance(node, Root):
 94                node = Root(children=[node])
 95            return AST(node)
 96
 97        raise Exception("Data passed to JSONFormat.parse must be either a str or a dict")
 98
 99    @classmethod
100    def render(
101        cls,
102        ast: AST,
103        components: Optional[dict[str, dict[str, list | NODE]]] = None,
104        indent: int = 2,
105        **kwargs,
106    ) -> str:
107        indent = indent or 2
108
109        def compile_children(node: Root | Element) -> dict:
110            data = {"type": node.type}
111
112            if node.type == "root":
113                if node.parent is not None:
114                    raise Exception("Root nodes must only occur as the root of an ast/tree")
115
116            for attr in vars(node):
117                if attr not in ["parent", "children", "num_lines"]:
118                    value = getattr(node, attr)
119                    if isinstance(value, Position):
120                        data[attr] = value.as_dict()
121                    else:
122                        data[attr] = value
123
124            if hasattr(node, "children"):
125                data["children"] = []
126                for child in visit_children(node):
127                    data["children"].append(compile_children(child))
128
129            return data
130
131        data = compile_children(ast.tree)
132        return dumps(data, indent=indent)

Logic for parsing and compiling html files.

class Formats.XML(phml.core.formats.format.Format):
 51class XMLFormat(Format):
 52    """Logic for parsing and compiling html files."""
 53
 54    extension: str = "xml"
 55
 56    @classmethod
 57    def parse(cls, data: str) -> str:
 58        if isinstance(data, Path):
 59            with open(data, "r", encoding="utf-8") as file:
 60                data = file.read()
 61
 62        if isinstance(data, str):
 63            root = fromstring(data)
 64            _namespace = namespace(root)
 65            root = AST(Root(children=[construct_element(root)]))
 66
 67            if _namespace != "":
 68                root.children[0]["xmlns"] = _namespace
 69
 70            return root
 71        raise Exception("Data passed into XMLFormat.parse must be either str or pathlib.Path")
 72
 73    @classmethod
 74    def compile(
 75        cls,
 76        ast: AST,
 77        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 78        **kwargs,
 79    ) -> AST:
 80        """Compile and process the given ast and return the resulting ast."""
 81
 82        attribs = {
 83            "version": kwargs.pop("version", None) or "1.0",
 84            "encoding": kwargs.pop("encoding", None) or "UTF-8",
 85        }
 86
 87        ast.tree.insert(0, PI("xml", attribs))
 88
 89        src = deepcopy(ast)
 90
 91        # 3. Search each element and find @if, @elif, and @else
 92        #    - Execute those statements
 93
 94        apply_conditions(src, VirtualPython(), **kwargs)
 95
 96        # 4. Search for python blocks and process them.
 97
 98        apply_python(src, VirtualPython(), **kwargs)
 99        remove_nodes(src, {"tag": "slot"})
100
101        return src
102
103    @classmethod
104    def render(
105        cls,
106        ast: AST,
107        components: Optional[dict[str, dict[str, list | NODE]]] = None,
108        indent: int = 2,
109        **kwargs,
110    ) -> str:
111        indent = indent or 2
112
113        attribs = {
114            "version": kwargs.pop("version", None) or "1.0",
115            "encoding": kwargs.pop("encoding", None) or "UTF-8",
116        }
117
118        ast.tree.insert(0, PI("xml", attribs))
119
120        src = deepcopy(ast)
121
122        # 3. Search each element and find @if, @elif, and @else
123        #    - Execute those statements
124
125        apply_conditions(src, VirtualPython(), components={}, **kwargs)
126
127        # 4. Search for python blocks and process them.
128
129        apply_python(src, VirtualPython(), **kwargs)
130        remove_nodes(src, {"tag": "slot"})
131
132        return ASTRenderer(src, indent).compile(include_doctype=False)

Logic for parsing and compiling html files.