chromium/third_party/pylint/pylint/pyreverse/writer.py

# -*- coding: utf-8 -*-
# Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:[email protected]
#
# 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()