diff --git a/.vscode/settings.json b/.vscode/settings.json index ed4a07f..052fef8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,5 +22,10 @@ "reorder-python-imports.args": [ "--application-directories=.:src", "--add-import 'from __future__ import annotations'", - ] + ], + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true } diff --git a/src/dev_toolbox/data_structures/tree.py b/src/dev_toolbox/data_structures/tree.py index 91464a8..0b155b5 100755 --- a/src/dev_toolbox/data_structures/tree.py +++ b/src/dev_toolbox/data_structures/tree.py @@ -4,45 +4,52 @@ from dataclasses import dataclass from dataclasses import field from functools import lru_cache +from typing import Callable from typing import Generator +from typing import Generic +from typing import Hashable +from typing import Sequence from typing import TYPE_CHECKING +from typing import TypeVar if TYPE_CHECKING: from typing_extensions import Self +_T = TypeVar("_T", bound=Hashable) + @dataclass(unsafe_hash=True) -class Node: +class TreeNode(Generic[_T]): """Node for tree.""" - name: str = field(hash=True) + data: _T = field(hash=True) parent: Self | None = field(default=None, hash=False) children: list[Self] = field(default_factory=list, hash=False) @classmethod - def build_tree(cls, parent_child_connections: list[tuple[str, str]]) -> list[Self]: + def build_tree(cls, parent_child_connections: list[tuple[_T, _T]]) -> list[Self]: """Build tree from connections.""" - nodes: dict[str, Self] = {} + nodes: dict[_T, Self] = {} for parent, child in parent_child_connections: if parent not in nodes: - nodes[parent] = cls(name=parent) + nodes[parent] = cls(data=parent) if child not in nodes: - nodes[child] = cls(name=child) + nodes[child] = cls(data=child) nodes[parent].children.append(nodes[child]) nodes[child].parent = nodes[parent] for node in nodes.values(): - node.children = sorted(node.children, key=lambda x: (cls.children_count(x), x.name)) + node.children = sorted(node.children, key=lambda x: (cls.children_count(x), x.data)) return sorted( (v for v in nodes.values() if v.parent is None), - key=lambda x: (cls.children_count(x), x.name), + key=lambda x: (cls.children_count(x), x.data), ) - def connections(self) -> Generator[tuple[str, str], None, None]: + def connections(self) -> Generator[tuple[_T, _T], None, None]: """Get connections.""" for child in self.children: - yield (self.name, child.name) + yield (self.data, child.data) yield from child.connections() def nodes(self) -> Generator[Self, None, None]: @@ -57,19 +64,35 @@ def children_count(cls, node: Self) -> int: """Count children.""" return sum(1 + cls.children_count(child) for child in node.children) - def print_node(self, level: int = 0) -> None: + def print_node(self, level: int = 0, _repr: Callable[[_T], str] = str) -> None: """Print node.""" - print(" " * level + "- " + self.name) + print(" " * level + "- " + _repr(self.data)) for child in self.children: - child.print_node(level + 1) + child.print_node(level + 1, _repr=_repr) - def print_tree(self, *, prefix: str = "") -> None: + def print_tree(self, *, prefix: str = "", _repr: Callable[[_T], str] = str) -> None: if not prefix: - print(self.name) + print(_repr(self.data)) for i, child in enumerate(self.children): if i == len(self.children) - 1: - print(f"{prefix}└── {child.name}") - child.print_tree(prefix=prefix + " ") + print(f"{prefix}└── {_repr(child.data)}") + child.print_tree(prefix=prefix + " ", _repr=_repr) else: - print(f"{prefix}├── {child.name}") - child.print_tree(prefix=f"{prefix}│ ") + print(f"{prefix}├── {_repr(child.data)}") + child.print_tree(prefix=f"{prefix}│ ", _repr=_repr) + + @classmethod + def parse_indent_hierarchy(cls, lines: Sequence[str]) -> list[Self]: + """Parse indent hierarchy.""" + parent_child_connections: list[tuple[str, str]] = [] + stack: list[tuple[int, str]] = [] + for line in lines: + indent = len(line) - len(line.lstrip()) + while stack and indent <= stack[-1][0]: + stack.pop() + if stack: + parent = stack[-1][1] + child = line.strip() + parent_child_connections.append((parent, child)) + stack.append((indent, line.strip())) + return cls.build_tree(parent_child_connections) # type: ignore[arg-type] diff --git a/src/dev_toolbox/reflection_tools/class_hierarchy.py b/src/dev_toolbox/reflection_tools/class_hierarchy.py index 55c3db8..3731305 100644 --- a/src/dev_toolbox/reflection_tools/class_hierarchy.py +++ b/src/dev_toolbox/reflection_tools/class_hierarchy.py @@ -10,7 +10,7 @@ from typing import Sequence from typing import TYPE_CHECKING -from dev_toolbox.data_structures.tree import Node +from dev_toolbox.data_structures.tree import TreeNode if TYPE_CHECKING: @@ -72,8 +72,8 @@ def main(argv: Sequence[str] | None = None) -> int: else: relations = get_relations(args.files_or_modules) - tree_top_nodes = Node.build_tree(list(relations)) - Node(name="args.root", children=tree_top_nodes).print_tree() + tree_top_nodes = TreeNode.build_tree(list(relations)) + TreeNode(data="args.root", children=tree_top_nodes).print_tree() return 0