Coverage for phml\core\parser\__init__.py: 100%
55 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:07 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:07 -0600
1"""phml.core.parser
3The core parsing module for phml. Handles parsing html and phmls strings
4along with json.
6Exposes phml.core.parser.Parser which handles all parsing functionality.
7"""
9from json import loads
10from pathlib import Path
11from typing import Callable, Optional
13from phml.nodes import AST, Root
15from .hypertext_markup_parser import HypertextMarkupParser
16from .json import json_to_ast
18__all__ = ["Parser"]
21class Parser:
22 """Primary logic to handle everything with a phml file.
24 This class can parse files as phml files and create an ast.
25 The ast and the nodes themselfs can translate themselves to;
26 html, phml, and json. The ast can recursively return itself as
27 an html string. However, only this class can process the python
28 blocks inside of the phml file.
30 Call Parser.convert() and pass any kwargs you wish to be exposed to
31 the process that processes the python. You may also use Parser.util to
32 pass extensions to convert and manipulate the html along with the python
33 processing.
34 """
36 parser: HypertextMarkupParser
37 """The custom builtin `html.parser` class that builds phml ast."""
39 ast: AST
40 """The recursive node tree of the phml ast."""
42 def __init__(self):
43 self.phml_parser = HypertextMarkupParser()
44 self.ast = None
46 def load(self, path: str | Path, handler: Optional[Callable] = None):
47 """Parse a given phml file to AST following hast and unist.
49 When finished the PHML.ast variable will be populated with the
50 resulting ast.
52 Args:
53 path (str | Path): The path to the file that should be parsed.
54 handler (Callable | None): A function to call instead of the built in
55 parser to parse to a phml.AST. Must take a string and return a phml.AST.
56 """
58 self.phml_parser.cur_tags = []
60 with open(path, "r", encoding="utf-8") as source:
61 src = source.read()
63 if handler is None:
64 path = Path(path)
66 if path.suffix == ".json":
67 self.ast = AST(json_to_ast(loads(src)))
68 else:
69 self.phml_parser.reset()
70 self.phml_parser.cur = Root()
72 try:
73 self.phml_parser.feed(src)
74 if len(self.phml_parser.cur_tags) > 0:
75 last = self.phml_parser.cur_tags[-1].position
76 raise Exception(
77 f"Unbalanced tags in source file '{path.as_posix()}' at \
78[{last.start.line}:{last.start.column}]"
79 )
80 self.ast = AST(self.phml_parser.cur)
81 except Exception as exception:
82 self.ast = None
83 raise Exception(f"'{path.as_posix()}': {exception}") from exception
84 else:
85 self.ast = handler(src)
87 return self
89 def parse(self, data: str | dict, handler: Optional[Callable] = None):
90 """Parse data from a phml/html string or from a dict to a phml ast.
92 Args:
93 data (str | dict): Data to parse in to a ast
94 data_type (str): Can be `HTML`, `PHML`, `MARKDOWN`, or `JSON` which
95 tells parser how to parse the data. Otherwise it will assume
96 str data to be html/phml and dict as `json`.
97 handler (Callable | None): A function to call instead of the built in
98 parser to parse to a phml.AST. Must take a string and return a phml.AST.
99 """
101 self.phml_parser.cur_tags = []
103 if handler is None:
104 if isinstance(data, dict):
105 self.ast = AST(json_to_ast(data))
106 elif isinstance(data, str):
107 self.phml_parser.reset()
108 self.phml_parser.cur = Root()
110 try:
111 self.phml_parser.feed(data)
112 if len(self.phml_parser.cur_tags) > 0:
113 last = self.phml_parser.cur_tags[-1].position
114 raise Exception(
115 f"Unbalanced tags in source at [{last.start.line}:{last.start.column}]"
116 )
117 self.ast = AST(self.phml_parser.cur)
118 except Exception as exception:
119 self.ast = None
120 raise Exception(
121 f"{data[:6] + '...' if len(data) > 6 else data}\
122: {exception}"
123 ) from exception
124 else:
125 self.ast = handler(data)
127 return self