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