Coverage for phml\core\compile\convert.py: 100%

67 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-08 11:07 -0600

1"""Helper methods for converting an ast to phml, html, and json.""" 

2 

3from copy import deepcopy 

4from json import dumps 

5from typing import Optional 

6 

7from phml.nodes import AST, All_Nodes, Element, Position, Root 

8from phml.utils import find_all, remove_nodes, visit_children 

9from phml.virtual_python import VirtualPython 

10 

11from .util import apply_conditions, apply_python, replace_components 

12 

13 

14def phml(ast: AST, indent: int = 0) -> str: 

15 """Compile a given phml ast to a phml string with a certain indent amount.""" 

16 return __to_html(ast, indent) 

17 

18 

19def html( 

20 ast: AST, 

21 components: Optional[dict[str, dict[str, list | All_Nodes]]] = None, 

22 indent: int = 0, 

23 **kwargs, 

24) -> str: 

25 """Compile a given phml ast to a html string with a certain indent amount. 

26 

27 Can provide components that replace certain elements in the ast tree along with additional 

28 kwargs that are exposed to executed python blocks. 

29 

30 Args: 

31 ast (AST): The phml ast to compile 

32 components (dict[str, dict[str, list | All_Nodes]] | None): key value pairs of element name 

33 and the replacement mapping. The replacement mapping holds reference to a components python, 

34 script, and style elements along with the root replacement node. 

35 indent (int): The offset amount to every indent 

36 **kwargs (Any): Additional information that will be exposed to executed python blocks. 

37 """ 

38 components = components or {} 

39 src = deepcopy(ast) 

40 

41 # 1. Search for all python elements and get source info. 

42 # - Remove when done 

43 virtual_python = VirtualPython() 

44 

45 for python_block in find_all(src, {"tag": "python"}): 

46 if len(python_block.children) == 1: 

47 if python_block.children[0].type == "text": 

48 virtual_python += VirtualPython(python_block.children[0].value) 

49 

50 remove_nodes(src, ["element", {"tag": "python"}]) 

51 

52 # 2. Replace specific element node with given replacement components 

53 replace_components(src, components, virtual_python, **kwargs) 

54 

55 for python_block in find_all(src, {"tag": "python"}): 

56 if len(python_block.children) == 1: 

57 if python_block.children[0].type == "text": 

58 virtual_python += VirtualPython(python_block.children[0].value) 

59 

60 remove_nodes(src, ["element", {"tag": "python"}]) 

61 

62 # 3. Search each element and find py-if, py-elif, py-else, and py-for 

63 # - Execute those statements 

64 

65 apply_conditions(src, virtual_python, **kwargs) 

66 

67 # 4. Search for python blocks and process them. 

68 

69 apply_python(src, virtual_python, **kwargs) 

70 

71 return __to_html(src, indent) 

72 

73 

74def json(ast: AST, indent: int = 0) -> str: 

75 """Compile a given phml ast to a json string with a certain indent amount.""" 

76 

77 def compile_children(node: Root | Element) -> dict: 

78 data = {"type": node.type} 

79 

80 if node.type == "root": 

81 if node.parent is not None: 

82 raise Exception("Root nodes must only occur as the root of an ast/tree.") 

83 

84 for attr in vars(node): 

85 if attr not in ["parent", "children"]: 

86 value = getattr(node, attr) 

87 if isinstance(value, Position): 

88 data[attr] = value.as_dict() 

89 else: 

90 data[attr] = value 

91 

92 if hasattr(node, "children"): 

93 data["children"] = [] 

94 for child in visit_children(node): 

95 data["children"].append(compile_children(child)) 

96 

97 return data 

98 

99 data = compile_children(ast.tree) 

100 return dumps(data, indent=indent) 

101 

102 

103def __to_html(ast: AST, offset: int = 0) -> str: 

104 def compile_children(node: All_Nodes, indent: int = 0) -> list[str]: 

105 data = [] 

106 if node.type == "element": 

107 if node.startend: 

108 data.append(" " * indent + node.start_tag()) 

109 else: 

110 if ( 

111 len(node.children) == 1 

112 and node.children[0].type == "text" 

113 and node.children[0].num_lines == 1 

114 ): 

115 data.append( 

116 "".join( 

117 [ 

118 " " * indent + node.start_tag(), 

119 node.children[0].stringify( 

120 indent + offset if node.children[0].num_lines > 1 else 0 

121 ), 

122 node.end_tag(), 

123 ] 

124 ) 

125 ) 

126 else: 

127 data.append(" " * indent + node.start_tag()) 

128 for child in visit_children(node): 

129 if child.type == "element": 

130 data.extend(compile_children(child, indent + offset)) 

131 else: 

132 data.append(child.stringify(indent + offset)) 

133 data.append(" " * indent + node.end_tag()) 

134 elif node.type == "root": 

135 for child in visit_children(node): 

136 data.extend(compile_children(child)) 

137 else: 

138 data.append(node.stringify(indent + offset)) 

139 return data 

140 

141 data = compile_children(ast.tree) 

142 

143 return "\n".join(data)