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

59 statements  

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

1"""phml.core.compile 

2 

3The heavy lifting module that compiles phml ast's to different string/file formats. 

4""" 

5 

6from typing import Any, Callable, Optional 

7 

8from phml.core.file_types import HTML, JSON, PHML 

9from phml.nodes import AST, All_Nodes, DocType 

10from phml.utils import check, parse_component, tag_from_file, valid_component_dict, visit_children 

11 

12from .convert import html, json, phml 

13 

14__all__ = ["Compiler"] 

15 

16 

17class Compiler: 

18 """Used to compile phml into other formats. HTML, PHML, 

19 JSON, Markdown, etc... 

20 """ 

21 

22 ast: AST 

23 """phml ast used by the compiler to generate a new format.""" 

24 

25 def __init__( 

26 self, 

27 ast: Optional[AST] = None, 

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

29 ): 

30 self.ast = ast 

31 self.components = components or {} 

32 

33 def add( 

34 self, 

35 *components: dict[str, dict[str, list | All_Nodes] | AST] 

36 | tuple[str, dict[str, list | All_Nodes] | AST], 

37 ): 

38 """Add a component to the compilers component list. 

39 

40 Components passed in can be of a few types. It can also be a dictionary of str 

41 being the name of the element to be replaced. The name can be snake case, camel 

42 case, or pascal cased. The value can either be the parsed result of the component 

43 from phml.utils.parse_component() or the parsed ast of the component. Lastely, 

44 the component can be a tuple. The first value is the name of the element to be 

45 replaced; with the second value being either the parsed result of the component 

46 or the component's ast. 

47 

48 Note: 

49 Any duplicate components will be replaced. 

50 

51 Args: 

52 components: Any number values indicating 

53 name of the component and the the component. The name is used 

54 to replace a element with the tag==name. 

55 """ 

56 

57 for component in components: 

58 if isinstance(component, dict): 

59 for key, value in component.items(): 

60 if isinstance(value, AST): 

61 self.components[tag_from_file(key)] = parse_component(value) 

62 elif isinstance(value, dict) and valid_component_dict(value): 

63 self.components[tag_from_file(key)] = value 

64 elif isinstance(component, tuple): 

65 if isinstance(component[0], str) and isinstance(component[1], AST): 

66 self.components[tag_from_file(component[0])] = parse_component(component[1]) 

67 elif isinstance(component[0], str) and valid_component_dict(component[1]): 

68 self.components[tag_from_file(component[0])] = component[1] 

69 

70 return self 

71 

72 def remove(self, *components: str | All_Nodes): 

73 """Takes either component names or components and removes them 

74 from the dictionary. 

75 

76 Args: 

77 components (str | All_Nodes): Any str name of components or 

78 node value to remove from the components list in the compiler. 

79 """ 

80 for component in components: 

81 if isinstance(component, str): 

82 if component in self.components: 

83 self.components.pop(component, None) 

84 else: 

85 raise KeyError(f"Invalid component name {component}") 

86 elif isinstance(component, All_Nodes): 

87 for key, value in self.components.items(): 

88 if isinstance(value, dict) and value["component"] == component: 

89 self.components.pop(key, None) 

90 break 

91 

92 return self 

93 

94 def compile( 

95 self, 

96 ast: Optional[AST] = None, 

97 to_format: str = HTML, 

98 indent: Optional[int] = None, 

99 handler: Optional[Callable] = None, 

100 scopes: Optional[list[str]] = None, 

101 **kwargs: Any, 

102 ) -> str: 

103 """Execute compilation to a different format.""" 

104 

105 ast = ast or self.ast 

106 

107 if ast is None: 

108 raise Exception("Must provide an ast to compile.") 

109 

110 doctypes = [dt for dt in visit_children(ast.tree) if check(dt, "doctype")] 

111 if len(doctypes) == 0: 

112 ast.tree.children.insert(0, DocType(parent=ast.tree)) 

113 

114 scopes = scopes or ["./"] 

115 if scopes is not None: 

116 from sys import path # pylint: disable=import-outside-toplevel 

117 

118 for scope in scopes: 

119 path.insert(0, scope) 

120 

121 if to_format == PHML: 

122 return phml(ast, indent or 4) 

123 

124 if to_format == HTML: 

125 return html(ast, self.components, indent or 4, **kwargs) 

126 

127 if to_format == JSON: 

128 return json(ast, indent or 2) 

129 

130 if handler is None: 

131 raise Exception(f"Unkown format < { to_format } >") 

132 

133 return handler(ast, indent)