phml.core.core
1from io import TextIOWrapper 2from pathlib import Path 3from typing import Any, Optional 4from phml.core.defaults.config import EnableDefault 5 6from phml.core.formats import Format, Formats 7from phml.core.nodes import AST, NODE 8from phml.utilities import cmpt_name_from_path, parse_component 9 10from phml.types import Component, Components, PathLike 11from phml.types.config import EnableKeys 12 13from .compiler import Compiler 14from .parser import Parser 15 16__all__ = ["PHML", "Compiler"] 17 18class PHML: 19 """A helper class that bundles the functionality 20 of the parser and compiler together. Allows for loading source files, 21 parsing strings and dicts, rendering to a different format, and finally 22 writing the results of a render to a file. 23 """ 24 25 @property 26 def ast(self) -> AST: 27 """The parsed ast value.""" 28 return self._parser.ast 29 30 @ast.setter 31 def ast(self, _ast: AST): 32 self._parser.ast = _ast 33 34 @property 35 def components(self) -> dict: 36 """The components currently stored in the compiler.""" 37 return self._compiler.components 38 39 def __init__( 40 self, 41 *, 42 scopes: list[str] | None = None, 43 components: Components | None = None, 44 enable: dict[EnableKeys, bool] = EnableDefault, 45 **contexts: Any, 46 ): 47 self._parser = Parser() 48 self._compiler = Compiler(components=components, enable=enable) 49 self._scopes = scopes or [] 50 self._context = dict(contexts) 51 52 def expose(self, **kwargs: Any): 53 """Add additional data to the compilers global values. These values are exposed for every 54 call to render or write. 55 """ 56 self._context.update(kwargs) 57 58 def redact(self, key: str): 59 """Remove a value from the compilers globally exposed values.""" 60 self._context.pop(key, None) 61 62 def expand(self, *args: str): 63 """Add relative paths to a directory, that you want added to the python path 64 for every time render or write is called. 65 """ 66 self._scopes.extend([arg for arg in args if arg not in self._scopes]) 67 68 def restrict(self, *args: str): 69 """Remove relative paths to a directory, that are in the compilers globally added scopes. 70 This prevents them from being added to the python path. 71 """ 72 for arg in args: 73 if arg in self._scopes: 74 self._scopes.remove(arg) 75 76 def add( 77 self, 78 *components: Component, 79 strip: str = "", 80 ): 81 """Add a component to the compiler's component list. 82 83 Components passed in can be of a few types. The first type it can be is a 84 pathlib.Path type. This will allow for automatic parsing of the file at the 85 path and then the filename and parsed ast are passed to the compiler. It can 86 also be a dictionary of str being the name of the element to be replaced. 87 The name can be snake case, camel case, or pascal cased. The value can either 88 be the parsed result of the component from phml.utilities.parse_component() or the 89 parsed ast of the component. Lastely, the component can be a tuple. The first 90 value is the name of the element to be replaced; with the second value being 91 either the parsed result of the component or the component's ast. 92 93 Note: 94 Any duplicate components will be replaced. 95 96 Args: 97 components: Any number values indicating 98 name of the component and the the component. The name is used 99 to replace a element with the tag==name. 100 """ 101 102 for component in components: 103 if isinstance(component, list): 104 if not all(isinstance(path, PathLike) for path in component): 105 raise TypeError("If a component argument is a list all values must be either a \ 106str or pathlib.Path pointing to the file.") 107 for path in component: 108 self._parser.load(Path(path)) 109 self._compiler.add( 110 ( 111 cmpt_name_from_path(Path(path), strip), 112 parse_component(self._parser.ast), 113 ) 114 ) 115 elif isinstance(component, PathLike): 116 self._parser.load(Path(component)) 117 self._compiler.add( 118 ( 119 cmpt_name_from_path(Path(component), strip), 120 parse_component(self._parser.ast), 121 ) 122 ) 123 elif isinstance(component, tuple) and isinstance(component[1], PathLike): 124 self._parser.load(Path(component[1])) 125 self._compiler.add((component[0], parse_component(self._parser.ast))) 126 else: 127 self._compiler.add(component) 128 return self 129 130 def remove(self, *components: str | NODE): 131 """Remove an element from the list of element replacements. 132 133 Takes any number of strings or node objects. If a string is passed 134 it is used as the key that will be removed. If a node object is passed 135 it will attempt to find a matching node and remove it. 136 """ 137 self._compiler.remove(*components) 138 return self 139 140 def load(self, file_path: str | Path, from_format: Optional[Format] = None, auto_close: bool = True): 141 """Load a source files data and parse it to phml. 142 143 Args: 144 file_path (str | Path): The file path to the source file. 145 """ 146 self._parser.load(file_path, from_format, auto_close) 147 return self 148 149 def parse(self, data: str | dict, from_format: Format = Formats.PHML, auto_close: bool = True): 150 """Parse a str or dict object into phml. 151 152 Args: 153 data (str | dict): Object to parse to phml 154 """ 155 self._parser.parse(data, from_format, auto_close) 156 return self 157 158 def compile( 159 self, 160 file_type: Format = Formats.HTML, 161 scopes: Optional[list[str]] = None, 162 components: Optional[dict] = None, 163 **kwargs, 164 ) -> AST: 165 """Compile the parsed ast into it's fully processed form. 166 167 Args: 168 file_type (str): The format to render to. Currently support html, phml, and json. 169 indent (Optional[int], optional): The number of spaces per indent. By default it will 170 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 171 has 2 spaces. 172 173 Returns: 174 AST: The processed ast. Ast is in the final format of the passed in file_type 175 """ 176 177 scopes = scopes or [] 178 for scope in self._scopes: 179 if scope not in scopes: 180 scopes.append(scope) 181 182 return self._compiler.compile( 183 self._parser.ast, 184 to_format=file_type, 185 scopes=scopes, 186 components=components, 187 **{**self._context, **kwargs}, 188 ) 189 190 def render( 191 self, 192 file_type: Format = Formats.HTML, 193 indent: Optional[int] = None, 194 scopes: Optional[list[str]] = None, 195 components: Optional[dict] = None, 196 **kwargs, 197 ) -> str: 198 """Render the parsed ast to a different format. Defaults to rendering to html. 199 200 Args: 201 file_type (str): The format to render to. Currently support html, phml, and json. 202 indent (Optional[int], optional): The number of spaces per indent. By default it will 203 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 204 has 2 spaces. 205 206 Returns: 207 str: The rendered content in the appropriate format. 208 """ 209 210 scopes = scopes or [] 211 for scope in self._scopes: 212 if scope not in scopes: 213 scopes.append(scope) 214 215 return self._compiler.render( 216 self._parser.ast, 217 to_format=file_type, 218 indent=indent, 219 scopes=scopes, 220 components=components, 221 **{**self._context, **kwargs}, 222 ) 223 224 def write( 225 self, 226 file: str | Path | TextIOWrapper, 227 file_type: Format = Formats.HTML, 228 indent: Optional[int] = None, 229 scopes: Optional[list[str]] = None, 230 replace_suffix: bool = False, 231 components: Optional[dict] = None, 232 **kwargs, 233 ): 234 """Renders the parsed ast to a different format, then writes 235 it to a given file. Defaults to rendering and writing out as html. 236 237 Args: 238 file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened 239 file to write to. 240 241 file_type (str): The format to render the ast as. 242 243 indent (Optional[int], optional): The number of spaces per indent. By default it will 244 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 245 has 2 spaces. 246 247 scopes (list[str], None): The relative paths from the cwd to the directory that will 248 be inserted into the python path. 249 250 replace_suffix (bool): Override to use the preferred file suffix no matter what. 251 Defaults to False, as the preferred suffix will only be used if no suffix is provided. 252 253 kwargs: Any additional data to pass to the compiler that will be exposed to the 254 phml files. 255 """ 256 if isinstance(file, (str | Path)): 257 file = Path(file) 258 259 file.parent.mkdir(parents=True, exist_ok=True) 260 261 if file.suffix == "" or replace_suffix: 262 file = file.with_suffix(file_type.suffix()) 263 264 with open(file, "+w", encoding="utf-8") as dest_file: 265 dest_file.write( 266 self.render( 267 file_type=file_type, 268 indent=indent, 269 scopes=scopes, 270 components=components, 271 **kwargs, 272 ) 273 ) 274 elif isinstance(file, TextIOWrapper): 275 file.write( 276 self.render( 277 file_type=file_type, 278 indent=indent, 279 scopes=scopes, 280 components=components, 281 **kwargs, 282 ) 283 ) 284 return self
19class PHML: 20 """A helper class that bundles the functionality 21 of the parser and compiler together. Allows for loading source files, 22 parsing strings and dicts, rendering to a different format, and finally 23 writing the results of a render to a file. 24 """ 25 26 @property 27 def ast(self) -> AST: 28 """The parsed ast value.""" 29 return self._parser.ast 30 31 @ast.setter 32 def ast(self, _ast: AST): 33 self._parser.ast = _ast 34 35 @property 36 def components(self) -> dict: 37 """The components currently stored in the compiler.""" 38 return self._compiler.components 39 40 def __init__( 41 self, 42 *, 43 scopes: list[str] | None = None, 44 components: Components | None = None, 45 enable: dict[EnableKeys, bool] = EnableDefault, 46 **contexts: Any, 47 ): 48 self._parser = Parser() 49 self._compiler = Compiler(components=components, enable=enable) 50 self._scopes = scopes or [] 51 self._context = dict(contexts) 52 53 def expose(self, **kwargs: Any): 54 """Add additional data to the compilers global values. These values are exposed for every 55 call to render or write. 56 """ 57 self._context.update(kwargs) 58 59 def redact(self, key: str): 60 """Remove a value from the compilers globally exposed values.""" 61 self._context.pop(key, None) 62 63 def expand(self, *args: str): 64 """Add relative paths to a directory, that you want added to the python path 65 for every time render or write is called. 66 """ 67 self._scopes.extend([arg for arg in args if arg not in self._scopes]) 68 69 def restrict(self, *args: str): 70 """Remove relative paths to a directory, that are in the compilers globally added scopes. 71 This prevents them from being added to the python path. 72 """ 73 for arg in args: 74 if arg in self._scopes: 75 self._scopes.remove(arg) 76 77 def add( 78 self, 79 *components: Component, 80 strip: str = "", 81 ): 82 """Add a component to the compiler's component list. 83 84 Components passed in can be of a few types. The first type it can be is a 85 pathlib.Path type. This will allow for automatic parsing of the file at the 86 path and then the filename and parsed ast are passed to the compiler. It can 87 also be a dictionary of str being the name of the element to be replaced. 88 The name can be snake case, camel case, or pascal cased. The value can either 89 be the parsed result of the component from phml.utilities.parse_component() or the 90 parsed ast of the component. Lastely, the component can be a tuple. The first 91 value is the name of the element to be replaced; with the second value being 92 either the parsed result of the component or the component's ast. 93 94 Note: 95 Any duplicate components will be replaced. 96 97 Args: 98 components: Any number values indicating 99 name of the component and the the component. The name is used 100 to replace a element with the tag==name. 101 """ 102 103 for component in components: 104 if isinstance(component, list): 105 if not all(isinstance(path, PathLike) for path in component): 106 raise TypeError("If a component argument is a list all values must be either a \ 107str or pathlib.Path pointing to the file.") 108 for path in component: 109 self._parser.load(Path(path)) 110 self._compiler.add( 111 ( 112 cmpt_name_from_path(Path(path), strip), 113 parse_component(self._parser.ast), 114 ) 115 ) 116 elif isinstance(component, PathLike): 117 self._parser.load(Path(component)) 118 self._compiler.add( 119 ( 120 cmpt_name_from_path(Path(component), strip), 121 parse_component(self._parser.ast), 122 ) 123 ) 124 elif isinstance(component, tuple) and isinstance(component[1], PathLike): 125 self._parser.load(Path(component[1])) 126 self._compiler.add((component[0], parse_component(self._parser.ast))) 127 else: 128 self._compiler.add(component) 129 return self 130 131 def remove(self, *components: str | NODE): 132 """Remove an element from the list of element replacements. 133 134 Takes any number of strings or node objects. If a string is passed 135 it is used as the key that will be removed. If a node object is passed 136 it will attempt to find a matching node and remove it. 137 """ 138 self._compiler.remove(*components) 139 return self 140 141 def load(self, file_path: str | Path, from_format: Optional[Format] = None, auto_close: bool = True): 142 """Load a source files data and parse it to phml. 143 144 Args: 145 file_path (str | Path): The file path to the source file. 146 """ 147 self._parser.load(file_path, from_format, auto_close) 148 return self 149 150 def parse(self, data: str | dict, from_format: Format = Formats.PHML, auto_close: bool = True): 151 """Parse a str or dict object into phml. 152 153 Args: 154 data (str | dict): Object to parse to phml 155 """ 156 self._parser.parse(data, from_format, auto_close) 157 return self 158 159 def compile( 160 self, 161 file_type: Format = Formats.HTML, 162 scopes: Optional[list[str]] = None, 163 components: Optional[dict] = None, 164 **kwargs, 165 ) -> AST: 166 """Compile the parsed ast into it's fully processed form. 167 168 Args: 169 file_type (str): The format to render to. Currently support html, phml, and json. 170 indent (Optional[int], optional): The number of spaces per indent. By default it will 171 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 172 has 2 spaces. 173 174 Returns: 175 AST: The processed ast. Ast is in the final format of the passed in file_type 176 """ 177 178 scopes = scopes or [] 179 for scope in self._scopes: 180 if scope not in scopes: 181 scopes.append(scope) 182 183 return self._compiler.compile( 184 self._parser.ast, 185 to_format=file_type, 186 scopes=scopes, 187 components=components, 188 **{**self._context, **kwargs}, 189 ) 190 191 def render( 192 self, 193 file_type: Format = Formats.HTML, 194 indent: Optional[int] = None, 195 scopes: Optional[list[str]] = None, 196 components: Optional[dict] = None, 197 **kwargs, 198 ) -> str: 199 """Render the parsed ast to a different format. Defaults to rendering to html. 200 201 Args: 202 file_type (str): The format to render to. Currently support html, phml, and json. 203 indent (Optional[int], optional): The number of spaces per indent. By default it will 204 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 205 has 2 spaces. 206 207 Returns: 208 str: The rendered content in the appropriate format. 209 """ 210 211 scopes = scopes or [] 212 for scope in self._scopes: 213 if scope not in scopes: 214 scopes.append(scope) 215 216 return self._compiler.render( 217 self._parser.ast, 218 to_format=file_type, 219 indent=indent, 220 scopes=scopes, 221 components=components, 222 **{**self._context, **kwargs}, 223 ) 224 225 def write( 226 self, 227 file: str | Path | TextIOWrapper, 228 file_type: Format = Formats.HTML, 229 indent: Optional[int] = None, 230 scopes: Optional[list[str]] = None, 231 replace_suffix: bool = False, 232 components: Optional[dict] = None, 233 **kwargs, 234 ): 235 """Renders the parsed ast to a different format, then writes 236 it to a given file. Defaults to rendering and writing out as html. 237 238 Args: 239 file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened 240 file to write to. 241 242 file_type (str): The format to render the ast as. 243 244 indent (Optional[int], optional): The number of spaces per indent. By default it will 245 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 246 has 2 spaces. 247 248 scopes (list[str], None): The relative paths from the cwd to the directory that will 249 be inserted into the python path. 250 251 replace_suffix (bool): Override to use the preferred file suffix no matter what. 252 Defaults to False, as the preferred suffix will only be used if no suffix is provided. 253 254 kwargs: Any additional data to pass to the compiler that will be exposed to the 255 phml files. 256 """ 257 if isinstance(file, (str | Path)): 258 file = Path(file) 259 260 file.parent.mkdir(parents=True, exist_ok=True) 261 262 if file.suffix == "" or replace_suffix: 263 file = file.with_suffix(file_type.suffix()) 264 265 with open(file, "+w", encoding="utf-8") as dest_file: 266 dest_file.write( 267 self.render( 268 file_type=file_type, 269 indent=indent, 270 scopes=scopes, 271 components=components, 272 **kwargs, 273 ) 274 ) 275 elif isinstance(file, TextIOWrapper): 276 file.write( 277 self.render( 278 file_type=file_type, 279 indent=indent, 280 scopes=scopes, 281 components=components, 282 **kwargs, 283 ) 284 ) 285 return self
A helper class that bundles the functionality of the parser and compiler together. Allows for loading source files, parsing strings and dicts, rendering to a different format, and finally writing the results of a render to a file.
40 def __init__( 41 self, 42 *, 43 scopes: list[str] | None = None, 44 components: Components | None = None, 45 enable: dict[EnableKeys, bool] = EnableDefault, 46 **contexts: Any, 47 ): 48 self._parser = Parser() 49 self._compiler = Compiler(components=components, enable=enable) 50 self._scopes = scopes or [] 51 self._context = dict(contexts)
53 def expose(self, **kwargs: Any): 54 """Add additional data to the compilers global values. These values are exposed for every 55 call to render or write. 56 """ 57 self._context.update(kwargs)
Add additional data to the compilers global values. These values are exposed for every call to render or write.
59 def redact(self, key: str): 60 """Remove a value from the compilers globally exposed values.""" 61 self._context.pop(key, None)
Remove a value from the compilers globally exposed values.
63 def expand(self, *args: str): 64 """Add relative paths to a directory, that you want added to the python path 65 for every time render or write is called. 66 """ 67 self._scopes.extend([arg for arg in args if arg not in self._scopes])
Add relative paths to a directory, that you want added to the python path for every time render or write is called.
69 def restrict(self, *args: str): 70 """Remove relative paths to a directory, that are in the compilers globally added scopes. 71 This prevents them from being added to the python path. 72 """ 73 for arg in args: 74 if arg in self._scopes: 75 self._scopes.remove(arg)
Remove relative paths to a directory, that are in the compilers globally added scopes. This prevents them from being added to the python path.
77 def add( 78 self, 79 *components: Component, 80 strip: str = "", 81 ): 82 """Add a component to the compiler's component list. 83 84 Components passed in can be of a few types. The first type it can be is a 85 pathlib.Path type. This will allow for automatic parsing of the file at the 86 path and then the filename and parsed ast are passed to the compiler. It can 87 also be a dictionary of str being the name of the element to be replaced. 88 The name can be snake case, camel case, or pascal cased. The value can either 89 be the parsed result of the component from phml.utilities.parse_component() or the 90 parsed ast of the component. Lastely, the component can be a tuple. The first 91 value is the name of the element to be replaced; with the second value being 92 either the parsed result of the component or the component's ast. 93 94 Note: 95 Any duplicate components will be replaced. 96 97 Args: 98 components: Any number values indicating 99 name of the component and the the component. The name is used 100 to replace a element with the tag==name. 101 """ 102 103 for component in components: 104 if isinstance(component, list): 105 if not all(isinstance(path, PathLike) for path in component): 106 raise TypeError("If a component argument is a list all values must be either a \ 107str or pathlib.Path pointing to the file.") 108 for path in component: 109 self._parser.load(Path(path)) 110 self._compiler.add( 111 ( 112 cmpt_name_from_path(Path(path), strip), 113 parse_component(self._parser.ast), 114 ) 115 ) 116 elif isinstance(component, PathLike): 117 self._parser.load(Path(component)) 118 self._compiler.add( 119 ( 120 cmpt_name_from_path(Path(component), strip), 121 parse_component(self._parser.ast), 122 ) 123 ) 124 elif isinstance(component, tuple) and isinstance(component[1], PathLike): 125 self._parser.load(Path(component[1])) 126 self._compiler.add((component[0], parse_component(self._parser.ast))) 127 else: 128 self._compiler.add(component) 129 return self
Add a component to the compiler's component list.
Components passed in can be of a few types. The first type it can be is a pathlib.Path type. This will allow for automatic parsing of the file at the path and then the filename and parsed ast are passed to the compiler. 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.
131 def remove(self, *components: str | NODE): 132 """Remove an element from the list of element replacements. 133 134 Takes any number of strings or node objects. If a string is passed 135 it is used as the key that will be removed. If a node object is passed 136 it will attempt to find a matching node and remove it. 137 """ 138 self._compiler.remove(*components) 139 return self
Remove an element from the list of element replacements.
Takes any number of strings or node objects. If a string is passed it is used as the key that will be removed. If a node object is passed it will attempt to find a matching node and remove it.
141 def load(self, file_path: str | Path, from_format: Optional[Format] = None, auto_close: bool = True): 142 """Load a source files data and parse it to phml. 143 144 Args: 145 file_path (str | Path): The file path to the source file. 146 """ 147 self._parser.load(file_path, from_format, auto_close) 148 return self
Load a source files data and parse it to phml.
Args
- file_path (str | Path): The file path to the source file.
150 def parse(self, data: str | dict, from_format: Format = Formats.PHML, auto_close: bool = True): 151 """Parse a str or dict object into phml. 152 153 Args: 154 data (str | dict): Object to parse to phml 155 """ 156 self._parser.parse(data, from_format, auto_close) 157 return self
Parse a str or dict object into phml.
Args
- data (str | dict): Object to parse to phml
159 def compile( 160 self, 161 file_type: Format = Formats.HTML, 162 scopes: Optional[list[str]] = None, 163 components: Optional[dict] = None, 164 **kwargs, 165 ) -> AST: 166 """Compile the parsed ast into it's fully processed form. 167 168 Args: 169 file_type (str): The format to render to. Currently support html, phml, and json. 170 indent (Optional[int], optional): The number of spaces per indent. By default it will 171 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 172 has 2 spaces. 173 174 Returns: 175 AST: The processed ast. Ast is in the final format of the passed in file_type 176 """ 177 178 scopes = scopes or [] 179 for scope in self._scopes: 180 if scope not in scopes: 181 scopes.append(scope) 182 183 return self._compiler.compile( 184 self._parser.ast, 185 to_format=file_type, 186 scopes=scopes, 187 components=components, 188 **{**self._context, **kwargs}, 189 )
Compile the parsed ast into it's fully processed form.
Args
- file_type (str): The format to render to. Currently support html, phml, and json.
- indent (Optional[int], optional): The number of spaces per indent. By default it will
- use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
- has 2 spaces.
Returns
AST: The processed ast. Ast is in the final format of the passed in file_type
191 def render( 192 self, 193 file_type: Format = Formats.HTML, 194 indent: Optional[int] = None, 195 scopes: Optional[list[str]] = None, 196 components: Optional[dict] = None, 197 **kwargs, 198 ) -> str: 199 """Render the parsed ast to a different format. Defaults to rendering to html. 200 201 Args: 202 file_type (str): The format to render to. Currently support html, phml, and json. 203 indent (Optional[int], optional): The number of spaces per indent. By default it will 204 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 205 has 2 spaces. 206 207 Returns: 208 str: The rendered content in the appropriate format. 209 """ 210 211 scopes = scopes or [] 212 for scope in self._scopes: 213 if scope not in scopes: 214 scopes.append(scope) 215 216 return self._compiler.render( 217 self._parser.ast, 218 to_format=file_type, 219 indent=indent, 220 scopes=scopes, 221 components=components, 222 **{**self._context, **kwargs}, 223 )
Render the parsed ast to a different format. Defaults to rendering to html.
Args
- file_type (str): The format to render to. Currently support html, phml, and json.
- indent (Optional[int], optional): The number of spaces per indent. By default it will
- use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
- has 2 spaces.
Returns
str: The rendered content in the appropriate format.
225 def write( 226 self, 227 file: str | Path | TextIOWrapper, 228 file_type: Format = Formats.HTML, 229 indent: Optional[int] = None, 230 scopes: Optional[list[str]] = None, 231 replace_suffix: bool = False, 232 components: Optional[dict] = None, 233 **kwargs, 234 ): 235 """Renders the parsed ast to a different format, then writes 236 it to a given file. Defaults to rendering and writing out as html. 237 238 Args: 239 file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened 240 file to write to. 241 242 file_type (str): The format to render the ast as. 243 244 indent (Optional[int], optional): The number of spaces per indent. By default it will 245 use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json 246 has 2 spaces. 247 248 scopes (list[str], None): The relative paths from the cwd to the directory that will 249 be inserted into the python path. 250 251 replace_suffix (bool): Override to use the preferred file suffix no matter what. 252 Defaults to False, as the preferred suffix will only be used if no suffix is provided. 253 254 kwargs: Any additional data to pass to the compiler that will be exposed to the 255 phml files. 256 """ 257 if isinstance(file, (str | Path)): 258 file = Path(file) 259 260 file.parent.mkdir(parents=True, exist_ok=True) 261 262 if file.suffix == "" or replace_suffix: 263 file = file.with_suffix(file_type.suffix()) 264 265 with open(file, "+w", encoding="utf-8") as dest_file: 266 dest_file.write( 267 self.render( 268 file_type=file_type, 269 indent=indent, 270 scopes=scopes, 271 components=components, 272 **kwargs, 273 ) 274 ) 275 elif isinstance(file, TextIOWrapper): 276 file.write( 277 self.render( 278 file_type=file_type, 279 indent=indent, 280 scopes=scopes, 281 components=components, 282 **kwargs, 283 ) 284 ) 285 return self
Renders the parsed ast to a different format, then writes it to a given file. Defaults to rendering and writing out as html.
Args
- file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened
- file to write to.
- file_type (str): The format to render the ast as.
- indent (Optional[int], optional): The number of spaces per indent. By default it will
- use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
- has 2 spaces.
- scopes (list[str], None): The relative paths from the cwd to the directory that will
- be inserted into the python path.
- replace_suffix (bool): Override to use the preferred file suffix no matter what.
- Defaults to False, as the preferred suffix will only be used if no suffix is provided.
- kwargs: Any additional data to pass to the compiler that will be exposed to the
- phml files.
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.