Coverage for phml\core\compile\__init__.py: 38%
55 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-30 09:38 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-30 09:38 -0600
1from typing import Any, Callable, Optional
3from phml.core.file_types import HTML, JSON, PHML
4from phml.nodes import AST, All_Nodes, DocType
5from phml.utils import parse_component, tag_from_file, test, visit_children
7from .convert import html, json, phml
9__all__ = ["Compiler"]
12class Compiler:
13 """Used to compile phml into other formats. HTML, PHML,
14 JSON, Markdown, etc...
15 """
17 ast: AST
18 """phml ast used by the compiler to generate a new format."""
20 def __init__(
21 self,
22 ast: Optional[AST] = None,
23 components: Optional[dict[str, dict[str, list | All_Nodes]]] = None,
24 ):
25 self.ast = ast
26 self.components = components or {}
28 def add(
29 self,
30 *components: dict[str, dict[str, list | All_Nodes] | AST]
31 | tuple[str, dict[str, list | All_Nodes] | AST],
32 ):
33 """Add a component to the compilers component list.
35 Components passed in can be of a few types. It can also be a dictionary of str
36 being the name of the element to be replaced. The name can be snake case, camel
37 case, or pascal cased. The value can either be the parsed result of the component
38 from phml.utils.parse_component() or the parsed ast of the component. Lastely,
39 the component can be a tuple. The first value is the name of the element to be
40 replaced; with the second value being either the parsed result of the component
41 or the component's ast.
43 Note:
44 Any duplicate components will be replaced.
46 Args:
47 components: Any number values indicating
48 name of the component and the the component. The name is used
49 to replace a element with the tag==name.
50 """
52 for component in components:
53 if isinstance(component, dict):
54 for key, value in component.items():
55 if isinstance(value, AST):
56 self.components[tag_from_file(key)] = parse_component(value)
57 else:
58 self.components[tag_from_file(key)] = value
59 elif isinstance(component, tuple):
60 if isinstance(component[1], AST):
61 self.components[tag_from_file(component[0])] = parse_component(component[1])
62 else:
63 self.components[tag_from_file(component[0])] = component[1]
65 return self
67 def remove(self, *components: str | All_Nodes):
68 """Takes either component names or components and removes them
69 from the dictionary.
71 Args:
72 components (str | All_Nodes): Any str name of components or
73 node value to remove from the components list in the compiler.
74 """
75 for component in components:
76 if isinstance(component, str):
77 if component in self.components:
78 self.components.pop(component, None)
79 else:
80 raise KeyError(f"Invalid component name {component}")
81 elif isinstance(component, All_Nodes):
82 for key, value in self.components:
83 if isinstance(value, dict) and value["component"] == component:
84 self.components.pop(key, None)
85 break
86 elif value == components:
87 self.components.pop(key, None)
88 break
90 return self
92 def compile(
93 self,
94 ast: Optional[AST] = None,
95 to_format: str = HTML,
96 indent: Optional[int] = None,
97 handler: Optional[Callable] = None,
98 **kwargs: Any,
99 ) -> str:
100 """Execute compilation to a different format."""
102 ast = ast or self.ast
104 if ast is None:
105 raise Exception("Must provide an ast to compile.")
107 doctypes = [dt for dt in visit_children(ast.tree) if test(dt, "doctype")]
108 if len(doctypes) == 0:
109 ast.tree.children.insert(0, DocType(parent=ast.tree))
111 if to_format == PHML:
112 return phml(ast, indent or 4)
113 elif to_format == HTML:
114 return html(ast, self.components, indent or 4, **kwargs)
115 elif to_format == JSON:
116 return json(ast, indent or 2)
117 elif handler is None:
118 raise Exception(f"Unkown format < { to_format } >")
119 else:
120 return handler(ast, indent)