phml.core.compiler

phml.core.compile

The heavy lifting module that compiles phml ast's to different string/file formats.

  1"""phml.core.compile
  2
  3The heavy lifting module that compiles phml ast's to different string/file
  4formats.
  5"""
  6
  7import os
  8import sys
  9from re import sub
 10from typing import TYPE_CHECKING, Any, Optional
 11from phml.core.defaults.config import EnableDefault
 12
 13from phml.core.formats import Format, Formats
 14from phml.core.formats.compile import *
 15from phml.core.nodes import AST, NODE
 16from phml.utilities import parse_component, valid_component_dict
 17
 18if TYPE_CHECKING:
 19    from phml.types.config import ConfigEnable, Config
 20
 21__all__ = ["Compiler"]
 22
 23
 24class Compiler:
 25    """Used to compile phml into other formats. HTML, PHML,
 26    JSON, Markdown, etc...
 27    """
 28
 29    ast: AST
 30    """phml ast used by the compiler to generate a new format."""
 31
 32    def __init__(
 33        self,
 34        _ast: Optional[AST] = None,
 35        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 36        enable: ConfigEnable | None = None,
 37    ):
 38        self.ast = _ast
 39        self.components = components or {}
 40        self.config: Config = {
 41            "enabled": enable or EnableDefault
 42        }
 43
 44    def add(
 45        self,
 46        *components: dict[str, dict[str, list | NODE] | AST]
 47        | tuple[str, dict[str, list | NODE] | AST],
 48    ):
 49        """Add a component to the compilers component list.
 50
 51        Components passed in can be of a few types. It can also be a
 52        dictionary of str being the name of the element to be replaced. The
 53        name can be snake case, camel case, or pascal cased. The value can
 54        either be the parsed result of the component from
 55        phml.utilities.parse_component() or the parsed ast of the component.
 56        Lastely, the component can be a tuple. The first value is the name of
 57        the element to be replaced; with the second value being either the
 58        parsed result of the component or the component's ast.
 59
 60        Note:
 61            Any duplicate components will be replaced.
 62
 63        Args:
 64            components: Any number values indicating
 65            name of the component and the the component. The name is used
 66            to replace a element with the tag==name.
 67        """
 68
 69        for component in components:
 70            if isinstance(component, dict):
 71                for key, value in component.items():
 72                    if isinstance(value, AST):
 73                        self.components[key] = {"data": parse_component(value), "cache": None}
 74                    elif isinstance(value, dict) and valid_component_dict(value):
 75                        self.components[key] = {"data": value, "cache": None}
 76            elif isinstance(component, tuple):
 77                if isinstance(component[0], str) and isinstance(component[1], AST):
 78                    self.components[component[0]] = {
 79                        "data": parse_component(component[1]),
 80                        "cache": None,
 81                    }
 82                elif isinstance(component[0], str) and valid_component_dict(component[1]):
 83                    self.components[component[0]] = {"data": component[1], "cache": None}
 84
 85        return self
 86
 87    def __construct_scope_path(self, scope: str) -> list[str]:
 88        """Get the individual sub directories to to the scopes path."""
 89        sub_dirs = sub(r"(\.+\/?)+", "", scope)
 90        sub_dirs = [sub_dir for sub_dir in os.path.split(sub_dirs) if sub_dir.strip() != ""]
 91        path = []
 92        for sub_dir in sub_dirs:
 93            if sub_dir in ["*", "**"]:
 94                raise Exception(f"Can not use wildcards in scopes: {scope}")
 95            path.append(sub_dir)
 96        return path
 97
 98    def remove(self, *components: str | NODE):
 99        """Takes either component names or components and removes them
100        from the dictionary.
101
102        Args:
103            components (str | NODE): Any str name of components or
104            node value to remove from the components list in the compiler.
105        """
106        for component in components:
107            if isinstance(component, str):
108                if component in self.components:
109                    self.components.pop(component, None)
110                else:
111                    raise KeyError(f"Invalid component name '{component}'")
112            elif isinstance(component, NODE):
113                for key, value in self.components.items():
114                    if isinstance(value["data"], dict) and value["data"]["component"] == component:
115                        self.components.pop(key, None)
116                        break
117
118        return self
119
120    def compile(
121        self,
122        _ast: Optional[AST] = None,
123        to_format: Format = Formats.HTML,
124        scopes: Optional[list[str]] = None,
125        components: Optional[dict] = None,
126        safe_vars: bool = False,
127        **kwargs: Any,
128    ) -> AST:
129        """Execute compilation to a different format."""
130
131        _ast = _ast or self.ast
132
133        if _ast is None:
134            raise Exception("Must provide an ast to compile")
135
136        # Insert the scopes into the path
137        scopes = scopes or ["./"]
138        if scopes is not None:
139
140            for scope in scopes:
141                sys.path.append(
142                    os.path.join(
143                        sys.path[0],
144                        *self.__construct_scope_path(scope),
145                    )
146                )
147
148        # Depending on the format parse with the appropriate function
149        components = components or dict()
150        cmpts = {**self.components, **components}
151        return to_format.compile(
152            _ast,
153            self.config,
154            cmpts,
155            safe_vars=safe_vars,
156            **kwargs
157        )
158
159    def render(
160        self,
161        _ast: Optional[AST] = None,
162        to_format: Format = Formats.HTML,
163        indent: Optional[int] = None,
164        scopes: Optional[list[str]] = None,
165        components: Optional[dict] = None,
166        safe_vars: bool = False,
167        **kwargs: Any,
168    ) -> str:
169        """Execute compilation to a different format."""
170
171        _ast = _ast or self.ast
172
173        if _ast is None:
174            raise Exception("Must provide an ast to compile")
175
176        # Insert the scopes into the path
177        scopes = scopes or ["./"]
178        if scopes is not None:
179
180            for scope in scopes:
181                sys.path.append(
182                    os.path.join(
183                        sys.path[0],
184                        *self.__construct_scope_path(scope),
185                    )
186                )
187
188        # Depending on the format parse with the appropriate function
189        components = components or dict()
190        cmpts = {**self.components, **components}
191        return to_format.render(
192            _ast,
193            self.config,
194            cmpts,
195            indent,
196            safe_vars=safe_vars,
197            **kwargs
198        )
class Compiler:
 25class Compiler:
 26    """Used to compile phml into other formats. HTML, PHML,
 27    JSON, Markdown, etc...
 28    """
 29
 30    ast: AST
 31    """phml ast used by the compiler to generate a new format."""
 32
 33    def __init__(
 34        self,
 35        _ast: Optional[AST] = None,
 36        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 37        enable: ConfigEnable | None = None,
 38    ):
 39        self.ast = _ast
 40        self.components = components or {}
 41        self.config: Config = {
 42            "enabled": enable or EnableDefault
 43        }
 44
 45    def add(
 46        self,
 47        *components: dict[str, dict[str, list | NODE] | AST]
 48        | tuple[str, dict[str, list | NODE] | AST],
 49    ):
 50        """Add a component to the compilers component list.
 51
 52        Components passed in can be of a few types. It can also be a
 53        dictionary of str being the name of the element to be replaced. The
 54        name can be snake case, camel case, or pascal cased. The value can
 55        either be the parsed result of the component from
 56        phml.utilities.parse_component() or the parsed ast of the component.
 57        Lastely, the component can be a tuple. The first value is the name of
 58        the element to be replaced; with the second value being either the
 59        parsed result of the component or the component's ast.
 60
 61        Note:
 62            Any duplicate components will be replaced.
 63
 64        Args:
 65            components: Any number values indicating
 66            name of the component and the the component. The name is used
 67            to replace a element with the tag==name.
 68        """
 69
 70        for component in components:
 71            if isinstance(component, dict):
 72                for key, value in component.items():
 73                    if isinstance(value, AST):
 74                        self.components[key] = {"data": parse_component(value), "cache": None}
 75                    elif isinstance(value, dict) and valid_component_dict(value):
 76                        self.components[key] = {"data": value, "cache": None}
 77            elif isinstance(component, tuple):
 78                if isinstance(component[0], str) and isinstance(component[1], AST):
 79                    self.components[component[0]] = {
 80                        "data": parse_component(component[1]),
 81                        "cache": None,
 82                    }
 83                elif isinstance(component[0], str) and valid_component_dict(component[1]):
 84                    self.components[component[0]] = {"data": component[1], "cache": None}
 85
 86        return self
 87
 88    def __construct_scope_path(self, scope: str) -> list[str]:
 89        """Get the individual sub directories to to the scopes path."""
 90        sub_dirs = sub(r"(\.+\/?)+", "", scope)
 91        sub_dirs = [sub_dir for sub_dir in os.path.split(sub_dirs) if sub_dir.strip() != ""]
 92        path = []
 93        for sub_dir in sub_dirs:
 94            if sub_dir in ["*", "**"]:
 95                raise Exception(f"Can not use wildcards in scopes: {scope}")
 96            path.append(sub_dir)
 97        return path
 98
 99    def remove(self, *components: str | NODE):
