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