-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
63 lines (51 loc) · 1.89 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import os
import sys
import ast
import graphviz
class ClassAnalyzer(ast.NodeVisitor):
def __init__(self):
self.classes = {}
def visit_ClassDef(self, node):
self.classes[node.name] = {
"bases": [base.id for base in node.bases],
"methods": [n.name for n in node.body if isinstance(n, ast.FunctionDef)],
"calls": self.analyze_function_calls(node),
}
def analyze_function_calls(self, node):
calls = []
for n in ast.walk(node):
if isinstance(n, ast.Call):
try:
func = next(
a.id for a in ast.walk(n.func) if isinstance(a, ast.Name)
)
calls.append(func)
except StopIteration:
pass
return calls
def generate_class_graph(directory):
analyzer = ClassAnalyzer()
for filename in os.listdir(directory):
if filename.endswith(".py"):
with open(os.path.join(directory, filename), "r") as f:
try:
tree = ast.parse(f.read())
analyzer.visit(tree)
except SyntaxError:
print(f"Skipping {filename} due to syntax error.")
graph = graphviz.Digraph(comment="Class Relationships")
for cls, data in analyzer.classes.items():
graph.node(cls)
for base in data["bases"]:
if base in analyzer.classes:
graph.edge(cls, base, label="inherits")
for call in data["calls"]:
if call in analyzer.classes and call != cls:
graph.edge(cls, call, label="calls")
graph.render("class_relationship_graph", view=True)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python class_graph.py <directory>")
sys.exit(1)
directory = sys.argv[1]
generate_class_graph(directory)