phml.misc.inspect
phml.utils.misc.inspect
Logic to inspect any phml node. Outputs a tree representation of the node as a string.
1"""phml.utils.misc.inspect 2 3Logic to inspect any phml node. Outputs a tree representation 4of the node as a string. 5""" 6 7from json import dumps 8 9from phml.nodes import AST, All_Nodes, Comment, Element, Root, Text 10 11__all__ = ["inspect", "normalize_indent"] 12 13 14def inspect(start: AST | All_Nodes, indent: int = 2): 15 """Recursively inspect the passed node or ast.""" 16 17 if isinstance(start, AST): 18 start = start.tree 19 20 def recursive_inspect(node: Element | Root, indent: int) -> list[str]: 21 """Generate signature for node then for each child recursively.""" 22 from phml import visit_children # pylint: disable=import-outside-toplevel 23 24 results = [*signature(node)] 25 26 for idx, child in enumerate(visit_children(node)): 27 if isinstance(child, (Element, Root)): 28 lines = recursive_inspect(child, indent) 29 30 child_prefix = "└" if idx == len(node.children) - 1 else "├" 31 nested_prefix = " " if idx == len(node.children) - 1 else "│" 32 33 lines[0] = f"{child_prefix}{idx} {lines[0]}" 34 if len(lines) > 1: 35 for line in range(1, len(lines)): 36 lines[line] = f"{nested_prefix} {lines[line]}" 37 results.extend(lines) 38 else: 39 lines = signature(child, indent) 40 41 child_prefix = "└" if idx == len(node.children) - 1 else "├" 42 nested_prefix = " " if idx == len(node.children) - 1 else "│" 43 44 lines[0] = f"{child_prefix}{idx} {lines[0]}" 45 if len(lines) > 1: 46 for line in range(1, len(lines)): 47 lines[line] = f"{nested_prefix} {lines[line]}" 48 49 results.extend(lines) 50 return results 51 52 if isinstance(start, (Element, Root)): 53 return "\n".join(recursive_inspect(start, indent)) 54 55 return "\n".join(signature(start)) 56 57 58def signature(node: All_Nodes, indent: int = 2): 59 """Generate the signature or base information for a single node.""" 60 sig = f"{node.type}" 61 # element node's tag 62 if isinstance(node, Element): 63 sig += f"<{node.tag}{'/' if node.startend else ''}>" 64 65 # count of children in parent node 66 if isinstance(node, (Element, Root)) and len(node.children) > 0: 67 sig += f" [{len(node.children)}]" 68 69 # position of non generated nodes 70 if node.position is not None: 71 sig += f" {node.position}" 72 73 result = [sig] 74 75 # element node's properties 76 if hasattr(node, "properties"): 77 for line in stringify_props(node): 78 result.append(f"│{' '*indent}{line}") 79 80 # literal node's value 81 if isinstance(node, (Text, Comment)): 82 for line in build_literal_value(node): 83 result.append(f"│{' '*indent}{line}") 84 85 return result 86 87 88def stringify_props(node: Element) -> list[str]: 89 """Generate a list of lines from strigifying the nodes properties.""" 90 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 [] 96 97 98def build_literal_value(node: Text | Comment) -> list[str]: 99 """Build the lines for the string value of a literal node.""" 100 101 lines = normalize_indent(node.value).split("\n") 102 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 112 113 114def normalize_indent(text: str) -> str: 115 """Remove extra prefix whitespac while preserving relative indenting. 116 117 Example: 118 ```python 119 if True: 120 print("Hello World") 121 ``` 122 123 becomes 124 125 ```python 126 if True: 127 print("Hello World") 128 ``` 129 """ 130 lines = text.split("\n") 131 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] 141 142 # Remove min_offset from each line 143 return "\n".join([line[min_offset:] for line in lines])
def
inspect( start: phml.nodes.AST.AST | phml.nodes.root.Root | phml.nodes.element.Element | phml.nodes.text.Text | phml.nodes.comment.Comment | phml.nodes.doctype.DocType | phml.nodes.parent.Parent | phml.nodes.node.Node | phml.nodes.literal.Literal, indent: int = 2):
15def inspect(start: AST | All_Nodes, indent: int = 2): 16 """Recursively inspect the passed node or ast.""" 17 18 if isinstance(start, AST): 19 start = start.tree 20 21 def recursive_inspect(node: Element | Root, indent: int) -> list[str]: 22 """Generate signature for node then for each child recursively.""" 23 from phml import visit_children # pylint: disable=import-outside-toplevel 24 25 results = [*signature(node)] 26 27 for idx, child in enumerate(visit_children(node)): 28 if isinstance(child, (Element, Root)): 29 lines = recursive_inspect(child, indent) 30 31 child_prefix = "└" if idx == len(node.children) - 1 else "├" 32 nested_prefix = " " if idx == len(node.children) - 1 else "│" 33 34 lines[0] = f"{child_prefix}{idx} {lines[0]}" 35 if len(lines) > 1: 36 for line in range(1, len(lines)): 37 lines[line] = f"{nested_prefix} {lines[line]}" 38 results.extend(lines) 39 else: 40 lines = signature(child, indent) 41 42 child_prefix = "└" if idx == len(node.children) - 1 else "├" 43 nested_prefix = " " if idx == len(node.children) - 1 else "│" 44 45 lines[0] = f"{child_prefix}{idx} {lines[0]}" 46 if len(lines) > 1: 47 for line in range(1, len(lines)): 48 lines[line] = f"{nested_prefix} {lines[line]}" 49 50 results.extend(lines) 51 return results 52 53 if isinstance(start, (Element, Root)): 54 return "\n".join(recursive_inspect(start, indent)) 55 56 return "\n".join(signature(start))
Recursively inspect the passed node or ast.
def
normalize_indent(text: str) -> str:
115def normalize_indent(text: str) -> str: 116 """Remove extra prefix whitespac while preserving relative indenting. 117 118 Example: 119 ```python 120 if True: 121 print("Hello World") 122 ``` 123 124 becomes 125 126 ```python 127 if True: 128 print("Hello World") 129 ``` 130 """ 131 lines = text.split("\n") 132 133 # Get min offset 134 if len(lines) > 1: 135 min_offset = len(lines[0]) 136 for line in lines: 137 offset = len(line) - len(line.lstrip()) 138 if offset < min_offset: 139 min_offset = offset 140 else: 141 return lines[0] 142 143 # Remove min_offset from each line 144 return "\n".join([line[min_offset:] for line in lines])
Remove extra prefix whitespac while preserving relative indenting.
Example:
if True:
print("Hello World")
becomes
if True:
print("Hello World")