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

1"""phml.core.parser 

2 

3The core parsing module for phml. Handles parsing html and phmls strings 

4along with json. 

5 

6Exposes phml.core.parser.Parser which handles all parsing functionality. 

7""" 

8 

9from json import loads 

10from pathlib import Path 

11from typing import Callable, Optional 

12 

13from phml.nodes import AST, Root 

14 

15from .hypertext_markup_parser import HypertextMarkupParser 

16from .json import json_to_ast 

17 

18__all__ = ["Parser"] 

19 

20 

21class Parser: 

22 """Primary logic to handle everything with a phml file. 

23 

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. 

29 

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 """ 

35 

36 parser: HypertextMarkupParser 

37 """The custom builtin `html.parser` class that builds phml ast.""" 

38 

39 ast: AST 

40 """The recursive node tree of the phml ast.""" 

41 

42 def __init__(self): 

43 self.phml_parser = HypertextMarkupParser() 

44 self.ast = None 

45 

46 def load(self, path: str | Path, handler: Optional[Callable] = None): 

47 """Parse a given phml file to AST following hast and unist. 

48 

49 When finished the PHML.ast variable will be populated with the 

50 resulting ast. 

51 

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 """ 

57 

58 self.phml_parser.cur_tags = [] 

59 

60 with open(path, "r", encoding="utf-8") as source: 

61 src = source.read() 

62 

63 if handler is None: 

64 path = Path(path) 

65 

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() 

71 

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) 

86 

87 return self 

88 

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. 

91 

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 """ 

100 

101 self.phml_parser.cur_tags = [] 

102 

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() 

109 

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) 

126 

127 return self