1
from __future__ import division
9
exclude_files = ["__init__.py", "autopath.py", "conftest.py"]
11
def include_file(path):
12
if ("test" in str(path) or "tool" in str(path) or
13
"documentation" in str(path) or "pyrex" in str(path) or
14
"_cache" in str(path)):
16
if path.basename in exclude_files:
20
def get_mod_from_path(path):
21
dirs = path.get("dirname")[0].split("/")
22
pypyindex = dirs.index("pypy")
23
return ".".join(dirs[pypyindex:] + path.get("purebasename"))
26
def find_references(path):
28
for line in path.open("r"):
29
if line.startswith(" "): # ignore local imports to reduce graph size
31
if "\\" in line: #ignore line continuations
34
line = line.split("#")[0].strip()
35
if line.startswith("import pypy."): # import pypy.bla.whatever
36
if " as " not in line:
37
refs.append((line[7:].strip(), None))
38
else: # import pypy.bla.whatever as somethingelse
39
assert line.count(" as ") == 1
40
line = line.split(" as ")
41
refs.append((line[0][7:].strip(), line[1].strip()))
42
elif line.startswith("from ") and "pypy" in line: #from pypy.b import a
44
if " as " not in line:
45
line = line.split(" import ")
46
what = line[1].split(",")
48
refs.append((line[0].strip() + "." + w.strip(), None))
49
else: # prom pypy.b import a as c
50
if line.count(" as ") != 1 or "," in line:
51
print"can't handle this: " + line
53
line = line.split(" as ")
54
what = line[0].replace(" import ", ".").replace(" ", "")
55
refs.append((what, line[1].strip()))
58
def get_module(ref, imports):
62
possible_mod = ".".join(ref[:i])
63
if possible_mod in imports:
68
def casteljeau(points, t):
70
while len(points) > 1:
71
for i in range(len(points) - 1):
72
points[i] = points[i] * (1 - t) + points[i + 1] * t
77
points = [0, 0, 1, 0, 0]
78
casteljeau([0, 0, 1, 0, 0], t) / 0.375
80
class ModuleGraph(object):
81
def __init__(self, path):
84
self.mod_to_cluster = {}
85
for f in path.visit("*.py"):
87
self.imports[get_mod_from_path(f)] = find_references(f)
88
self.remove_object_refs()
89
self.remove_double_refs()
91
for mod in self.imports:
92
self.incoming[mod] = sets.Set()
93
for mod, refs in self.imports.iteritems():
95
if ref[0] in self.incoming:
96
self.incoming[ref[0]].add(mod)
97
self.remove_single_nodes()
98
self.topgraph_properties = ["rankdir=LR"]
100
def remove_object_refs(self):
101
# reduces cases like import pypy.translator.genc.basetype.CType to
102
# import pypy.translator.genc.basetype
103
for mod, refs in self.imports.iteritems():
106
if refs[i][0] in self.imports:
109
nref = get_module(refs[i][0], self.imports)
111
print "removing", repr(refs[i])
114
refs[i] = (nref, None)
117
def remove_double_refs(self):
118
# remove several references to the same module
119
for mod, refs in self.imports.iteritems():
121
seen_refs = sets.Set()
123
if refs[i] not in seen_refs:
124
seen_refs.add(refs[i])
129
def remove_single_nodes(self):
130
# remove nodes that have no attached edges
132
for mod, refs in self.imports.iteritems():
133
if len(refs) == 0 and len(self.incoming[mod]) == 0:
139
def create_clusters(self):
140
self.topgraph_properties.append("compound=true;")
141
self.clustered = True
142
hierarchy = [sets.Set() for i in range(6)]
143
for mod in self.imports:
144
for i, d in enumerate(mod.split(".")):
147
if len(hierarchy[i]) != 1:
149
for mod in self.imports:
150
cluster = mod.split(".")[i]
151
if i == len(mod.split(".")) - 1:
153
if cluster not in self.clusters:
154
self.clusters[cluster] = sets.Set()
155
self.clusters[cluster].add(mod)
156
self.mod_to_cluster[mod] = cluster
158
def remove_tangling_randomly(self):
159
# remove edges to nodes that have a lot incoming edges randomly
161
for mod, incoming in self.incoming.iteritems():
162
if len(incoming) > 10:
166
incoming = self.incoming[mod]
167
while len(remove) < len(incoming) * 0.80:
168
remove.add(random.choice(list(incoming)))
170
for i in range(len(self.imports[rem])):
171
if self.imports[rem][i][1] == mod:
173
del self.imports[rem][i]
175
print "removing", mod, "<-", rem
176
self.remove_single_nodes()
178
def dotfile(self, dot):
180
f.write("digraph G {\n")
181
for prop in self.topgraph_properties:
182
f.write("\t%s\n" % prop)
183
#write clusters and inter-cluster edges
184
for cluster, nodes in self.clusters.iteritems():
185
f.write("\tsubgraph cluster_%s {\n" % cluster)
186
f.write("\t\tstyle=filled;\n\t\tcolor=lightgrey\n")
188
f.write('\t\t"%s";\n' % node[5:])
189
for mod, refs in self.imports.iteritems():
191
if mod in nodes and ref[0] in nodes:
192
f.write('\t\t"%s" -> "%s";\n' % (mod[5:], ref[0][5:]))
194
#write edges between clusters
195
for mod, refs in self.imports.iteritems():
197
nodes = self.clusters[self.mod_to_cluster[mod]]
201
if ref[0] not in nodes:
202
f.write('\t"%s" -> "%s";\n' % (mod[5:], ref[0][5:]))
206
if __name__ == "__main__":
208
if len(sys.argv) > 1:
209
path = py.path.local(sys.argv[1])
211
path = py.path.local(".")
212
gr = ModuleGraph(path)
214
dot = path.join("import_graph.dot")