Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| # Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE). | |
| # http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
| # | |
| # This program is free software; you can redistribute it and/or modify it under | |
| # the terms of the GNU General Public License as published by the Free Software | |
| # Foundation; either version 2 of the License, or (at your option) any later | |
| # version. | |
| # | |
| # This program is distributed in the hope that it will be useful, but WITHOUT | |
| # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License along with | |
| # this program; if not, write to the Free Software Foundation, Inc., | |
| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
| """Utilities for creating VCG and Dot diagrams""" | |
| from logilab.common.vcgutils import VCGPrinter | |
| from logilab.common.graph import DotBackend | |
| from pylint.pyreverse.utils import is_exception | |
| class DiagramWriter(object): | |
| """base class for writing project diagrams | |
| """ | |
| def __init__(self, config, styles): | |
| self.config = config | |
| self.pkg_edges, self.inh_edges, self.imp_edges, self.ass_edges = styles | |
| self.printer = None # defined in set_printer | |
| def write(self, diadefs): | |
| """write files for <project> according to <diadefs> | |
| """ | |
| for diagram in diadefs: | |
| basename = diagram.title.strip().replace(' ', '_') | |
| file_name = '%s.%s' % (basename, self.config.output_format) | |
| self.set_printer(file_name, basename) | |
| if diagram.TYPE == 'class': | |
| self.write_classes(diagram) | |
| else: | |
| self.write_packages(diagram) | |
| self.close_graph() | |
| def write_packages(self, diagram): | |
| """write a package diagram""" | |
| # sorted to get predictable (hence testable) results | |
| for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)): | |
| self.printer.emit_node(i, label=self.get_title(obj), shape='box') | |
| obj.fig_id = i | |
| # package dependencies | |
| for rel in diagram.get_relationships('depends'): | |
| self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | |
| **self.pkg_edges) | |
| def write_classes(self, diagram): | |
| """write a class diagram""" | |
| # sorted to get predictable (hence testable) results | |
| for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)): | |
| self.printer.emit_node(i, **self.get_values(obj)) | |
| obj.fig_id = i | |
| # inheritance links | |
| for rel in diagram.get_relationships('specialization'): | |
| self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | |
| **self.inh_edges) | |
| # implementation links | |
| for rel in diagram.get_relationships('implements'): | |
| self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | |
| **self.imp_edges) | |
| # generate associations | |
| for rel in diagram.get_relationships('association'): | |
| self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, | |
| label=rel.name, **self.ass_edges) | |
| def set_printer(self, file_name, basename): | |
| """set printer""" | |
| raise NotImplementedError | |
| def get_title(self, obj): | |
| """get project title""" | |
| raise NotImplementedError | |
| def get_values(self, obj): | |
| """get label and shape for classes.""" | |
| raise NotImplementedError | |
| def close_graph(self): | |
| """finalize the graph""" | |
| raise NotImplementedError | |
| class DotWriter(DiagramWriter): | |
| """write dot graphs from a diagram definition and a project | |
| """ | |
| def __init__(self, config): | |
| styles = [dict(arrowtail='none', arrowhead="open"), | |
| dict(arrowtail='none', arrowhead='empty'), | |
| dict(arrowtail='node', arrowhead='empty', style='dashed'), | |
| dict(fontcolor='green', arrowtail='none', | |
| arrowhead='diamond', style='solid'), | |
| ] | |
| DiagramWriter.__init__(self, config, styles) | |
| def set_printer(self, file_name, basename): | |
| """initialize DotWriter and add options for layout. | |
| """ | |
| layout = dict(rankdir="BT") | |
| self.printer = DotBackend(basename, additionnal_param=layout) | |
| self.file_name = file_name | |
| def get_title(self, obj): | |
| """get project title""" | |
| return obj.title | |
| def get_values(self, obj): | |
| """get label and shape for classes. | |
| The label contains all attributes and methods | |
| """ | |
| label = obj.title | |
| if obj.shape == 'interface': | |
| label = u'«interface»\\n%s' % label | |
| if not self.config.only_classnames: | |
| label = r'%s|%s\l|' % (label, r'\l'.join(obj.attrs)) | |
| for func in obj.methods: | |
| label = r'%s%s()\l' % (label, func.name) | |
| label = '{%s}' % label | |
| if is_exception(obj.node): | |
| return dict(fontcolor='red', label=label, shape='record') | |
| return dict(label=label, shape='record') | |
| def close_graph(self): | |
| """print the dot graph into <file_name>""" | |
| self.printer.generate(self.file_name) | |
| class VCGWriter(DiagramWriter): | |
| """write vcg graphs from a diagram definition and a project | |
| """ | |
| def __init__(self, config): | |
| styles = [dict(arrowstyle='solid', backarrowstyle='none', | |
| backarrowsize=0), | |
| dict(arrowstyle='solid', backarrowstyle='none', | |
| backarrowsize=10), | |
| dict(arrowstyle='solid', backarrowstyle='none', | |
| linestyle='dotted', backarrowsize=10), | |
| dict(arrowstyle='solid', backarrowstyle='none', | |
| textcolor='green'), | |
| ] | |
| DiagramWriter.__init__(self, config, styles) | |
| def set_printer(self, file_name, basename): | |
| """initialize VCGWriter for a UML graph""" | |
| self.graph_file = open(file_name, 'w+') | |
| self.printer = VCGPrinter(self.graph_file) | |
| self.printer.open_graph(title=basename, layoutalgorithm='dfs', | |
| late_edge_labels='yes', port_sharing='no', | |
| manhattan_edges='yes') | |
| self.printer.emit_node = self.printer.node | |
| self.printer.emit_edge = self.printer.edge | |
| def get_title(self, obj): | |
| """get project title in vcg format""" | |
| return r'\fb%s\fn' % obj.title | |
| def get_values(self, obj): | |
| """get label and shape for classes. | |
| The label contains all attributes and methods | |
| """ | |
| if is_exception(obj.node): | |
| label = r'\fb\f09%s\fn' % obj.title | |
| else: | |
| label = r'\fb%s\fn' % obj.title | |
| if obj.shape == 'interface': | |
| shape = 'ellipse' | |
| else: | |
| shape = 'box' | |
| if not self.config.only_classnames: | |
| attrs = obj.attrs | |
| methods = [func.name for func in obj.methods] | |
| # box width for UML like diagram | |
| maxlen = max(len(name) for name in [obj.title] + methods + attrs) | |
| line = '_' * (maxlen + 2) | |
| label = r'%s\n\f%s' % (label, line) | |
| for attr in attrs: | |
| label = r'%s\n\f08%s' % (label, attr) | |
| if attrs: | |
| label = r'%s\n\f%s' % (label, line) | |
| for func in methods: | |
| label = r'%s\n\f10%s()' % (label, func) | |
| return dict(label=label, shape=shape) | |
| def close_graph(self): | |
| """close graph and file""" | |
| self.printer.close_graph() | |
| self.graph_file.close() | |