phml.core

phml.core

All core parsing, compiling, and valid file_types.

 1"""phml.core
 2
 3All core parsing, compiling, and valid file_types.
 4"""
 5
 6from . import file_types
 7from .compile import Compiler
 8from .parser import Parser
 9
10__all__ = ["Compiler", "Parser", "file_types"]
class Compiler:
 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                    else:
 64                        self.components[tag_from_file(key)] = value
 65            elif isinstance(component, tuple):
 66                if isinstance(component[1], AST):
 67                    self.components[tag_from_file(component[0])] = parse_component(component[1])
 68                else:
 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:
 89                    if isinstance(value, dict) and value["component"] == component:
 90                        self.components.pop(key, None)
 91                        break
 92
 93                    if value == components:
 94                        self.components.pop(key, None)
 95                        break
 96
 97        return self
 98
 99    def compile(
100        self,
101        ast: Optional[AST] = None,
102        to_format: str = HTML,
103        indent: Optional[int] = None,
104        handler: Optional[Callable] = None,
105        **kwargs: Any,
106    ) -> str:
107        """Execute compilation to a different format."""
108
109        ast = ast or self.ast
110
111        if ast is None:
112            raise Exception("Must provide an ast to compile.")
113
114        doctypes = [dt for dt in visit_children(ast.tree) if test(dt, "doctype")]
115        if len(doctypes) == 0:
116            ast.tree.children.insert(0, DocType(parent=ast.tree))
117
118        if to_format == PHML:
119            return phml(ast, indent or 4)
120
121        if to_format == HTML:
122            return html(ast, self.components, indent or 4, **kwargs)
123
124        if to_format == JSON:
125            return json(ast, indent or 2)
126
127        if handler is None:
128            raise Exception(f"Unkown format < { to_format } >")
129
130        return handler(ast, indent)

Used to compile phml into other formats. HTML, PHML, JSON, Markdown, etc...

Compiler( ast: Optional[phml.nodes.AST.AST] = None, components: Optional[dict[str, dict[str, list | phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal]]] = None)
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 {}

phml ast used by the compiler to generate a new format.

def add( self, *components: dict[str, dict[str, list | phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal] | phml.nodes.AST.AST] | tuple[str, dict[str, list | phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal] | phml.nodes.AST.AST]):
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                    else:
64                        self.components[tag_from_file(key)] = value
65            elif isinstance(component, tuple):
66                if isinstance(component[1], AST):
67                    self.components[tag_from_file(component[0])] = parse_component(component[1])
68                else:
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.

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.
def remove( self, *components: str | phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal):
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:
89                    if isinstance(value, dict) and value["component"] == component:
90                        self.components.pop(key, None)
91                        break
92
93                    if value == components:
94                        self.components.pop(key, None)
95                        break
96
97        return self

Takes either component names or components and removes them from the dictionary.

Args
  • components (str | All_Nodes): Any str name of components or
  • node value to remove from the components list in the compiler.
def compile( self, ast: Optional[phml.nodes.AST.AST] = None, to_format: str = 'html', indent: Optional[int] = None, handler: Optional[Callable] = None, **kwargs: Any) -> str:
 99    def compile(
100        self,
101        ast: Optional[AST] = None,
102        to_format: str = HTML,
103        indent: Optional[int] = None,
104        handler: Optional[Callable] = None,
105        **kwargs: Any,
106    ) -> str:
107        """Execute compilation to a different format."""
108
109        ast = ast or self.ast
110
111        if ast is None:
112            raise Exception("Must provide an ast to compile.")
113
114        doctypes = [dt for dt in visit_children(ast.tree) if test(dt, "doctype")]
115        if len(doctypes) == 0:
116            ast.tree.children.insert(0, DocType(parent=ast.tree))
117
118        if to_format == PHML:
119            return phml(ast, indent or 4)
120
121        if to_format == HTML:
122            return html(ast, self.components, indent or 4, **kwargs)
123
124        if to_format == JSON:
125            return json(ast, indent or 2)
126
127        if handler is None:
128            raise Exception(f"Unkown format < { to_format } >")
129
130        return handler(ast, indent)

Execute compilation to a different format.

