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)
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...
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.
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.
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.