from dataclasses import dataclass
from typing import Iterable, Protocol, Sequence, runtime_checkable
@runtime_checkable
class TreeLike(Protocol):
@property
def children(self) -> Sequence["TreeLike"]: ... # Supports indexing like node.children[i]
def summary(self, **kwargs) -> str: ...
@dataclass(frozen=True)
class HTML():
html: str
def _repr_html_(self):
return self.html
def summarize_node(node: TreeLike, collapse = False, **kwargs) -> tuple[str, TreeLike]:
"""
Extracts a summarized representation of the node while collapsing single-child paths.
Returns the summary string and the last node in the chain that has multiple children.
"""
summaries = []
while True:
summary = node.summary(**kwargs)
if len(summary) > 50:
summary = summary[:50] + "..."
summaries.append(summary)
if not collapse:
break
# Move down if there's exactly one child, otherwise stop
if len(node.children) != 1:
break
node = node.children[0]
return ", ".join(summaries), node
def node_tree_to_string(node : TreeLike, prefix : str = "", depth = None) -> Iterable[str]:
summary, node = summarize_node(node)
if depth is not None and depth <= 0:
yield summary + " - ...\n"
return
# Special case for nodes with only a single child, this makes the printed representation more compact
elif len(node.children) == 1:
yield summary + ", "
yield from node_tree_to_string(node.children[0], prefix, depth = depth)
return
else:
yield summary + "\n"
for index, child in enumerate(node.children):
connector = "└── " if index == len(node.children) - 1 else "├── "
yield prefix + connector
extension = " " if index == len(node.children) - 1 else "│ "
yield from node_tree_to_string(child, prefix + extension, depth = depth - 1 if depth is not None else None)
def _node_tree_to_html(node : TreeLike, prefix : str = "", depth = 1, connector = "", **kwargs) -> Iterable[str]:
summary, node = summarize_node(node, **kwargs)
if len(node.children) == 0:
yield f'{connector}{summary}'
return
else:
open = "open" if depth > 0 else ""
yield f"{connector}{summary}
"
for index, child in enumerate(node.children):
connector = "└── " if index == len(node.children) - 1 else "├── "
extension = " " if index == len(node.children) - 1 else "│ "
yield from _node_tree_to_html(child, prefix + extension, depth = depth - 1, connector = prefix+connector, **kwargs)
yield "
{nodes}"