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

55 statements  

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

1from typing import Any, Callable, Optional 

2 

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

4from phml.nodes import AST, All_Nodes, DocType 

5from phml.utils import parse_component, tag_from_file, test, visit_children 

6 

7from .convert import html, json, phml 

8 

9__all__ = ["Compiler"] 

10 

11 

12class Compiler: 

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

14 JSON, Markdown, etc... 

15 """ 

16 

17 ast: AST 

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

19 

20 def __init__( 

21 self, 

22 ast: Optional[AST] = None, 

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

24 ): 

25 self.ast = ast 

26 self.components = components or {} 

27 

28 def add( 

29 self, 

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

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

32 ): 

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

34 

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

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

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

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

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

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

41 or the component's ast. 

42 

43 Note: 

44 Any duplicate components will be replaced. 

45 

46 Args: 

47 components: Any number values indicating 

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

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

50 """ 

51 

52 for component in components: 

53 if isinstance(component, dict): 

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

55 if isinstance(value, AST): 

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

57 else: 

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

59 elif isinstance(component, tuple): 

60 if isinstance(component[1], AST): 

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

62 else: 

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

64 

65 return self 

66 

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

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

69 from the dictionary. 

70 

71 Args: 

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

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

74 """ 

75 for component in components: 

76 if isinstance(component, str): 

77 if component in self.components: 

78 self.components.pop(component, None) 

79 else: 

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

81 elif isinstance(component, All_Nodes): 

82 for key, value in self.components: 

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

84 self.components.pop(key, None) 

85 break 

86 elif value == components: 

87 self.components.pop(key, None) 

88 break 

89 

90 return self 

91 

92 def compile( 

93 self, 

94 ast: Optional[AST] = None, 

95 to_format: str = HTML, 

96 indent: Optional[int] = None, 

97 handler: Optional[Callable] = None, 

98 **kwargs: Any, 

99 ) -> str: 

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

101 

102 ast = ast or self.ast 

103 

104 if ast is None: 

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

106 

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

108 if len(doctypes) == 0: 

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

110 

111 if to_format == PHML: 

112 return phml(ast, indent or 4) 

113 elif to_format == HTML: 

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

115 elif to_format == JSON: 

116 return json(ast, indent or 2) 

117 elif handler is None: 

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

119 else: 

120 return handler(ast, indent)