diff options
author | Mike Crute <mcrute@gmail.com> | 2009-07-26 13:15:14 -0400 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2009-07-26 13:15:14 -0400 |
commit | ed57bb4c421bbd9c4009196217b8f1709a1474f9 (patch) | |
tree | f508aad7af0b6a1a07955b43874c7466e66cc366 | |
download | pydepgraph-ed57bb4c421bbd9c4009196217b8f1709a1474f9.tar.bz2 pydepgraph-ed57bb4c421bbd9c4009196217b8f1709a1474f9.tar.xz pydepgraph-ed57bb4c421bbd9c4009196217b8f1709a1474f9.zip |
Initial import
-rw-r--r-- | depgraph2dot.py | 196 | ||||
-rw-r--r-- | py2depgraph.py | 62 |
2 files changed, 258 insertions, 0 deletions
diff --git a/depgraph2dot.py b/depgraph2dot.py new file mode 100644 index 0000000..bbebf18 --- /dev/null +++ b/depgraph2dot.py | |||
@@ -0,0 +1,196 @@ | |||
1 | # Copyright 2004 Toby Dickenson | ||
2 | # | ||
3 | # Permission is hereby granted, free of charge, to any person obtaining | ||
4 | # a copy of this software and associated documentation files (the | ||
5 | # "Software"), to deal in the Software without restriction, including | ||
6 | # without limitation the rights to use, copy, modify, merge, publish, | ||
7 | # distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | # permit persons to whom the Software is furnished to do so, subject | ||
9 | # to the following conditions: | ||
10 | # | ||
11 | # The above copyright notice and this permission notice shall be included | ||
12 | # in all copies or substantial portions of the Software. | ||
13 | # | ||
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
17 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
18 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
19 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
20 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | |||
22 | |||
23 | import sys, getopt, colorsys, imp, md5 | ||
24 | |||
25 | class pydepgraphdot: | ||
26 | |||
27 | def main(self,argv): | ||
28 | opts,args = getopt.getopt(argv,'',['mono']) | ||
29 | self.colored = 1 | ||
30 | for o,v in opts: | ||
31 | if o=='--mono': | ||
32 | self.colored = 0 | ||
33 | self.render() | ||
34 | |||
35 | def fix(self,s): | ||
36 | # Convert a module name to a syntactically correct node name | ||
37 | return s.replace('.','_') | ||
38 | |||
39 | def render(self): | ||
40 | p,t = self.get_data() | ||
41 | |||
42 | # normalise our input data | ||
43 | for k,d in p.items(): | ||
44 | for v in d.keys(): | ||
45 | if not p.has_key(v): | ||
46 | p[v] = {} | ||
47 | |||
48 | f = self.get_output_file() | ||
49 | |||
50 | f.write('digraph G {\n') | ||
51 | #f.write('concentrate = true;\n') | ||
52 | #f.write('ordering = out;\n') | ||
53 | f.write('ranksep=1.0;\n') | ||
54 | f.write('node [style=filled,fontname=Helvetica,fontsize=10];\n') | ||
55 | allkd = p.items() | ||
56 | allkd.sort() | ||
57 | for k,d in allkd: | ||
58 | tk = t.get(k) | ||
59 | if self.use(k,tk): | ||
60 | allv = d.keys() | ||
61 | allv.sort() | ||
62 | for v in allv: | ||
63 | tv = t.get(v) | ||
64 | if self.use(v,tv) and not self.toocommon(v,tv): | ||
65 | f.write('%s -> %s' % ( self.fix(k),self.fix(v) ) ) | ||
66 | self.write_attributes(f,self.edge_attributes(k,v)) | ||
67 | f.write(';\n') | ||
68 | f.write(self.fix(k)) | ||
69 | self.write_attributes(f,self.node_attributes(k,tk)) | ||
70 | f.write(';\n') | ||
71 | f.write('}\n') | ||
72 | |||
73 | def write_attributes(self,f,a): | ||
74 | if a: | ||
75 | f.write(' [') | ||
76 | f.write(','.join(a)) | ||
77 | f.write(']') | ||
78 | |||
79 | def node_attributes(self,k,type): | ||
80 | a = [] | ||
81 | a.append('label="%s"' % self.label(k)) | ||
82 | if self.colored: | ||
83 | a.append('fillcolor="%s"' % self.color(k,type)) | ||
84 | else: | ||
85 | a.append('fillcolor=white') | ||
86 | if self.toocommon(k,type): | ||
87 | a.append('peripheries=2') | ||
88 | return a | ||
89 | |||
90 | def edge_attributes(self,k,v): | ||
91 | a = [] | ||
92 | weight = self.weight(k,v) | ||
93 | if weight!=1: | ||
94 | a.append('weight=%d' % weight) | ||
95 | length = self.alien(k,v) | ||
96 | if length: | ||
97 | a.append('minlen=%d' % length) | ||
98 | return a | ||
99 | |||
100 | def get_data(self): | ||
101 | t = eval(sys.stdin.read()) | ||
102 | return t['depgraph'],t['types'] | ||
103 | |||
104 | def get_output_file(self): | ||
105 | return sys.stdout | ||
106 | |||
107 | def use(self,s,type): | ||
108 | # Return true if this module is interesting and should be drawn. Return false | ||
109 | # if it should be completely omitted. This is a default policy - please override. | ||
110 | if s in ('os','sys','qt','time','__future__','types','re','string'): | ||
111 | # nearly all modules use all of these... more or less. They add nothing to | ||
112 | # our diagram. | ||
113 | return 0 | ||
114 | if s.startswith('encodings.'): | ||
115 | return 0 | ||
116 | if s=='__main__': | ||
117 | return 1 | ||
118 | if self.toocommon(s,type): | ||
119 | # A module where we dont want to draw references _to_. Dot doesnt handle these | ||
120 | # well, so it is probably best to not draw them at all. | ||
121 | return 0 | ||
122 | return 1 | ||
123 | |||
124 | def toocommon(self,s,type): | ||
125 | # Return true if references to this module are uninteresting. Such references | ||
126 | # do not get drawn. This is a default policy - please override. | ||
127 | # | ||
128 | if s=='__main__': | ||
129 | # references *to* __main__ are never interesting. omitting them means | ||
130 | # that main floats to the top of the page | ||
131 | return 1 | ||
132 | if type==imp.PKG_DIRECTORY: | ||
133 | # dont draw references to packages. | ||
134 | return 1 | ||
135 | return 0 | ||
136 | |||
137 | def weight(self,a,b): | ||
138 | # Return the weight of the dependency from a to b. Higher weights | ||
139 | # usually have shorter straighter edges. Return 1 if it has normal weight. | ||
140 | # A value of 4 is usually good for ensuring that a related pair of modules | ||
141 | # are drawn next to each other. This is a default policy - please override. | ||
142 | # | ||
143 | if b.split('.')[-1].startswith('_'): | ||
144 | # A module that starts with an underscore. You need a special reason to | ||
145 | # import these (for example random imports _random), so draw them close | ||
146 | # together | ||
147 | return 4 | ||
148 | return 1 | ||
149 | |||
150 | def alien(self,a,b): | ||
151 | # Return non-zero if references to this module are strange, and should be drawn | ||
152 | # extra-long. the value defines the length, in rank. This is also good for putting some | ||
153 | # vertical space between seperate subsystems. This is a default policy - please override. | ||
154 | # | ||
155 | return 0 | ||
156 | |||
157 | def label(self,s): | ||
158 | # Convert a module name to a formatted node label. This is a default policy - please override. | ||
159 | # | ||
160 | return '\\.\\n'.join(s.split('.')) | ||
161 | |||
162 | def color(self,s,type): | ||
163 | # Return the node color for this module name. This is a default policy - please override. | ||
164 | # | ||
165 | # Calculate a color systematically based on the hash of the module name. Modules in the | ||
166 | # same package have the same color. Unpackaged modules are grey | ||
167 | t = self.normalise_module_name_for_hash_coloring(s,type) | ||
168 | return self.color_from_name(t) | ||
169 | |||
170 | def normalise_module_name_for_hash_coloring(self,s,type): | ||
171 | if type==imp.PKG_DIRECTORY: | ||
172 | return s | ||
173 | else: | ||
174 | i = s.rfind('.') | ||
175 | if i<0: | ||
176 | return '' | ||
177 | else: | ||
178 | return s[:i] | ||
179 | |||
180 | def color_from_name(self,name): | ||
181 | n = md5.md5(name).digest() | ||
182 | hf = float(ord(n[0])+ord(n[1])*0xff)/0xffff | ||
183 | sf = float(ord(n[2]))/0xff | ||
184 | vf = float(ord(n[3]))/0xff | ||
185 | r,g,b = colorsys.hsv_to_rgb(hf, 0.3+0.6*sf, 0.8+0.2*vf) | ||
186 | return '#%02x%02x%02x' % (r*256,g*256,b*256) | ||
187 | |||
188 | |||
189 | def main(): | ||
190 | pydepgraphdot().main(sys.argv[1:]) | ||
191 | |||
192 | if __name__=='__main__': | ||
193 | main() | ||
194 | |||
195 | |||
196 | |||
diff --git a/py2depgraph.py b/py2depgraph.py new file mode 100644 index 0000000..5e97e06 --- /dev/null +++ b/py2depgraph.py | |||
@@ -0,0 +1,62 @@ | |||
1 | # Copyright 2004 Toby Dickenson | ||
2 | # | ||
3 | # Permission is hereby granted, free of charge, to any person obtaining | ||
4 | # a copy of this software and associated documentation files (the | ||
5 | # "Software"), to deal in the Software without restriction, including | ||
6 | # without limitation the rights to use, copy, modify, merge, publish, | ||
7 | # distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | # permit persons to whom the Software is furnished to do so, subject | ||
9 | # to the following conditions: | ||
10 | # | ||
11 | # The above copyright notice and this permission notice shall be included | ||
12 | # in all copies or substantial portions of the Software. | ||
13 | # | ||
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
17 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
18 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
19 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
20 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | |||
22 | import sys, pprint | ||
23 | import modulefinder | ||
24 | |||
25 | class mymf(modulefinder.ModuleFinder): | ||
26 | def __init__(self,*args,**kwargs): | ||
27 | self._depgraph = {} | ||
28 | self._types = {} | ||
29 | self._last_caller = None | ||
30 | modulefinder.ModuleFinder.__init__(self,*args,**kwargs) | ||
31 | |||
32 | def import_hook(self, name, caller=None, fromlist=None): | ||
33 | old_last_caller = self._last_caller | ||
34 | try: | ||
35 | self._last_caller = caller | ||
36 | return modulefinder.ModuleFinder.import_hook(self,name,caller,fromlist) | ||
37 | finally: | ||
38 | self._last_caller = old_last_caller | ||
39 | |||
40 | def import_module(self,partnam,fqname,parent): | ||
41 | r = modulefinder.ModuleFinder.import_module(self,partnam,fqname,parent) | ||
42 | if r is not None: | ||
43 | self._depgraph.setdefault(self._last_caller.__name__,{})[r.__name__] = 1 | ||
44 | return r | ||
45 | |||
46 | def load_module(self, fqname, fp, pathname, (suffix, mode, type)): | ||
47 | r = modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, (suffix, mode, type)) | ||
48 | if r is not None: | ||
49 | self._types[r.__name__] = type | ||
50 | return r | ||
51 | |||
52 | |||
53 | def main(argv): | ||
54 | path = sys.path[:] | ||
55 | debug = 0 | ||
56 | exclude = [] | ||
57 | mf = mymf(path,debug,exclude) | ||
58 | mf.run_script(argv[0]) | ||
59 | pprint.pprint({'depgraph':mf._depgraph,'types':mf._types}) | ||
60 | |||
61 | if __name__=='__main__': | ||
62 | main(sys.argv[1:]) \ No newline at end of file | ||