Coverage for phml\utils\misc\inspect.py: 49%
74 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-30 09:38 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-30 09:38 -0600
1"""phml.utils.misc.inspect
3Logic to inspect any phml node. Outputs a tree representation
4of the node as a string.
5"""
7from phml.builder import p
8from phml.nodes import AST, All_Nodes, Comment, Element, Root, Text
10__all__ = ["inspect", "normalize_indent"]
13def inspect(start: AST | All_Nodes, indent: int = 2):
14 """Recursively inspect the passed node or ast."""
16 if isinstance(start, AST):
17 start = start.tree
19 def recursive_inspect(node: Element | Root, indent: int) -> list[str]:
20 """Generate signature for node then for each child recursively."""
21 from phml.utils import visit_children
23 results = [*signature(node)]
25 for idx, child in enumerate(visit_children(node)):
26 if isinstance(child, (Element, Root)):
27 lines = recursive_inspect(child, indent)
29 child_prefix = "└" if idx == len(node.children) - 1 else "├"
30 nested_prefix = " " if idx == len(node.children) - 1 else "│"
32 lines[0] = f"{child_prefix}{idx} {lines[0]}"
33 if len(lines) > 1:
34 for line in range(1, len(lines)):
35 lines[line] = f"{nested_prefix} {lines[line]}"
36 results.extend(lines)
37 else:
38 lines = signature(child, indent)
40 child_prefix = "└" if idx == len(node.children) - 1 else "├"
41 nested_prefix = " " if idx == len(node.children) - 1 else "│"
43 lines[0] = f"{child_prefix}{idx} {lines[0]}"
44 if len(lines) > 1:
45 for line in range(1, len(lines)):
46 lines[line] = f"{nested_prefix} {lines[line]}"
48 results.extend(lines)
49 return results
51 if isinstance(start, (Element, Root)):
52 return "\n".join(recursive_inspect(start, indent))
53 else:
54 return "\n".join(signature(start))
57def signature(node: All_Nodes, indent: int = 2):
58 """Generate the signature or base information for a single node."""
59 sig = f"{node.type}"
60 # element node's tag
61 if isinstance(node, Element):
62 sig += f"<{node.tag}>"
64 # count of children in parent node
65 if isinstance(node, (Element, Root)) and len(node.children) > 0:
66 sig += f" [{len(node.children)}]"
68 # position of non generated nodes
69 if node.position is not None:
70 sig += f" {node.position}"
72 result = [sig]
74 # element node's properties
75 if hasattr(node, "properties"):
76 for line in stringify_props(node):
77 result.append(f"│{' '*indent}{line}")
79 # literal node's value
80 if isinstance(node, (Text, Comment)):
81 for line in build_literal_value(node):
82 result.append(f"│{' '*indent}{line}")
84 return result
87def stringify_props(node: Element) -> list[str]:
88 """Generate a list of lines from strigifying the nodes properties."""
89 from json import dumps
91 if len(node.properties.keys()) > 0:
92 lines = dumps(node.properties, indent=2).split("\n")
93 lines[0] = f"properties: {lines[0]}"
94 return lines
95 return []
98def build_literal_value(node: Text | Comment) -> list[str]:
99 """Build the lines for the string value of a literal node."""
101 lines = normalize_indent(node.value).split("\n")
103 if len(lines) == 1:
104 lines[0] = f'"{lines[0]}"'
105 else:
106 lines[0] = f'"{lines[0]}'
107 lines[-1] = f' {lines[-1]}"'
108 if len(lines) > 2:
109 for idx in range(1, len(lines) - 1):
110 lines[idx] = f' {lines[idx]}'
111 return lines
114def normalize_indent(text: str) -> str:
115 """Remove extra prefix whitespac while preserving relative indenting.
117 Example:
118 ```python
119 if True:
120 print("Hello World")
121 ```
123 becomes
125 ```python
126 if True:
127 print("Hello World")
128 ```
129 """
130 lines = text.split("\n")
132 # Get min offset
133 if len(lines) > 1:
134 min_offset = len(lines[0])
135 for line in lines:
136 offset = len(line) - len(line.lstrip())
137 if offset < min_offset:
138 min_offset = offset
139 else:
140 return lines[0]
142 # Remove min_offset from each line
143 return "\n".join([line[min_offset:] for line in lines])