Coverage for phml\core\parser\__init__.py: 75%

53 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-30 09:38 -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 .hypertextMarkupParser 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 with open(path, "r", encoding="utf-8") as source: 

59 src = source.read() 

60 

61 if handler is None: 

62 path = Path(path) 

63 

64 if path.suffix == ".json": 

65 self.ast = AST(json_to_ast(loads(src))) 

66 else: 

67 self.phml_parser.reset() 

68 self.phml_parser.cur = Root() 

69 

70 try: 

71 self.phml_parser.feed(src) 

72 if len(self.phml_parser.cur_tags) > 0: 

73 last = self.phml_parser.cur_tags[-1].position 

74 raise Exception( 

75 f"Unbalanced tags in source file '{path}' at [{last.start.line}:{last.start.column}]" 

76 ) 

77 self.ast = AST(self.phml_parser.cur) 

78 except Exception as e: 

79 self.ast = None 

80 raise Exception(f"'{path}': {e}") 

81 else: 

82 self.ast = handler(src) 

83 

84 return self 

85 

86 def parse(self, data: str | dict, handler: Optional[Callable] = None): 

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

88 

89 Args: 

90 data (str | dict): Data to parse in to a ast 

91 data_type (str): Can be `HTML`, `PHML`, `MARKDOWN`, or `JSON` which 

92 tells parser how to parse the data. Otherwise it will assume 

93 str data to be html/phml and dict as `json`. 

94 handler (Callable | None): A function to call instead of the built in 

95 parser to parse to a phml.AST. Must take a string and return a phml.AST. 

96 """ 

97 if handler is None: 

98 if isinstance(data, dict): 

99 self.ast = AST(json_to_ast(data)) 

100 elif isinstance(data, str): 

101 self.phml_parser.reset() 

102 self.phml_parser.cur = Root() 

103 

104 try: 

105 self.phml_parser.feed(data) 

106 if len(self.phml_parser.cur_tags) > 0: 

107 last = self.phml_parser.cur_tags[-1].position 

108 raise Exception( 

109 f"Unbalanced tags in source at [{last.start.line}:{last.start.column}]" 

110 ) 

111 self.ast = AST(self.phml_parser.cur) 

112 except Exception as e: 

113 self.ast = None 

114 raise Exception(f"{data[:6] + '...' if len(data) > 6 else data}: {e}") 

115 else: 

116 self.ast = handler(data) 

117 

118 return self