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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
"""Given a Python module or package, it draws the classes defined in it.
It is smart enough to recognize disconnected inheritance hierarchies.
Usage:
$ python drawclasses.py <module-or-package-in-the-pythonpath>
"""
import os, types, inspect
from ms.tools import minidoc
from ms.iter_utils import skip_redundant as remove_dupl
def ismeta(c):
return isinstance(c, type) and issubclass(c, type)
def isclass(c):
return isinstance(c, (type, types.ClassType))
def getclasses(module_or_package):
x = minidoc.import_(module_or_package)
if hasattr(x, "__path__"): # is package
packagecontent = os.listdir(x.__path__[0])
for fname in packagecontent:
if fname.endswith('.py') or fname.endswith('.pyc') \
or fname.endswith('.pyd') or fname.endswith('.pyo'):
module = x.__name__ + "." + minidoc.body(fname)
for cls in getclasses(module):
yield cls
else: # is module
for name,obj in x.__dict__.iteritems():
if isclass(obj):
yield obj
def codegen(name, ls):
drawn = set()
yield 'digraph %s{' % name
# yield 'caption [shape=box,label=%s\n]' % name
yield 'size="7,10"'
# yield 'rotate=90'
for c in ls:
bases = list(c.__bases__)
if object in bases:
bases.remove(object)
for b in bases:
yield "%s-> %s" % (b.__name__,c.__name__)
drawn.add(b)
#yield "%s -> %s [style=dashed]" % (c.__name__,type(c).__name__)
drawn.add(c)
yield ''
for c in drawn:
if ismeta(c):
name = "%s" % c.__name__
else:
name = "%s [shape=box]" % c.__name__
if hasattr(c,'__metaclass__'):
name += ' [color=red]' # ' [style=dashed]'
yield name
yield '}'
def mro(cls):
return inspect.getmro(cls)
def samegraph(A, B): # always True for new-style
"Returns true if the MRO's of A and B have an intersection."
mro_A = set(mro(A))
mro_B = set(mro(B))
return bool(mro_A & mro_B)
def graphs(classes):
"Given a set of classes, yields the connected subsets."
klasses = list(classes)
for a in klasses:
graph = [a]
for b in klasses[:]:
if b is not a and samegraph(b,a):
graph.append(b)
klasses.remove(b)
yield graph
def graphgen(classes):
"""Yields inheritance graphs."""
i = 1; singles = [] # single classes
for graph in graphs(classes):
if len(graph) > 1: # it's a hierarchy
name = '"Diagram #%s"' % i; i += 1
yield '\n'.join(remove_dupl(codegen(name, graph)))
else: # it's a single class (may derive from a built-in)
singles.append(graph[0])
if singles:
name = '"Diagram #%s"' % i
yield '\n'.join(remove_dupl(codegen(name, singles)))
if __name__ == '__main__':
import sys
try:
package = sys.argv[1]
except:
sys.exit(__doc__)
classes = list(getclasses(package))
for code in graphgen(classes):
#os.popen('dot -Tps -o /tmp/x.ps; evince /tmp/x.ps','w').write(code)
os.popen('dot -Tpng -o /tmp/x.png; firefox /tmp/x.png','w').write(code)
print code
|