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

69 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-30 09:38 -0600

1from copy import deepcopy 

2from typing import Optional 

3 

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

5from phml.utils import find_all, remove_nodes, visit_children 

6from phml.virtual_python import VirtualPython 

7 

8from .util import apply_conditions, apply_python, replace_components 

9 

10 

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

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

13 return __to_html(ast, indent) 

14 

15 

16def html( 

17 ast: AST, 

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

19 indent: int = 0, 

20 **kwargs, 

21) -> str: 

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

23 

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

25 kwargs that are exposed to executed python blocks. 

26 

27 Args: 

28 ast (AST): The phml ast to compile 

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

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

31 and style elements along with the root replacement node. 

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

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

34 """ 

35 components = components or {} 

36 src = deepcopy(ast) 

37 

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

39 # - Remove when done 

40 vp = VirtualPython() 

41 

42 for pb in find_all(src, {"tag": "python"}): 

43 if len(pb.children) == 1: 

44 if pb.children[0].type == "text": 

45 vp += VirtualPython(pb.children[0].value) 

46 

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

48 

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

50 replace_components(src, components, vp, **kwargs) 

51 

52 for pb in find_all(src, {"tag": "python"}): 

53 if len(pb.children) == 1: 

54 if pb.children[0].type == "text": 

55 vp += VirtualPython(pb.children[0].value) 

56 

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

58 

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

60 # - Execute those statements 

61 

62 apply_conditions(src, vp, **kwargs) 

63 

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

65 

66 apply_python(src, vp, **kwargs) 

67 

68 return __to_html(src, indent) 

69 

70 

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

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

73 from json import dumps 

74 

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

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

77 

78 if data["type"] == "root": 

79 if node.parent is not None: 

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

81 

82 for attr in vars(node): 

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

84 value = getattr(node, attr) 

85 if isinstance(value, Position): 

86 data[attr] = value.as_dict() 

87 else: 

88 data[attr] = value 

89 

90 if hasattr(node, "children"): 

91 data["children"] = [] 

92 for child in visit_children(node): 

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

94 

95 return data 

96 

97 data = compile_children(ast.tree) 

98 return dumps(data, indent=indent) 

99 

100 

101def markdown(ast: AST) -> str: 

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

103 raise NotImplementedError("Markdown is not supported.") 

104 

105 

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

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

108 data = [] 

109 if node.type == "element": 

110 if node.startend: 

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

112 else: 

113 if ( 

114 len(node.children) == 1 

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

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

117 ): 

118 data.append( 

119 "".join( 

120 [ 

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

122 node.children[0].stringify( 

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

124 ), 

125 node.end_tag(), 

126 ] 

127 ) 

128 ) 

129 else: 

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

131 for c in visit_children(node): 

132 if c.type == "element": 

133 data.extend(compile_children(c, indent + offset)) 

134 else: 

135 data.append(c.stringify(indent + offset)) 

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

137 elif node.type == "root": 

138 for child in visit_children(node): 

139 data.extend(compile_children(child)) 

140 else: 

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

142 return data 

143 

144 data = compile_children(ast.tree) 

145 

146 return "\n".join(data)