phml.core
All core parsing, compiling, and valid file_types.
18class Compiler: 19 """Used to compile phml into other formats. HTML, PHML, 20 JSON, Markdown, etc... 21 """ 22 23 ast: AST 24 """phml ast used by the compiler to generate a new format.""" 25 26 def __init__( 27 self, 28 ast: Optional[AST] = None, 29 components: Optional[dict[str, dict[str, list | All_Nodes]]] = None, 30 ): 31 self.ast = ast 32 self.components = components or {} 33 34 def add( 35 self, 36 *components: dict[str, dict[str, list | All_Nodes] | AST] 37 | tuple[str, dict[str, list | All_Nodes] | AST], 38 ): 39 """Add a component to the compilers component list. 40 41 Components passed in can be of a few types. It can also be a dictionary of str 42 being the name of the element to be replaced. The name can be snake case, camel 43 case, or pascal cased. The value can either be the parsed result of the component 44 from phml.utils.parse_component() or the parsed ast of the component. Lastely, 45 the component can be a tuple. The first value is the name of the element to be 46 replaced; with the second value being either the parsed result of the component 47 or the component's ast. 48 49 Note: 50 Any duplicate components will be replaced. 51 52 Args: 53 components: Any number values indicating 54 name of the component and the the component. The name is used 55 to replace a element with the tag==name. 56 """ 57 58 for component in components: 59 if isinstance(component, dict): 60 for key, value in component.items(): 61 if isinstance(value, AST): 62 self.components[tag_from_file(key)] = parse_component(value) 63 elif isinstance(value, dict) and valid_component_dict(value): 64 self.components[tag_from_file(key)] = value 65 elif isinstance(component, tuple): 66 if isinstance(component[0], str) and isinstance(component[1], AST): 67 self.components[tag_from_file(component[0])] = parse_component(component[1]) 68 elif isinstance(component[0], str) and valid_component_dict(component[1]): 69 self.components[tag_from_file(component[0])] = component[1] 70 71 return self 72 73 def remove(self, *components: str | All_Nodes): 74 """Takes either component names or components and removes them 75 from the dictionary. 76 77 Args: 78 components (str | All_Nodes): Any str name of components or 79 node value to remove from the components list in the compiler. 80 """ 81 for component in components: 82 if isinstance(component, str): 83 if component in self.components: 84 self.components.pop(component, None) 85 else: 86 raise KeyError(f"Invalid component name {component}") 87 elif isinstance(component, All_Nodes): 88 for key, value in self.components.items(): 89 if isinstance(value, dict) and value["component"] == component: 90 self.components.pop(key, None) 91 break 92 93 return self 94 95 def compile( 96 self, 97 ast: Optional[AST] = None, 98 to_format: str = HTML, 99 indent: Optional[int] = None, 100 handler: Optional[Callable] = None, 101 scopes: Optional[list[str]] = None, 102 **kwargs: Any, 103 ) -> str: 104 """Execute compilation to a different format.""" 105 106 ast = ast or self.ast 107 108 if ast is None: 109 raise Exception("Must provide an ast to compile.") 110 111 doctypes = [dt for dt in visit_children(ast.tree) if check(dt, "doctype")] 112 if len(doctypes) == 0: 113 ast.tree.children.insert(0, DocType(parent=ast.tree)) 114 115 scopes = scopes or ["./"] 116 if scopes is not None: 117 from sys import path # pylint: disable=import-outside-toplevel 118 119 for scope in scopes: 120 path.insert(0, scope) 121 122 if to_format == PHML: 123 return phml(ast, indent or 4) 124 125 if to_format == HTML: 126 return html(ast, self.components, indent or 4, **kwargs) 127 128 if to_format == JSON: 129 return json(ast, indent or 2) 130 131 if handler is None: 132 raise Exception(f"Unkown format < { to_format } >") 133 134 return handler(ast, indent)
Used to compile phml into other formats. HTML, PHML, JSON, Markdown, etc...
34 def add( 35 self, 36 *components: dict[str, dict[str, list | All_Nodes] | AST] 37 | tuple[str, dict[str, list | All_Nodes] | AST], 38 ): 39 """Add a component to the compilers component list. 40 41 Components passed in can be of a few types. It can also be a dictionary of str 42 being the name of the element to be replaced. The name can be snake case, camel 43 case, or pascal cased. The value can either be the parsed result of the component 44 from phml.utils.parse_component() or the parsed ast of the component. Lastely, 45 the component can be a tuple. The first value is the name of the element to be 46 replaced; with the second value being either the parsed result of the component 47 or the component's ast. 48 49 Note: 50 Any duplicate components will be replaced. 51 52 Args: 53 components: Any number values indicating 54 name of the component and the the component. The name is used 55 to replace a element with the tag==name. 56 """ 57 58 for component in components: 59 if isinstance(component, dict): 60 for key, value in component.items(): 61 if isinstance(value, AST): 62 self.components[tag_from_file(key)] = parse_component(value) 63 elif isinstance(value, dict) and valid_component_dict(value): 64 self.components[tag_from_file(key)] = value 65 elif isinstance(component, tuple): 66 if isinstance(component[0], str) and isinstance(component[1], AST): 67 self.components[tag_from_file(component[0])] = parse_component(component[1]) 68 elif isinstance(component[0], str) and valid_component_dict(component[1]): 69 self.components[tag_from_file(component[0])] = component[1] 70 71 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.utils.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.
73 def remove(self, *components: str | All_Nodes): 74 """Takes either component names or components and removes them 75 from the dictionary. 76 77 Args: 78 components (str | All_Nodes): Any str name of components or 79 node value to remove from the components list in the compiler. 80 """ 81 for component in components: 82 if isinstance(component, str): 83 if component in self.components: 84 self.components.pop(component, None) 85 else: 86 raise KeyError(f"Invalid component name {component}") 87 elif isinstance(component, All_Nodes): 88 for key, value in self.components.items(): 89 if isinstance(value, dict) and value["component"] == component: 90 self.components.pop(key, None) 91 break 92 93 return self
Takes either component names or components and removes them from the dictionary.
Arguments:
- components (str | All_Nodes): Any str name of components or
- node value to remove from the components list in the compiler.
95 def compile( 96 self, 97 ast: Optional[AST] = None, 98 to_format: str = HTML, 99 indent: Optional[int] = None, 100 handler: Optional[Callable] = None, 101 scopes: Optional[list[str]] = None, 102 **kwargs: Any, 103 ) -> str: 104 """Execute compilation to a different format.""" 105 106 ast = ast or self.ast 107 108 if ast is None: 109 raise Exception("Must provide an ast to compile.") 110 111 doctypes = [dt for dt in visit_children(ast.tree) if check(dt, "doctype")] 112 if len(doctypes) == 0: 113 ast.tree.children.insert(0, DocType(parent=ast.tree)) 114 115 scopes = scopes or ["./"] 116 if scopes is not None: 117 from sys import path # pylint: disable=import-outside-toplevel 118 119 for scope in scopes: 120 path.insert(0, scope) 121 122 if to_format == PHML: 123 return phml(ast, indent or 4) 124 125 if to_format == HTML: 126 return html(ast, self.components, indent or 4, **kwargs) 127 128 if to_format == JSON: 129 return json(ast, indent or 2) 130 131 if handler is None: 132 raise Exception(f"Unkown format < { to_format } >") 133 134 return handler(ast, indent)
Execute compilation to a different format.
22class Parser: 23 """Primary logic to handle everything with a phml file. 24 25 This class can parse files as phml files and create an ast. 26 The ast and the nodes themselfs can translate themselves to; 27 html, phml, and json. The ast can recursively return itself as 28 an html string. However, only this class can process the python 29 blocks inside of the phml file. 30 31 Call Parser.convert() and pass any kwargs you wish to be exposed to 32 the process that processes the python. You may also use Parser.util to 33 pass extensions to convert and manipulate the html along with the python 34 processing. 35 """ 36 37 parser: HypertextMarkupParser 38 """The custom builtin `html.parser` class that builds phml ast.""" 39 40 ast: AST 41 """The recursive node tree of the phml ast.""" 42 43 def __init__(self): 44 self.phml_parser = HypertextMarkupParser() 45 self.ast = None 46 47 def load(self, path: str | Path, handler: Optional[Callable] = None): 48 """Parse a given phml file to AST following hast and unist. 49 50 When finished the PHML.ast variable will be populated with the 51 resulting ast. 52 53 Args: 54 path (str | Path): The path to the file that should be parsed. 55 handler (Callable | None): A function to call instead of the built in 56 parser to parse to a phml.AST. Must take a string and return a phml.AST. 57 """ 58 59 self.phml_parser.cur_tags = [] 60 61 with open(path, "r", encoding="utf-8") as source: 62 src = source.read() 63 64 if handler is None: 65 path = Path(path) 66 67 if path.suffix == ".json": 68 self.ast = AST(json_to_ast(loads(src))) 69 else: 70 self.phml_parser.reset() 71 self.phml_parser.cur = Root() 72 73 try: 74 self.phml_parser.feed(src) 75 if len(self.phml_parser.cur_tags) > 0: 76 last = self.phml_parser.cur_tags[-1].position 77 raise Exception( 78 f"Unbalanced tags in source file '{path.as_posix()}' at \ 79[{last.start.line}:{last.start.column}]" 80 ) 81 self.ast = AST(self.phml_parser.cur) 82 except Exception as exception: 83 self.ast = None 84 raise Exception(f"'{path.as_posix()}': {exception}") from exception 85 else: 86 self.ast = handler(src) 87 88 return self 89 90 def parse(self, data: str | dict, handler: Optional[Callable] = None): 91 """Parse data from a phml/html string or from a dict to a phml ast. 92 93 Args: 94 data (str | dict): Data to parse in to a ast 95 data_type (str): Can be `HTML`, `PHML`, `MARKDOWN`, or `JSON` which 96 tells parser how to parse the data. Otherwise it will assume 97 str data to be html/phml and dict as `json`. 98 handler (Callable | None): A function to call instead of the built in 99 parser to parse to a phml.AST. Must take a string and return a phml.AST. 100 """ 101 102 self.phml_parser.cur_tags = [] 103 104 if handler is None: 105 if isinstance(data, dict): 106 self.ast = AST(json_to_ast(data)) 107 elif isinstance(data, str): 108 self.phml_parser.reset() 109 self.phml_parser.cur = Root() 110 111 try: 112 self.phml_parser.feed(data) 113 if len(self.phml_parser.cur_tags) > 0: 114 last = self.phml_parser.cur_tags[-1].position 115 raise Exception( 116 f"Unbalanced tags in source at [{last.start.line}:{last.start.column}]" 117 ) 118 self.ast = AST(self.phml_parser.cur) 119 except Exception as exception: 120 self.ast = None 121 raise Exception( 122 f"{data[:6] + '...' if len(data) > 6 else data}\ 123: {exception}" 124 ) from exception 125 else: 126 self.ast = handler(data) 127 128 return self
Primary logic to handle everything with a phml file.
This class can parse files as phml files and create an ast. The ast and the nodes themselfs can translate themselves to; html, phml, and json. The ast can recursively return itself as an html string. However, only this class can process the python blocks inside of the phml file.
Call Parser.convert() and pass any kwargs you wish to be exposed to the process that processes the python. You may also use Parser.util to pass extensions to convert and manipulate the html along with the python processing.
The custom builtin html.parser
class that builds phml ast.
47 def load(self, path: str | Path, handler: Optional[Callable] = None): 48 """Parse a given phml file to AST following hast and unist. 49 50 When finished the PHML.ast variable will be populated with the 51 resulting ast. 52 53 Args: 54 path (str | Path): The path to the file that should be parsed. 55 handler (Callable | None): A function to call instead of the built in 56 parser to parse to a phml.AST. Must take a string and return a phml.AST. 57 """ 58 59 self.phml_parser.cur_tags = [] 60 61 with open(path, "r", encoding="utf-8") as source: 62 src = source.read() 63 64 if handler is None: 65 path = Path(path) 66 67 if path.suffix == ".json": 68 self.ast = AST(json_to_ast(loads(src))) 69 else: 70 self.phml_parser.reset() 71 self.phml_parser.cur = Root() 72 73 try: 74 self.phml_parser.feed(src) 75 if len(self.phml_parser.cur_tags) > 0: 76 last = self.phml_parser.cur_tags[-1].position 77 raise Exception( 78 f"Unbalanced tags in source file '{path.as_posix()}' at \ 79[{last.start.line}:{last.start.column}]" 80 ) 81 self.ast = AST(self.phml_parser.cur) 82 except Exception as exception: 83 self.ast = None 84 raise Exception(f"'{path.as_posix()}': {exception}") from exception 85 else: 86 self.ast = handler(src) 87 88 return self
Parse a given phml file to AST following hast and unist.
When finished the PHML.ast variable will be populated with the resulting ast.
Arguments:
- path (str | Path): The path to the file that should be parsed.
- handler (Callable | None): A function to call instead of the built in
- parser to parse to a phml.AST. Must take a string and return a phml.AST.
90 def parse(self, data: str | dict, handler: Optional[Callable] = None): 91 """Parse data from a phml/html string or from a dict to a phml ast. 92 93 Args: 94 data (str | dict): Data to parse in to a ast 95 data_type (str): Can be `HTML`, `PHML`, `MARKDOWN`, or `JSON` which 96 tells parser how to parse the data. Otherwise it will assume 97 str data to be html/phml and dict as `json`. 98 handler (Callable | None): A function to call instead of the built in 99 parser to parse to a phml.AST. Must take a string and return a phml.AST. 100 """ 101 102 self.phml_parser.cur_tags = [] 103 104 if handler is None: 105 if isinstance(data, dict): 106 self.ast = AST(json_to_ast(data)) 107 elif isinstance(data, str): 108 self.phml_parser.reset() 109 self.phml_parser.cur = Root() 110 111 try: 112 self.phml_parser.feed(data) 113 if len(self.phml_parser.cur_tags) > 0: 114 last = self.phml_parser.cur_tags[-1].position 115 raise Exception( 116 f"Unbalanced tags in source at [{last.start.line}:{last.start.column}]" 117 ) 118 self.ast = AST(self.phml_parser.cur) 119 except Exception as exception: 120 self.ast = None 121 raise Exception( 122 f"{data[:6] + '...' if len(data) > 6 else data}\ 123: {exception}" 124 ) from exception 125 else: 126 self.ast = handler(data) 127 128 return self
Parse data from a phml/html string or from a dict to a phml ast.
Arguments:
- data (str | dict): Data to parse in to a ast
- data_type (str): Can be
HTML
,PHML
,MARKDOWN
, orJSON
which - tells parser how to parse the data. Otherwise it will assume
- str data to be html/phml and dict as
json
. - handler (Callable | None): A function to call instead of the built in
- parser to parse to a phml.AST. Must take a string and return a phml.AST.