Skip to content

Commit 31e0364

Browse files
committed
Added module dep grapher for matplotlib.
1 parent fb62755 commit 31e0364

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

third-party/modgraph.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python
2+
from collections import Counter, OrderedDict
3+
from functools import lru_cache
4+
from modulefinder import Module, ModuleFinder
5+
import sys
6+
7+
import networkx as nx
8+
9+
import matplotlib.pyplot as plt
10+
11+
12+
def shorten(mod_name:str, length:int=2) -> str:
13+
return ".".join(mod_name.split(".")[:length])
14+
15+
16+
@lru_cache(maxsize=4095)
17+
def levenshtein_distance(s:str, t:str) -> int:
18+
if not s: return len(t)
19+
if not t: return len(s)
20+
if s[0] == t[0]: return levenshtein_distance(s[1:], t[1:])
21+
l1 = levenshtein_distance(s, t[1:])
22+
l2 = levenshtein_distance(s[1:], t)
23+
l3 = levenshtein_distance(s[1:], t[1:])
24+
return 1 + min(l1, l2, l3)
25+
26+
27+
def make_colors(graph:list) -> list:
28+
names = graph.nodes()
29+
longest = max(names)
30+
raw = [levenshtein_distance(x, longest) for x in names]
31+
largest_raw = max(raw)
32+
degrees = [graph.degree(x) for x in graph]
33+
largest_degrees = max(degrees)
34+
return map(lambda x, y: x + y,
35+
[int(10 * x/largest_degrees) for x in degrees],
36+
[10 * x/largest_raw for x in raw])
37+
38+
39+
class CustomFinder(ModuleFinder):
40+
def __init__(self, root:str, include:list, layout:str, graph_class:type,
41+
mode:str, *args, **kwargs):
42+
super().__init__(*args, **kwargs)
43+
self.cf_root = root
44+
self.cf_include = include
45+
self.cf_layout = layout
46+
self.cf_graph_class = graph_class
47+
self.cf_mode = mode
48+
self.cf_imports = OrderedDict()
49+
self.cf_weights = Counter()
50+
self.cf_node_multiplier = 1
51+
52+
def matches(self, name:str) -> bool:
53+
if True in [name.startswith(x) for x in self.cf_include]:
54+
return True
55+
return False
56+
57+
def import_hook(self, name:str, caller:Module=None, fromlist:list=None,
58+
level:int=-1) -> Module:
59+
if self.matches(name):
60+
if caller:
61+
print(caller.__name__, " -> ", name)
62+
self.cf_weights[name] += 1
63+
self.cf_imports[(caller.__name__, name)] = 1
64+
super().import_hook(name, caller, fromlist, level)
65+
66+
def relations(self) -> list:
67+
return [(key, val, {"weight": self.cf_weights[val]})
68+
for (key, val) in self.cf_imports.keys()]
69+
70+
def simple_relations(self) -> list:
71+
new_weights = Counter()
72+
relations = []
73+
for (key, val) in self.cf_imports.keys():
74+
(short_key, short_val) = (shorten(key), shorten(val))
75+
new_weights[short_val] += self.cf_weights[val]
76+
relations.append((short_key, short_val))
77+
self.cf_weights = new_weights
78+
return [(key, val, {"weight": self.cf_weights[val]})
79+
for (key, val) in relations]
80+
81+
def as_dict(self):
82+
return [dict([x]) for x in self.cf_imports.keys()]
83+
84+
def graph(self) -> nx.Graph:
85+
if self.cf_mode == "simple":
86+
data = self.simple_relations()
87+
self.cf_node_multiplier =10
88+
elif self.cf_mode == "full":
89+
data = self.relations()
90+
self.cf_node_multiplier = 200
91+
elif self.cf_mode == "structured":
92+
data = self.structured_relations()
93+
else:
94+
raise Exception("Undefined mode.")
95+
return self.cf_graph_class(data)
96+
97+
def render(self) -> None:
98+
plt.figure(figsize=(35,35))
99+
graph = self.graph()
100+
node_sizes = [self.cf_node_multiplier * self.cf_weights[x]
101+
for x in graph]
102+
node_colors = [float(x) for x in make_colors(graph)]
103+
print("\nDegrees: ", [graph.degree(x) for x in graph])
104+
print("\nShorten: ", [shorten(x) for x in graph])
105+
print("\nColors: ", [int(x) for x in make_colors(graph)])
106+
pos = nx.graphviz_layout(graph, prog=self.cf_layout, root=self.cf_root)
107+
nx.draw(graph, pos, node_size=node_sizes, node_color=node_colors,
108+
with_labels=True, alpha=0.5)
109+
plt.savefig("modgraph.png")
110+
111+
112+
def usage(scriptname:str) -> None:
113+
print("Usage:\n\t{} filename [layout [mode]]\n".format(scriptname))
114+
115+
116+
if __name__ == '__main__':
117+
args = sys.argv
118+
if len(args) < 2:
119+
usage(args[0])
120+
exit(1)
121+
if len(args) >= 3:
122+
layout = args[2]
123+
else:
124+
layout = "dot"
125+
if len(args) >= 4:
126+
mode = args[3]
127+
else:
128+
mode = "full"
129+
finder = CustomFinder("__main__", ["matpl", "mpl"], layout, nx.Graph, mode)
130+
finder.run_script(sys.argv[1])
131+
print("\nModules: ", sorted(finder.modules.keys()))
132+
print("\nAs dict: ", finder.as_dict())
133+
print("\nWeights: ", finder.cf_weights.items())
134+
finder.render()

0 commit comments

Comments
 (0)