100        """Takes either component names or components and removes them
101        from the dictionary.
102
103        Args:
104            components (str | NODE): Any str name of components or
105            node value to remove from the components list in the compiler.
106        """
107        for component in components:
108            if isinstance(component, str):
109                if component in self.components:
110                    self.components.pop(component, None)
111                else:
112                    raise KeyError(f"Invalid component name '{component}'")
113            elif isinstance(component, NODE):
114                for key, value in self.components.items():
115                    if isinstance(value["data"], dict) and value["data"]["component"] == component:
116                        self.components.pop(key, None)
117                        break
118
119        return self
120
121    def compile(
122        self,
123        _ast: Optional[AST] = None,
124        to_format: Format = Formats.HTML,
125        scopes: Optional[list[str]] = None,
126        components: Optional[dict] = None,
127        safe_vars: bool = False,
128        **kwargs: Any,
129    ) -> AST:
130        """Execute compilation to a different format."""
131
132        _ast = _ast or self.ast
133
134        if _ast is None:
135            raise Exception("Must provide an ast to compile")
136
137        # Insert the scopes into the path
138        scopes = scopes or ["./"]
139        if scopes is not None:
140
141            for scope in scopes:
142                sys.path.append(
143                    os.path.join(
144                        sys.path[0],
145                        *self.__construct_scope_path(scope),
146                    )
147                )
148
149        # Depending on the format parse with the appropriate function
150        components = components or dict()
151        cmpts = {**self.components, **components}
152        return to_format.compile(
153            _ast,
154            self.config,
155            cmpts,
156            safe_vars=safe_vars,
157            **kwargs
158        )
159
160    def render(
161        self,
162        _ast: Optional[AST] = None,
163        to_format: Format = Formats.HTML,
164        indent: Optional[int] = None,
165        scopes: Optional[list[str]] = None,
166        components: Optional[dict] = None,
167        safe_vars: bool = False,
168        **kwargs: Any,
169    ) -> str:
170        """Execute compilation to a different format."""
171
172        _ast = _ast or self.ast
173
174        if _ast is None:
175            raise Exception("Must provide an ast to compile")
176
177        # Insert the scopes into the path
178        scopes = scopes or ["./"]
179        if scopes is not None:
180
181            for scope in scopes:
182                sys.path.append(
183                    os.path.join(
184                        sys.path[0],
185                        *self.__construct_scope_path(scope),
186                    )
187                )
188
189        # Depending on the format parse with the appropriate function
190        components = components or dict()
191        cmpts = {**self.components, **components}
192        return to_format.render(
193            _ast,
194            self.config,
195            cmpts,
196            indent,
197            safe_vars=safe_vars,
198            **kwargs
199        )

