OLD | NEW |
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 # Copyright (c) 2008-2010 LOGILAB S.A. (Paris, FRANCE). | 2 # Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE). |
3 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 3 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
4 # | 4 # |
5 # This program is free software; you can redistribute it and/or modify it under | 5 # This program is free software; you can redistribute it and/or modify it under |
6 # the terms of the GNU General Public License as published by the Free Software | 6 # the terms of the GNU General Public License as published by the Free Software |
7 # Foundation; either version 2 of the License, or (at your option) any later | 7 # Foundation; either version 2 of the License, or (at your option) any later |
8 # version. | 8 # version. |
9 # | 9 # |
10 # This program is distributed in the hope that it will be useful, but WITHOUT | 10 # This program is distributed in the hope that it will be useful, but WITHOUT |
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | 12 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
13 # | 13 # |
14 # You should have received a copy of the GNU General Public License along with | 14 # You should have received a copy of the GNU General Public License along with |
15 # this program; if not, write to the Free Software Foundation, Inc., | 15 # this program; if not, write to the Free Software Foundation, Inc., |
16 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | 16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 """ | 17 """Utilities for creating VCG and Dot diagrams""" |
18 Utilities for creating VCG and Dot diagrams. | |
19 """ | |
20 | 18 |
21 from logilab.common.vcgutils import VCGPrinter | 19 from logilab.common.vcgutils import VCGPrinter |
22 from logilab.common.graph import DotBackend | 20 from logilab.common.graph import DotBackend |
23 | 21 |
24 from pylint.pyreverse.utils import is_exception | 22 from pylint.pyreverse.utils import is_exception |
25 | 23 |
26 class DiagramWriter: | 24 class DiagramWriter(object): |
27 """base class for writing project diagrams | 25 """base class for writing project diagrams |
28 """ | 26 """ |
29 def __init__(self, config, styles): | 27 def __init__(self, config, styles): |
30 self.config = config | 28 self.config = config |
31 self.pkg_edges, self.inh_edges, self.imp_edges, self.ass_edges = styles | 29 self.pkg_edges, self.inh_edges, self.imp_edges, self.ass_edges = styles |
32 self.printer = None # defined in set_printer | 30 self.printer = None # defined in set_printer |
33 | 31 |
34 def write(self, diadefs): | 32 def write(self, diadefs): |
35 """write files for <project> according to <diadefs> | 33 """write files for <project> according to <diadefs> |
36 """ | 34 """ |
37 for diagram in diadefs: | 35 for diagram in diadefs: |
38 basename = diagram.title.strip().replace(' ', '_') | 36 basename = diagram.title.strip().replace(' ', '_') |
39 file_name = '%s.%s' % (basename, self.config.output_format) | 37 file_name = '%s.%s' % (basename, self.config.output_format) |
40 self.set_printer(file_name, basename) | 38 self.set_printer(file_name, basename) |
41 if diagram.TYPE == 'class': | 39 if diagram.TYPE == 'class': |
42 self.write_classes(diagram) | 40 self.write_classes(diagram) |
43 else: | 41 else: |
44 self.write_packages(diagram) | 42 self.write_packages(diagram) |
45 self.close_graph() | 43 self.close_graph() |
46 | 44 |
47 def write_packages(self, diagram): | 45 def write_packages(self, diagram): |
48 """write a package diagram""" | 46 """write a package diagram""" |
49 for obj in diagram.modules(): | 47 # sorted to get predictable (hence testable) results |
50 label = self.get_title(obj) | 48 for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)
): |
51 self.printer.emit_node(obj.fig_id, label=label, shape='box') | 49 self.printer.emit_node(i, label=self.get_title(obj), shape='box') |
| 50 obj.fig_id = i |
52 # package dependencies | 51 # package dependencies |
53 for rel in diagram.relationships.get('depends', ()): | 52 for rel in diagram.get_relationships('depends'): |
54 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | 53 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, |
55 **self.pkg_edges) | 54 **self.pkg_edges) |
56 | 55 |
57 def write_classes(self, diagram): | 56 def write_classes(self, diagram): |
58 """write a class diagram""" | 57 """write a class diagram""" |
59 for obj in diagram.objects: | 58 # sorted to get predictable (hence testable) results |
60 self.printer.emit_node(obj.fig_id, **self.get_values(obj) ) | 59 for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)): |
| 60 self.printer.emit_node(i, **self.get_values(obj)) |
| 61 obj.fig_id = i |
61 # inheritance links | 62 # inheritance links |
62 for rel in diagram.relationships.get('specialization', ()): | 63 for rel in diagram.get_relationships('specialization'): |
63 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | 64 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, |
64 **self.inh_edges) | 65 **self.inh_edges) |
65 # implementation links | 66 # implementation links |
66 for rel in diagram.relationships.get('implements', ()): | 67 for rel in diagram.get_relationships('implements'): |
67 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | 68 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, |
68 **self.imp_edges) | 69 **self.imp_edges) |
69 # generate associations | 70 # generate associations |
70 for rel in diagram.relationships.get('association', ()): | 71 for rel in diagram.get_relationships('association'): |
71 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | 72 self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, |
72 label=rel.name, **self.ass_edges) | 73 label=rel.name, **self.ass_edges) |
73 | 74 |
74 def set_printer(self, file_name, basename): | 75 def set_printer(self, file_name, basename): |
75 """set printer""" | 76 """set printer""" |
76 raise NotImplementedError | 77 raise NotImplementedError |
77 | 78 |
78 def get_title(self, obj): | 79 def get_title(self, obj): |
79 """get project title""" | 80 """get project title""" |
80 raise NotImplementedError | 81 raise NotImplementedError |
81 | 82 |
82 def get_values(self, obj): | 83 def get_values(self, obj): |
83 """get label and shape for classes.""" | 84 """get label and shape for classes.""" |
84 raise NotImplementedError | 85 raise NotImplementedError |
85 | 86 |
86 def close_graph(self): | 87 def close_graph(self): |
87 """finalize the graph""" | 88 """finalize the graph""" |
88 raise NotImplementedError | 89 raise NotImplementedError |
89 | 90 |
90 | 91 |
91 class DotWriter(DiagramWriter): | 92 class DotWriter(DiagramWriter): |
92 """write dot graphs from a diagram definition and a project | 93 """write dot graphs from a diagram definition and a project |
93 """ | 94 """ |
94 | 95 |
95 def __init__(self, config): | 96 def __init__(self, config): |
96 styles = [dict(arrowtail='none', arrowhead="open"), | 97 styles = [dict(arrowtail='none', arrowhead="open"), |
97 dict(arrowtail = "none", arrowhead='empty'), | 98 dict(arrowtail='none', arrowhead='empty'), |
98 dict(arrowtail="node", arrowhead='empty', style='dashed'), | 99 dict(arrowtail='node', arrowhead='empty', style='dashed'), |
99 dict(fontcolor='green', arrowtail='none', | 100 dict(fontcolor='green', arrowtail='none', |
100 arrowhead='diamond', style='solid') ] | 101 arrowhead='diamond', style='solid'), |
| 102 ] |
101 DiagramWriter.__init__(self, config, styles) | 103 DiagramWriter.__init__(self, config, styles) |
102 | 104 |
103 def set_printer(self, file_name, basename): | 105 def set_printer(self, file_name, basename): |
104 """initialize DotWriter and add options for layout. | 106 """initialize DotWriter and add options for layout. |
105 """ | 107 """ |
106 layout = dict(rankdir="BT") | 108 layout = dict(rankdir="BT") |
107 self.printer = DotBackend(basename, additionnal_param=layout) | 109 self.printer = DotBackend(basename, additionnal_param=layout) |
108 self.file_name = file_name | 110 self.file_name = file_name |
109 | 111 |
110 def get_title(self, obj): | 112 def get_title(self, obj): |
111 """get project title""" | 113 """get project title""" |
112 return obj.title | 114 return obj.title |
113 | 115 |
114 def get_values(self, obj): | 116 def get_values(self, obj): |
115 """get label and shape for classes. | 117 """get label and shape for classes. |
116 | 118 |
117 The label contains all attributes and methods | 119 The label contains all attributes and methods |
118 """ | 120 """ |
119 label = obj.title | 121 label = obj.title |
120 if obj.shape == 'interface': | 122 if obj.shape == 'interface': |
121 label = "«interface»\\n%s" % label | 123 label = u'«interface»\\n%s' % label |
122 if not self.config.only_classnames: | 124 if not self.config.only_classnames: |
123 label = "%s|%s\l|" % (label, r"\l".join(obj.attrs) ) | 125 label = r'%s|%s\l|' % (label, r'\l'.join(obj.attrs)) |
124 for func in obj.methods: | 126 for func in obj.methods: |
125 label = r'%s%s()\l' % (label, func.name) | 127 label = r'%s%s()\l' % (label, func.name) |
126 label = '{%s}' % label | 128 label = '{%s}' % label |
127 if is_exception(obj.node): | 129 if is_exception(obj.node): |
128 return dict(fontcolor="red", label=label, shape="record") | 130 return dict(fontcolor='red', label=label, shape='record') |
129 return dict(label=label, shape="record") | 131 return dict(label=label, shape='record') |
130 | 132 |
131 def close_graph(self): | 133 def close_graph(self): |
132 """print the dot graph into <file_name>""" | 134 """print the dot graph into <file_name>""" |
133 self.printer.generate(self.file_name) | 135 self.printer.generate(self.file_name) |
134 | 136 |
135 | 137 |
136 class VCGWriter(DiagramWriter): | 138 class VCGWriter(DiagramWriter): |
137 """write vcg graphs from a diagram definition and a project | 139 """write vcg graphs from a diagram definition and a project |
138 """ | 140 """ |
139 def __init__(self, config): | 141 def __init__(self, config): |
140 styles = [dict(arrowstyle='solid', backarrowstyle='none', | 142 styles = [dict(arrowstyle='solid', backarrowstyle='none', |
141 backarrowsize=0), | 143 backarrowsize=0), |
142 dict(arrowstyle='solid', backarrowstyle='none', | 144 dict(arrowstyle='solid', backarrowstyle='none', |
143 backarrowsize=10), | 145 backarrowsize=10), |
144 dict(arrowstyle='solid', backarrowstyle='none', | 146 dict(arrowstyle='solid', backarrowstyle='none', |
145 linestyle='dotted', backarrowsize=10), | 147 linestyle='dotted', backarrowsize=10), |
146 dict(arrowstyle='solid', backarrowstyle='none', | 148 dict(arrowstyle='solid', backarrowstyle='none', |
147 textcolor='green') ] | 149 textcolor='green'), |
| 150 ] |
148 DiagramWriter.__init__(self, config, styles) | 151 DiagramWriter.__init__(self, config, styles) |
149 | 152 |
150 def set_printer(self, file_name, basename): | 153 def set_printer(self, file_name, basename): |
151 """initialize VCGWriter for a UML graph""" | 154 """initialize VCGWriter for a UML graph""" |
152 self.graph_file = open(file_name, 'w+') | 155 self.graph_file = open(file_name, 'w+') |
153 self.printer = VCGPrinter(self.graph_file) | 156 self.printer = VCGPrinter(self.graph_file) |
154 self.printer.open_graph(title=basename, layoutalgorithm='dfs', | 157 self.printer.open_graph(title=basename, layoutalgorithm='dfs', |
155 late_edge_labels='yes', port_sharing='no', | 158 late_edge_labels='yes', port_sharing='no', |
156 manhattan_edges='yes') | 159 manhattan_edges='yes') |
157 self.printer.emit_node = self.printer.node | 160 self.printer.emit_node = self.printer.node |
158 self.printer.emit_edge = self.printer.edge | 161 self.printer.emit_edge = self.printer.edge |
159 | 162 |
160 def get_title(self, obj): | 163 def get_title(self, obj): |
161 """get project title in vcg format""" | 164 """get project title in vcg format""" |
162 return r'\fb%s\fn' % obj.title | 165 return r'\fb%s\fn' % obj.title |
163 | 166 |
164 def get_values(self, obj): | 167 def get_values(self, obj): |
165 """get label and shape for classes. | 168 """get label and shape for classes. |
166 | 169 |
167 The label contains all attributes and methods | 170 The label contains all attributes and methods |
168 """ | 171 """ |
169 if is_exception(obj.node): | 172 if is_exception(obj.node): |
170 label = r'\fb\f09%s\fn' % obj.title | 173 label = r'\fb\f09%s\fn' % obj.title |
171 else: | 174 else: |
172 label = r'\fb%s\fn' % obj.title | 175 label = r'\fb%s\fn' % obj.title |
173 if obj.shape == 'interface': | 176 if obj.shape == 'interface': |
174 shape = 'ellipse' | 177 shape = 'ellipse' |
175 else: | 178 else: |
176 shape = 'box' | 179 shape = 'box' |
177 if not self.config.only_classnames: | 180 if not self.config.only_classnames: |
178 attrs = obj.attrs | 181 attrs = obj.attrs |
179 methods = [func.name for func in obj.methods] | 182 methods = [func.name for func in obj.methods] |
180 # box width for UML like diagram | 183 # box width for UML like diagram |
181 maxlen = max(len(name) for name in [obj.title] + methods + attrs) | 184 maxlen = max(len(name) for name in [obj.title] + methods + attrs) |
182 line = "_" * (maxlen + 2) | 185 line = '_' * (maxlen + 2) |
183 label = r'%s\n\f%s' % (label, line) | 186 label = r'%s\n\f%s' % (label, line) |
184 for attr in attrs: | 187 for attr in attrs: |
185 label = r'%s\n\f08%s' % (label, attr) | 188 label = r'%s\n\f08%s' % (label, attr) |
186 if attrs: | 189 if attrs: |
187 label = r'%s\n\f%s' % (label, line) | 190 label = r'%s\n\f%s' % (label, line) |
188 for func in methods: | 191 for func in methods: |
189 label = r'%s\n\f10%s()' % (label, func) | 192 label = r'%s\n\f10%s()' % (label, func) |
190 return dict(label=label, shape=shape) | 193 return dict(label=label, shape=shape) |
191 | 194 |
192 def close_graph(self): | 195 def close_graph(self): |
193 """close graph and file""" | 196 """close graph and file""" |
194 self.printer.close_graph() | 197 self.printer.close_graph() |
195 self.graph_file.close() | 198 self.graph_file.close() |
196 | 199 |
OLD | NEW |