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