Used to compile phml into other formats. HTML, PHML, JSON, Markdown, etc...

Compiler( _ast: Optional[phml.core.nodes.AST.AST] = None, components: Optional[dict[str, dict[str, list | phml.core.nodes.nodes.Root | phml.core.nodes.nodes.Element | phml.core.nodes.nodes.Text | phml.core.nodes.nodes.Comment | phml.core.nodes.nodes.DocType | phml.core.nodes.nodes.Parent | phml.core.nodes.nodes.Node | phml.core.nodes.nodes.Literal]]] = None, enable: dict[typing.Literal['html', 'markdown'], bool] | None = None)
33    def __init__(
34        self,
35        _ast: Optional[AST] = None,
36        components: Optional[dict[str, dict[str, list | NODE]]] = None,
37        enable: ConfigEnable | None = None,
38    ):
39        self.ast = _ast
40        self.components = components or {}
41        self.config: Config = {
42            "enabled": enable or EnableDefault
43        }

phml ast used by the compiler to generate a new format.

45    def add(
46        self,
47        *components: dict[str, dict[str, list | NODE] | AST]
48        | tuple[str, dict[str, list | NODE] | AST],
49    ):
50        """Add a component to the compilers component list.
51
52        Components passed in can be of a few types. It can also be a
53        dictionary of str being the name of the element to be replaced. The
54        name can be snake case, camel case, or pascal cased. The value can
55        either be the parsed result of the component from
56        phml.utilities.parse_component() or the parsed ast of the component.
57        Lastely, the component can be a tuple. The first value is the name of
58        the element to be replaced; with the second value being either the
59        parsed result of the component or the component's ast.
60
61        Note:
62            Any duplicate components will be replaced.
63
64        Args:
65            components: Any number values indicating
66            name of the component and the the component. The name is used
67            to replace a element with the tag==name.
68        """
69
70        for component in components:
71            if isinstance(component, dict):
72                for key, value in component.items():
73                    if isinstance(value, AST):
74                        self.components[key] = {"data": parse_component(value), "cache": None}
75                    elif isinstance(value, dict) and valid_component_dict(value):
76                        self.components[key] = {"data": value, "cache": None}
77            elif isinstance(component, tuple):
78                if isinstance(component[0], str) and isinstance(component[1], AST):
79                    self.components[component[0]] = {
80                        "data": parse_component(component[1]),
81                        "cache": None,
82                    }
83                elif isinstance(component[0], str) and valid_component_dict(component[1]):
84                    self.components[component[0]] = {"data": component[1], "cache": None}
85
86        return self

