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")