class Parser:
 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        with open(path, "r", encoding="utf-8") as source:
 60            src = source.read()
 61
 62        if handler is None:
 63            path = Path(path)
 64
 65            if path.suffix == ".json":
 66                self.ast = AST(json_to_ast(loads(src)))
 67            else:
 68                self.phml_parser.reset()
 69                self.phml_parser.cur = Root()
 70
 71                try:
 72                    self.phml_parser.feed(src)
 73                    if len(self.phml_parser.cur_tags) > 0:
 74                        last = self.phml_parser.cur_tags[-1].position
 75                        raise Exception(
 76                            f"Unbalanced tags in source file '{path}' at \
 77[{last.start.line}:{last.start.column}]"
 78                        )
 79                    self.ast = AST(self.phml_parser.cur)
 80                except Exception as exception:
 81                    self.ast = None
 82                    raise Exception(f"'{path}': {exception}") from exception
 83        else:
 84            self.ast = handler(src)
 85
 86        return self
 87
 88    def parse(self, data: str | dict, handler: Optional[Callable] = None):
 89        """Parse data from a phml/html string or from a dict to a phml ast.
 90
 91        Args:
 92            data (str | dict): Data to parse in to a ast
 93            data_type (str): Can be `HTML`, `PHML`, `MARKDOWN`, or `JSON` which
 94            tells parser how to parse the data. Otherwise it will assume
 95            str data to be html/phml and dict as `json`.
 96            handler (Callable | None): A function to call instead of the built in
 97            parser to parse to a phml.AST. Must take a string and return a phml.AST.
 98        """
 99        if handler is None:
100            if isinstance(data, dict):
101                self.ast = AST(json_to_ast(data))
102            elif isinstance(data, str):
103                self.phml_parser.reset()
104                self.phml_parser.cur = Root()
105
106                try:
107                    self.phml_parser.feed(data)
108                    if len(self.phml_parser.cur_tags) > 0:
109                        last = self.phml_parser.cur_tags[-1].position
110                        raise Exception(
111                            f"Unbalanced tags in source at [{last.start.line}:{last.start.column}]"
112                        )
113                    self.ast = AST(self.phml_parser.cur)
114                except Exception as exception:
115                    self.ast = None
116                    raise Exception(
117                        f"{data[:6] + '...' if len(data) > 6 else data}\
118: {exception}"
119                    ) from exception
120        else:
121            self.ast = handler(data)
122
123        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.

Parser()
43    def __init__(self):
44        self.phml_parser = HypertextMarkupParser()
45        self.ast = None
parser: phml.core.parser.hypertext_markup_parser.HypertextMarkupParser

The custom builtin html.parser class that builds phml ast.

The recursive node tree of the phml ast.

def load(self, path: str | pathlib.Path, handler: Optional[Callable] = None):
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        with open(path, "r", encoding="utf-8") as source:
60            src = source.read()
61
62        if handler is None:
63            path = Path(path)
64
65            if path.suffix == ".json":
66                self.ast = AST(json_to_ast(loads(src)))
67            else:
68                self.phml_parser.reset()
69                self.phml_parser.cur = Root()
70
71                try:
72                    self.phml_parser.feed(src)
73                    if len(self.phml_parser.cur_tags) > 0:
74                        last = self.phml_parser.cur_tags[-1].position
75                        raise Exception(
76                            f"Unbalanced tags in source file '{path}' at \
77[{last.start.line}:{last.start.column}]"
78                        )
79                    self.ast = AST(self.phml_parser.cur)
80                except Exception as exception:
81                    self.ast = None
82                    raise Exception(f"'{path}': {exception}") from exception
83        else:
84            self.ast = handler(src)
85
86        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.

Args
  • 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.
def parse(self, data: str | dict, handler: Optional[Callable] = None):
 88    def parse(self, data: str | dict, handler: Optional[Callable] = None):
 89        """Parse data from a phml/html string or from a dict to a phml ast.
 90
 91        Args:
 92            data (str | dict): Data to parse in to a ast
 93            data_type (str): Can be `HTML`, `PHML`, `MARKDOWN`, or `JSON` which
 94            tells parser how to parse the data. Otherwise it will assume
 95            str data to be html/phml and dict as `json`.
 96            handler (Callable | None): A function to call instead of the built in
 97            parser to parse to a phml.AST. Must take a string and return a phml.AST.
 98        """
 99        if handler is None:
100            if isinstance(data, dict):
101                self.ast = AST(json_to_ast(data))
102            elif isinstance(data, str):
103                self.phml_parser.reset()
104                self.phml_parser.cur = Root()
105
106                try:
107                    self.phml_parser.feed(data)
108                    if len(self.phml_parser.cur_tags) > 0:
109                        last = self.phml_parser.cur_tags[-1].position
110                        raise Exception(
111                            f"Unbalanced tags in source at [{last.start.line}:{last.start.column}]"
112                        )
113                    self.ast = AST(self.phml_parser.cur)
114                except Exception as exception:
115                    self.ast = None
116                    raise Exception(
117                        f"{data[:6] + '...' if len(data) > 6 else data}\
118: {exception}"
119                    ) from exception
120        else:
121            self.ast = handler(data)
122
123        return self

Parse data from a phml/html string or from a dict to a phml ast.

Args
  • data (str | dict): Data to parse in to a ast
  • data_type (str): Can be HTML, PHML, MARKDOWN, or JSON 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.