Add a component to the compilers component list.

Components passed in can be of a few types. It can also be a dictionary of str being the name of the element to be replaced. The name can be snake case, camel case, or pascal cased. The value can either be the parsed result of the component from phml.utilities.parse_component() or the parsed ast of the component. Lastely, the component can be a tuple. The first value is the name of the element to be replaced; with the second value being either the parsed result of the component or the component's ast.

Note

Any duplicate components will be replaced.

Args
  • components: Any number values indicating
  • name of the component and the the component. The name is used
  • to replace a element with the tag==name.
 99    def remove(self, *components: str | NODE):
100        """Takes either component names or components and removes them
101        from the dictionary.
102
103        Args:
104            components (str | NODE): Any str name of components or
105            node value to remove from the components list in the compiler.
106        """
107        for component in components:
108            if isinstance(component, str):
109                if component in self.components:
110                    self.components.pop(component, None)
111                else:
112                    raise KeyError(f"Invalid component name '{component}'")
113            elif isinstance(component, NODE):
114                for key, value in self.components.items():
115                    if isinstance(value["data"], dict) and value["data"]["component"] == component:
116                        self.components.pop(key, None)
117                        break
118
119        return self

Takes either component names or components and removes them from the dictionary.

Args
  • components (str | NODE): Any str name of components or
  • node value to remove from the components list in the compiler.
def compile( self, _ast: Optional[phml.core.nodes.AST.AST] = None, to_format: phml.core.formats.format.Format = <class 'phml.core.formats.html_format.HTMLFormat'>, scopes: Optional[list[str]] = None, components: Optional[dict] = None, safe_vars: bool = False, **kwargs: Any) -> phml.core.nodes.AST.AST:
121    def compile(
122        self,
123        _ast: Optional[AST] = None,
124        to_format: Format = Formats.HTML,
125        scopes: Optional[list[str]] = None,
126        components: Optional[dict] = None,
127        safe_vars: bool = False,
128        **kwargs: Any,
129    ) -> AST:
130        """Execute compilation to a different format."""
131
132        _ast = _ast or self.ast
133
134        if _ast is None:
135            raise Exception("Must provide an ast to compile")
136
137        # Insert the scopes into the path
138        scopes = scopes or ["./"]
139        if scopes is not None:
140
141            for scope in scopes:
142                sys.path.append(
143                    os.path.join(
144                        sys.path[0],
145                        *self.__construct_scope_path(scope),
146                    )
147                )
148
149        # Depending on the format parse with the appropriate function
150        components = components or dict()
151        cmpts = {**self.components, **components}
152        return to_format.compile(
153            _ast,
154            self.config,
155            cmpts,
156            safe_vars=safe_vars,
157            **kwargs
158        )

Execute compilation to a different format.

def render( self, _ast: Optional[phml.core.nodes.AST.AST] = None, to_format: phml.core.formats.format.Format = <class 'phml.core.formats.html_format.HTMLFormat'>, indent: Optional[int] = None, scopes: Optional[list[str]] = None, components: Optional[dict] = None, safe_vars: bool = False, **kwargs: Any) -> str:
160    def render(
161        self,
162        _ast: Optional[AST] = None,
163        to_format: Format = Formats.HTML,
164        indent: Optional[int] = None,
165        scopes: Optional[list[str]] = None,
166        components: Optional[dict] = None,
167        safe_vars: bool = False,
168        **kwargs: Any,
169    ) -> str:
170        """Execute compilation to a different format."""
171
172        _ast = _ast or self.ast
173
174        if _ast is None:
175            raise Exception("Must provide an ast to compile")
176
177        # Insert the scopes into the path
178        scopes = scopes or ["./"]
179        if scopes is not None:
180
181            for scope in scopes:
182                sys.path.append(
183                    os.path.join(
184                        sys.path[0],
185                        *self.__construct_scope_path(scope),
186                    )
187                )
188
189        # Depending on the format parse with the appropriate function
190        components = components or dict()
191        cmpts = {**self.components, **components}
192        return to_format.render(
193            _ast,
194            self.config,
195            cmpts,
196            indent,
197            safe_vars=safe_vars,
198            **kwargs
199        )

Execute compilation to a different format.