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

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

28    def __init__(
29        self,
30        _ast: Optional[AST] = None,
31        components: Optional[dict[str, dict[str, list | NODE]]] = None,
32    ):
33        self.ast = _ast
34        self.components = components or {}

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

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

Arguments:
  • 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.
 86    def remove(self, *components: str | NODE):
 87        """Takes either component names or components and removes them
 88        from the dictionary.
 89
 90        Args:
 91            components (str | NODE): Any str name of components or
 92            node value to remove from the components list in the compiler.
 93        """
 94        for component in components:
 95            if isinstance(component, str):
 96                if component in self.components:
 97                    self.components.pop(component, None)
 98                else:
 99                    raise KeyError(f"Invalid component name '{component}'")
100            elif isinstance(component, NODE):
101                for key, value in self.components.items():
102                    if isinstance(value["data"], dict) and value["data"]["component"] == component:
103                        self.components.pop(key, None)
104                        break
105
106        return self

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

Arguments:
  • 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:
108    def compile(
109        self,
110        _ast: Optional[AST] = None,
111        to_format: Format = Formats.HTML,
112        scopes: Optional[list[str]] = None,
113        components: Optional[dict] = None,
114        safe_vars: bool = False,
115        **kwargs: Any,
116    ) -> AST:
117        """Execute compilation to a different format."""
118
119        _ast = _ast or self.ast
120
121        if _ast is None:
122            raise Exception("Must provide an ast to compile")
123
124        # Insert the scopes into the path
125        scopes = scopes or ["./"]
126        if scopes is not None:
127
128            for scope in scopes:
129                sys.path.append(
130                    os.path.join(
131                        sys.path[0],
132                        *self.__construct_scope_path(scope),
133                    )
134                )
135
136        # Depending on the format parse with the appropriate function
137        components = components or dict()
138        cmpts = {**self.components, **components}
139        return to_format.compile(_ast, cmpts, safe_vars=safe_vars, **kwargs)

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:
141    def render(
142        self,
143        _ast: Optional[AST] = None,
144        to_format: Format = Formats.HTML,
145        indent: Optional[int] = None,
146        scopes: Optional[list[str]] = None,
147        components: Optional[dict] = None,
148        safe_vars: bool = False,
149        **kwargs: Any,
150    ) -> str:
151        """Execute compilation to a different format."""
152
153        _ast = _ast or self.ast
154
155        if _ast is None:
156            raise Exception("Must provide an ast to compile")
157
158        # Insert the scopes into the path
159        scopes = scopes or ["./"]
160        if scopes is not None:
161
162            for scope in scopes:
163                sys.path.append(
164                    os.path.join(
165                        sys.path[0],
166                        *self.__construct_scope_path(scope),
167                    )
168                )
169
170        # Depending on the format parse with the appropriate function
171        components = components or dict()
172        cmpts = {**self.components, **components}
173        return to_format.render(_ast, cmpts, indent, safe_vars=safe_vars, **kwargs)

Execute compilation to a different format.