classmap-hanthink 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/CHANGELOG.md +10 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/PKG-INFO +19 -1
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/README.md +18 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/pyproject.toml +1 -1
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/src/classmap_hanthink/__init__.py +2 -1
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/src/classmap_hanthink/cli.py +7 -5
- classmap_hanthink-0.2.0/src/classmap_hanthink/render.py +144 -0
- classmap_hanthink-0.2.0/src/classmap_hanthink/tree.py +72 -0
- classmap_hanthink-0.2.0/tests/test_external.py +36 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/uv.lock +1 -1
- classmap_hanthink-0.1.0/src/classmap_hanthink/render.py +0 -99
- classmap_hanthink-0.1.0/src/classmap_hanthink/tree.py +0 -35
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/.gitignore +0 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/LICENSE +0 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/examples/sample_classes.py +0 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/src/classmap_hanthink/model.py +0 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/src/classmap_hanthink/scanner.py +0 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/tests/test_render.py +0 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/tests/test_scanner.py +0 -0
- {classmap_hanthink-0.1.0 → classmap_hanthink-0.2.0}/tests/test_tree.py +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-06-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added `--show-external` CLI option.
|
|
8
|
+
- Added external and built-in base class detection.
|
|
9
|
+
- Added `External Base Classes` output section.
|
|
10
|
+
- Added external base support for Mermaid and explanation output.
|
|
11
|
+
|
|
12
|
+
|
|
3
13
|
## [0.1.0] - 2026-06-04
|
|
4
14
|
|
|
5
15
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: classmap-hanthink
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Scan Python class inheritance relationships and render them as readable maps.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Han-think/classmap-hanthink
|
|
6
6
|
Author: Han-think
|
|
@@ -106,3 +106,21 @@ uv build
|
|
|
106
106
|
- Render optional explanation output
|
|
107
107
|
- Render optional Mermaid classDiagram output
|
|
108
108
|
- Provide CLI command: `classmap`
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## External Base Classes
|
|
112
|
+
|
|
113
|
+
Use `--show-external` to show classes that inherit from external or built-in base classes.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
classmap examples/sample_classes.py --show-external
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Example output:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
External Base Classes
|
|
123
|
+
|
|
124
|
+
dict
|
|
125
|
+
└── ExternalChild [examples/sample_classes.py:21]
|
|
126
|
+
```
|
|
@@ -92,3 +92,21 @@ uv build
|
|
|
92
92
|
- Render optional explanation output
|
|
93
93
|
- Render optional Mermaid classDiagram output
|
|
94
94
|
- Provide CLI command: `classmap`
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
## External Base Classes
|
|
98
|
+
|
|
99
|
+
Use `--show-external` to show classes that inherit from external or built-in base classes.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
classmap examples/sample_classes.py --show-external
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Example output:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
External Base Classes
|
|
109
|
+
|
|
110
|
+
dict
|
|
111
|
+
└── ExternalChild [examples/sample_classes.py:21]
|
|
112
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "classmap-hanthink"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
authors = [{ name = "Han-think" }]
|
|
9
9
|
description = "Scan Python class inheritance relationships and render them as readable maps."
|
|
10
10
|
readme = "README.md"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .model import ClassInfo
|
|
2
2
|
from .scanner import scan_file, scan_path
|
|
3
|
-
from .tree import build_inheritance_map, find_roots, find_independent_classes
|
|
3
|
+
from .tree import build_inheritance_map, build_external_base_map, find_roots, find_independent_classes
|
|
4
4
|
from .render import render_text_tree, render_explanation, render_mermaid
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
@@ -8,6 +8,7 @@ __all__ = [
|
|
|
8
8
|
"scan_file",
|
|
9
9
|
"scan_path",
|
|
10
10
|
"build_inheritance_map",
|
|
11
|
+
"build_external_base_map",
|
|
11
12
|
"find_roots",
|
|
12
13
|
"find_independent_classes",
|
|
13
14
|
"render_text_tree",
|
|
@@ -2,7 +2,7 @@ import argparse
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
from .scanner import scan_path
|
|
5
|
-
from .tree import build_inheritance_map
|
|
5
|
+
from .tree import build_external_base_map, build_inheritance_map
|
|
6
6
|
from .render import render_text_tree, render_explanation, render_mermaid
|
|
7
7
|
|
|
8
8
|
|
|
@@ -14,18 +14,20 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
14
14
|
parser.add_argument("target", nargs="?", default=".", help="Python file or folder to scan.")
|
|
15
15
|
parser.add_argument("--explain", action="store_true", help="Show beginner-friendly explanation.")
|
|
16
16
|
parser.add_argument("--mermaid", action="store_true", help="Render Mermaid classDiagram output.")
|
|
17
|
+
parser.add_argument("--show-external", action="store_true", help="Show external or built-in base classes.")
|
|
17
18
|
|
|
18
19
|
args = parser.parse_args(argv)
|
|
19
20
|
|
|
20
21
|
target = Path(args.target)
|
|
21
22
|
classes = scan_path(target)
|
|
22
23
|
tree = build_inheritance_map(classes)
|
|
24
|
+
external_map = build_external_base_map(classes)
|
|
23
25
|
|
|
24
26
|
if args.mermaid:
|
|
25
|
-
print(render_mermaid(classes, tree))
|
|
27
|
+
print(render_mermaid(classes, tree, external_map, show_external=args.show_external))
|
|
26
28
|
if args.explain:
|
|
27
29
|
print()
|
|
28
|
-
print(render_explanation(classes, tree))
|
|
30
|
+
print(render_explanation(classes, tree, external_map, show_external=args.show_external))
|
|
29
31
|
return 0
|
|
30
32
|
|
|
31
33
|
print("classmap report")
|
|
@@ -34,11 +36,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
34
36
|
print(f"Resolved path: {target.resolve()}")
|
|
35
37
|
print(f"Classes found: {len(classes)}")
|
|
36
38
|
print()
|
|
37
|
-
print(render_text_tree(classes, tree))
|
|
39
|
+
print(render_text_tree(classes, tree, external_map, show_external=args.show_external))
|
|
38
40
|
|
|
39
41
|
if args.explain:
|
|
40
42
|
print()
|
|
41
|
-
print(render_explanation(classes, tree))
|
|
43
|
+
print(render_explanation(classes, tree, external_map, show_external=args.show_external))
|
|
42
44
|
|
|
43
45
|
return 0
|
|
44
46
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from .model import ClassInfo
|
|
2
|
+
from .tree import find_independent_classes, find_roots
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _location_map(classes: list[ClassInfo]) -> dict[str, str]:
|
|
6
|
+
return {item.name: item.location for item in classes}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _render_node(name, tree, locations, prefix="", is_last=True, is_root=True) -> list[str]:
|
|
10
|
+
label = f"{name} [{locations.get(name, 'unknown')}]"
|
|
11
|
+
|
|
12
|
+
if is_root:
|
|
13
|
+
lines = [label]
|
|
14
|
+
else:
|
|
15
|
+
connector = "└── " if is_last else "├── "
|
|
16
|
+
lines = [prefix + connector + label]
|
|
17
|
+
|
|
18
|
+
children = tree.get(name, [])
|
|
19
|
+
|
|
20
|
+
if not is_root:
|
|
21
|
+
prefix = prefix + (" " if is_last else "│ ")
|
|
22
|
+
|
|
23
|
+
for index, child in enumerate(children):
|
|
24
|
+
lines.extend(_render_node(child, tree, locations, prefix=prefix, is_last=index == len(children) - 1, is_root=False))
|
|
25
|
+
|
|
26
|
+
return lines
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def render_external_bases(external_map: dict[str, list[str]], locations: dict[str, str] | None = None) -> str:
|
|
30
|
+
"""Render external or built-in base class relationships."""
|
|
31
|
+
if not external_map:
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
locations = locations or {}
|
|
35
|
+
lines = ["External Base Classes", ""]
|
|
36
|
+
|
|
37
|
+
for base, children in sorted(external_map.items()):
|
|
38
|
+
lines.append(base)
|
|
39
|
+
for index, child in enumerate(children):
|
|
40
|
+
connector = "└── " if index == len(children) - 1 else "├── "
|
|
41
|
+
lines.append(f"{connector}{child} [{locations.get(child, 'unknown')}]")
|
|
42
|
+
lines.append("")
|
|
43
|
+
|
|
44
|
+
return "\n".join(lines).rstrip()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def render_text_tree(
|
|
48
|
+
classes: list[ClassInfo],
|
|
49
|
+
tree: dict[str, list[str]],
|
|
50
|
+
external_map: dict[str, list[str]] | None = None,
|
|
51
|
+
*,
|
|
52
|
+
show_external: bool = False,
|
|
53
|
+
) -> str:
|
|
54
|
+
"""Render the inheritance map as a readable text tree."""
|
|
55
|
+
if not classes:
|
|
56
|
+
return "No classes found."
|
|
57
|
+
|
|
58
|
+
locations = _location_map(classes)
|
|
59
|
+
roots = find_roots(classes, tree)
|
|
60
|
+
independent = find_independent_classes(classes, tree, external_map, exclude_external=show_external)
|
|
61
|
+
|
|
62
|
+
lines: list[str] = ["Inheritance Tree", ""]
|
|
63
|
+
|
|
64
|
+
if roots:
|
|
65
|
+
for root in roots:
|
|
66
|
+
lines.extend(_render_node(root, tree, locations))
|
|
67
|
+
lines.append("")
|
|
68
|
+
else:
|
|
69
|
+
lines.append("No inheritance relationships found.")
|
|
70
|
+
lines.append("")
|
|
71
|
+
|
|
72
|
+
if show_external and external_map:
|
|
73
|
+
lines.append(render_external_bases(external_map, locations))
|
|
74
|
+
lines.append("")
|
|
75
|
+
|
|
76
|
+
if independent:
|
|
77
|
+
lines.append("Independent Classes")
|
|
78
|
+
lines.append("")
|
|
79
|
+
for name in independent:
|
|
80
|
+
lines.append(f"- {name} [{locations.get(name, 'unknown')}]")
|
|
81
|
+
lines.append("")
|
|
82
|
+
|
|
83
|
+
return "\n".join(lines).rstrip()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def render_explanation(
|
|
87
|
+
classes: list[ClassInfo],
|
|
88
|
+
tree: dict[str, list[str]],
|
|
89
|
+
external_map: dict[str, list[str]] | None = None,
|
|
90
|
+
*,
|
|
91
|
+
show_external: bool = False,
|
|
92
|
+
) -> str:
|
|
93
|
+
"""Render beginner-friendly explanation text."""
|
|
94
|
+
if not classes:
|
|
95
|
+
return "No classes were found in the target."
|
|
96
|
+
|
|
97
|
+
lines = ["Explanation", ""]
|
|
98
|
+
|
|
99
|
+
for parent, children in sorted(tree.items()):
|
|
100
|
+
joined = ", ".join(children)
|
|
101
|
+
lines.append(f"- {parent} has {len(children)} child class(es): {joined}")
|
|
102
|
+
|
|
103
|
+
if show_external and external_map:
|
|
104
|
+
for base, children in sorted(external_map.items()):
|
|
105
|
+
joined = ", ".join(children)
|
|
106
|
+
lines.append(f"- {base} is an external or built-in base class for: {joined}")
|
|
107
|
+
|
|
108
|
+
independent = find_independent_classes(classes, tree, external_map, exclude_external=show_external)
|
|
109
|
+
for name in independent:
|
|
110
|
+
lines.append(f"- {name} is independent.")
|
|
111
|
+
|
|
112
|
+
if len(lines) == 2:
|
|
113
|
+
lines.append("- Classes were found, but no inheritance relationships were detected.")
|
|
114
|
+
|
|
115
|
+
return "\n".join(lines)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def render_mermaid(
|
|
119
|
+
classes: list[ClassInfo],
|
|
120
|
+
tree: dict[str, list[str]],
|
|
121
|
+
external_map: dict[str, list[str]] | None = None,
|
|
122
|
+
*,
|
|
123
|
+
show_external: bool = False,
|
|
124
|
+
) -> str:
|
|
125
|
+
"""Render the inheritance map as Mermaid classDiagram syntax."""
|
|
126
|
+
lines = ["classDiagram"]
|
|
127
|
+
added_edge = False
|
|
128
|
+
|
|
129
|
+
for parent, children in sorted(tree.items()):
|
|
130
|
+
for child in children:
|
|
131
|
+
lines.append(f" {parent} <|-- {child}")
|
|
132
|
+
added_edge = True
|
|
133
|
+
|
|
134
|
+
if show_external and external_map:
|
|
135
|
+
for base, children in sorted(external_map.items()):
|
|
136
|
+
for child in children:
|
|
137
|
+
lines.append(f" {base} <|-- {child}")
|
|
138
|
+
added_edge = True
|
|
139
|
+
|
|
140
|
+
if not added_edge:
|
|
141
|
+
for item in classes:
|
|
142
|
+
lines.append(f" class {item.name}")
|
|
143
|
+
|
|
144
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
from .model import ClassInfo
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_inheritance_map(classes: list[ClassInfo]) -> dict[str, list[str]]:
|
|
7
|
+
"""Build a parent-to-children inheritance map using only scanned classes."""
|
|
8
|
+
known_names = {item.name for item in classes}
|
|
9
|
+
children: dict[str, list[str]] = defaultdict(list)
|
|
10
|
+
|
|
11
|
+
for item in classes:
|
|
12
|
+
for base in item.bases:
|
|
13
|
+
short_base = base.split(".")[-1]
|
|
14
|
+
if short_base in known_names:
|
|
15
|
+
children[short_base].append(item.name)
|
|
16
|
+
|
|
17
|
+
return {parent: sorted(child_list) for parent, child_list in children.items()}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_external_base_map(classes: list[ClassInfo]) -> dict[str, list[str]]:
|
|
21
|
+
"""Build an external-base-to-children map.
|
|
22
|
+
|
|
23
|
+
External base classes are base classes that appear in class definitions
|
|
24
|
+
but are not defined inside the scanned target.
|
|
25
|
+
"""
|
|
26
|
+
known_names = {item.name for item in classes}
|
|
27
|
+
external: dict[str, list[str]] = defaultdict(list)
|
|
28
|
+
|
|
29
|
+
for item in classes:
|
|
30
|
+
for base in item.bases:
|
|
31
|
+
short_base = base.split(".")[-1]
|
|
32
|
+
if short_base not in known_names:
|
|
33
|
+
external[base].append(item.name)
|
|
34
|
+
|
|
35
|
+
return {base: sorted(child_list) for base, child_list in sorted(external.items())}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def find_roots(classes: list[ClassInfo], tree: dict[str, list[str]]) -> list[str]:
|
|
39
|
+
"""Find root classes that have internal children but no internal parent."""
|
|
40
|
+
child_names = {child for children in tree.values() for child in children}
|
|
41
|
+
roots = [item.name for item in classes if item.name in tree and item.name not in child_names]
|
|
42
|
+
return sorted(set(roots))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def find_independent_classes(
|
|
46
|
+
classes: list[ClassInfo],
|
|
47
|
+
tree: dict[str, list[str]],
|
|
48
|
+
external_map: dict[str, list[str]] | None = None,
|
|
49
|
+
*,
|
|
50
|
+
exclude_external: bool = False,
|
|
51
|
+
) -> list[str]:
|
|
52
|
+
"""Find classes that are not connected to known inheritance relationships."""
|
|
53
|
+
known_names = {item.name for item in classes}
|
|
54
|
+
child_names = {child for children in tree.values() for child in children}
|
|
55
|
+
parent_names = set(tree.keys())
|
|
56
|
+
external_children = set()
|
|
57
|
+
|
|
58
|
+
if exclude_external and external_map:
|
|
59
|
+
external_children = {child for children in external_map.values() for child in children}
|
|
60
|
+
|
|
61
|
+
independent = []
|
|
62
|
+
|
|
63
|
+
for item in classes:
|
|
64
|
+
has_known_parent = any(base.split(".")[-1] in known_names for base in item.bases)
|
|
65
|
+
has_child = item.name in parent_names
|
|
66
|
+
is_child = item.name in child_names
|
|
67
|
+
has_external_parent = item.name in external_children
|
|
68
|
+
|
|
69
|
+
if not has_known_parent and not has_child and not is_child and not has_external_parent:
|
|
70
|
+
independent.append(item.name)
|
|
71
|
+
|
|
72
|
+
return sorted(independent)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from classmap_hanthink.model import ClassInfo
|
|
2
|
+
from classmap_hanthink.tree import build_external_base_map, build_inheritance_map, find_independent_classes
|
|
3
|
+
from classmap_hanthink.render import render_text_tree, render_mermaid
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_build_external_base_map():
|
|
7
|
+
classes = [ClassInfo("Animal"), ClassInfo("Dog", ("Animal",)), ClassInfo("ExternalChild", ("dict",))]
|
|
8
|
+
external = build_external_base_map(classes)
|
|
9
|
+
assert external == {"dict": ["ExternalChild"]}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_external_child_removed_from_independent_when_enabled():
|
|
13
|
+
classes = [ClassInfo("Config"), ClassInfo("ExternalChild", ("dict",))]
|
|
14
|
+
tree = build_inheritance_map(classes)
|
|
15
|
+
external = build_external_base_map(classes)
|
|
16
|
+
independent = find_independent_classes(classes, tree, external, exclude_external=True)
|
|
17
|
+
assert independent == ["Config"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_render_text_tree_show_external():
|
|
21
|
+
classes = [ClassInfo("ExternalChild", ("dict",), file="sample.py", line=1)]
|
|
22
|
+
tree = build_inheritance_map(classes)
|
|
23
|
+
external = build_external_base_map(classes)
|
|
24
|
+
output = render_text_tree(classes, tree, external, show_external=True)
|
|
25
|
+
assert "External Base Classes" in output
|
|
26
|
+
assert "dict" in output
|
|
27
|
+
assert "ExternalChild [sample.py:1]" in output
|
|
28
|
+
assert "Independent Classes" not in output
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_render_mermaid_show_external():
|
|
32
|
+
classes = [ClassInfo("ExternalChild", ("dict",))]
|
|
33
|
+
tree = build_inheritance_map(classes)
|
|
34
|
+
external = build_external_base_map(classes)
|
|
35
|
+
output = render_mermaid(classes, tree, external, show_external=True)
|
|
36
|
+
assert "dict <|-- ExternalChild" in output
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
from .model import ClassInfo
|
|
2
|
-
from .tree import find_independent_classes, find_roots
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def _location_map(classes: list[ClassInfo]) -> dict[str, str]:
|
|
6
|
-
return {item.name: item.location for item in classes}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _render_node(name, tree, locations, prefix="", is_last=True, is_root=True) -> list[str]:
|
|
10
|
-
label = f"{name} [{locations.get(name, 'unknown')}]"
|
|
11
|
-
|
|
12
|
-
if is_root:
|
|
13
|
-
lines = [label]
|
|
14
|
-
else:
|
|
15
|
-
connector = "└── " if is_last else "├── "
|
|
16
|
-
lines = [prefix + connector + label]
|
|
17
|
-
|
|
18
|
-
children = tree.get(name, [])
|
|
19
|
-
|
|
20
|
-
if not is_root:
|
|
21
|
-
prefix = prefix + (" " if is_last else "│ ")
|
|
22
|
-
|
|
23
|
-
for index, child in enumerate(children):
|
|
24
|
-
lines.extend(
|
|
25
|
-
_render_node(
|
|
26
|
-
child,
|
|
27
|
-
tree,
|
|
28
|
-
locations,
|
|
29
|
-
prefix=prefix,
|
|
30
|
-
is_last=index == len(children) - 1,
|
|
31
|
-
is_root=False,
|
|
32
|
-
)
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
return lines
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def render_text_tree(classes: list[ClassInfo], tree: dict[str, list[str]]) -> str:
|
|
39
|
-
if not classes:
|
|
40
|
-
return "No classes found."
|
|
41
|
-
|
|
42
|
-
locations = _location_map(classes)
|
|
43
|
-
roots = find_roots(classes, tree)
|
|
44
|
-
independent = find_independent_classes(classes, tree)
|
|
45
|
-
|
|
46
|
-
lines: list[str] = ["Inheritance Tree", ""]
|
|
47
|
-
|
|
48
|
-
if roots:
|
|
49
|
-
for root in roots:
|
|
50
|
-
lines.extend(_render_node(root, tree, locations))
|
|
51
|
-
lines.append("")
|
|
52
|
-
else:
|
|
53
|
-
lines.append("No inheritance relationships found.")
|
|
54
|
-
lines.append("")
|
|
55
|
-
|
|
56
|
-
if independent:
|
|
57
|
-
lines.append("Independent Classes")
|
|
58
|
-
lines.append("")
|
|
59
|
-
for name in independent:
|
|
60
|
-
lines.append(f"- {name} [{locations.get(name, 'unknown')}]")
|
|
61
|
-
lines.append("")
|
|
62
|
-
|
|
63
|
-
return "\n".join(lines).rstrip()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def render_explanation(classes: list[ClassInfo], tree: dict[str, list[str]]) -> str:
|
|
67
|
-
if not classes:
|
|
68
|
-
return "No classes were found in the target."
|
|
69
|
-
|
|
70
|
-
lines = ["Explanation", ""]
|
|
71
|
-
|
|
72
|
-
for parent, children in sorted(tree.items()):
|
|
73
|
-
joined = ", ".join(children)
|
|
74
|
-
lines.append(f"- {parent} has {len(children)} child class(es): {joined}")
|
|
75
|
-
|
|
76
|
-
independent = find_independent_classes(classes, tree)
|
|
77
|
-
for name in independent:
|
|
78
|
-
lines.append(f"- {name} is independent.")
|
|
79
|
-
|
|
80
|
-
if len(lines) == 2:
|
|
81
|
-
lines.append("- Classes were found, but no inheritance relationships were detected.")
|
|
82
|
-
|
|
83
|
-
return "\n".join(lines)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def render_mermaid(classes: list[ClassInfo], tree: dict[str, list[str]]) -> str:
|
|
87
|
-
lines = ["classDiagram"]
|
|
88
|
-
added_edge = False
|
|
89
|
-
|
|
90
|
-
for parent, children in sorted(tree.items()):
|
|
91
|
-
for child in children:
|
|
92
|
-
lines.append(f" {parent} <|-- {child}")
|
|
93
|
-
added_edge = True
|
|
94
|
-
|
|
95
|
-
if not added_edge:
|
|
96
|
-
for item in classes:
|
|
97
|
-
lines.append(f" class {item.name}")
|
|
98
|
-
|
|
99
|
-
return "\n".join(lines)
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from collections import defaultdict
|
|
2
|
-
|
|
3
|
-
from .model import ClassInfo
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def build_inheritance_map(classes: list[ClassInfo]) -> dict[str, list[str]]:
|
|
7
|
-
known_names = {item.name for item in classes}
|
|
8
|
-
children: dict[str, list[str]] = defaultdict(list)
|
|
9
|
-
|
|
10
|
-
for item in classes:
|
|
11
|
-
for base in item.bases:
|
|
12
|
-
short_base = base.split(".")[-1]
|
|
13
|
-
if short_base in known_names:
|
|
14
|
-
children[short_base].append(item.name)
|
|
15
|
-
|
|
16
|
-
return {parent: sorted(child_list) for parent, child_list in children.items()}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def find_roots(classes: list[ClassInfo], tree: dict[str, list[str]]) -> list[str]:
|
|
20
|
-
child_names = {child for children in tree.values() for child in children}
|
|
21
|
-
roots = [item.name for item in classes if item.name in tree and item.name not in child_names]
|
|
22
|
-
return sorted(set(roots))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def find_independent_classes(classes: list[ClassInfo], tree: dict[str, list[str]]) -> list[str]:
|
|
26
|
-
known_names = {item.name for item in classes}
|
|
27
|
-
parent_names = set(tree.keys())
|
|
28
|
-
independent = []
|
|
29
|
-
|
|
30
|
-
for item in classes:
|
|
31
|
-
has_known_parent = any(base.split(".")[-1] in known_names for base in item.bases)
|
|
32
|
-
if not has_known_parent and item.name not in parent_names:
|
|
33
|
-
independent.append(item.name)
|
|
34
|
-
|
|
35
|
-
return sorted(independent)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|