diff options
author | Ionel Cristian Maries <contact@ionelmc.ro> | 2015-02-14 18:13:20 +0200 |
---|---|---|
committer | Ionel Cristian Maries <contact@ionelmc.ro> | 2015-02-14 18:13:20 +0200 |
commit | 26eb7f9a2e6cc6c229c6a86b6c6bb823e8f5de57 (patch) | |
tree | 4a3f9776764d7c19e21bb6bd00ca0b2164bed4d0 /pylint/pyreverse/diagrams.py | |
parent | d5d2d14e03a22430c5feba1789fd62c8156755f2 (diff) | |
download | pylint-git-26eb7f9a2e6cc6c229c6a86b6c6bb823e8f5de57.tar.gz |
Move all package files to a pylint package.
Diffstat (limited to 'pylint/pyreverse/diagrams.py')
-rw-r--r-- | pylint/pyreverse/diagrams.py | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py new file mode 100644 index 000000000..f0d7a92c6 --- /dev/null +++ b/pylint/pyreverse/diagrams.py @@ -0,0 +1,247 @@ +# Copyright (c) 2004-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. +"""diagram objects +""" + +import astroid +from pylint.pyreverse.utils import is_interface, FilterMixIn + +class Figure(object): + """base class for counter handling""" + +class Relationship(Figure): + """a relation ship from an object in the diagram to another + """ + def __init__(self, from_object, to_object, relation_type, name=None): + Figure.__init__(self) + self.from_object = from_object + self.to_object = to_object + self.type = relation_type + self.name = name + + +class DiagramEntity(Figure): + """a diagram object, i.e. a label associated to an astroid node + """ + def __init__(self, title='No name', node=None): + Figure.__init__(self) + self.title = title + self.node = node + +class ClassDiagram(Figure, FilterMixIn): + """main class diagram handling + """ + TYPE = 'class' + def __init__(self, title, mode): + FilterMixIn.__init__(self, mode) + Figure.__init__(self) + self.title = title + self.objects = [] + self.relationships = {} + self._nodes = {} + self.depends = [] + + def get_relationships(self, role): + # sorted to get predictable (hence testable) results + return sorted(self.relationships.get(role, ()), + key=lambda x: (x.from_object.fig_id, x.to_object.fig_id)) + + def add_relationship(self, from_object, to_object, + relation_type, name=None): + """create a relation ship + """ + rel = Relationship(from_object, to_object, relation_type, name) + self.relationships.setdefault(relation_type, []).append(rel) + + def get_relationship(self, from_object, relation_type): + """return a relation ship or None + """ + for rel in self.relationships.get(relation_type, ()): + if rel.from_object is from_object: + return rel + raise KeyError(relation_type) + + def get_attrs(self, node): + """return visible attributes, possibly with class name""" + attrs = [] + for node_name, ass_nodes in list(node.instance_attrs_type.items()) + \ + list(node.locals_type.items()): + if not self.show_attr(node_name): + continue + names = self.class_names(ass_nodes) + if names: + node_name = "%s : %s" % (node_name, ", ".join(names)) + attrs.append(node_name) + return sorted(attrs) + + def get_methods(self, node): + """return visible methods""" + methods = [ + m for m in node.values() + if isinstance(m, astroid.Function) and self.show_attr(m.name) + ] + return sorted(methods, key=lambda n: n.name) + + def add_object(self, title, node): + """create a diagram object + """ + assert node not in self._nodes + ent = DiagramEntity(title, node) + self._nodes[node] = ent + self.objects.append(ent) + + def class_names(self, nodes): + """return class names if needed in diagram""" + names = [] + for ass_node in nodes: + if isinstance(ass_node, astroid.Instance): + ass_node = ass_node._proxied + if isinstance(ass_node, astroid.Class) \ + and hasattr(ass_node, "name") and not self.has_node(ass_node): + if ass_node.name not in names: + ass_name = ass_node.name + names.append(ass_name) + return names + + def nodes(self): + """return the list of underlying nodes + """ + return self._nodes.keys() + + def has_node(self, node): + """return true if the given node is included in the diagram + """ + return node in self._nodes + + def object_from_node(self, node): + """return the diagram object mapped to node + """ + return self._nodes[node] + + def classes(self): + """return all class nodes in the diagram""" + return [o for o in self.objects if isinstance(o.node, astroid.Class)] + + def classe(self, name): + """return a class by its name, raise KeyError if not found + """ + for klass in self.classes(): + if klass.node.name == name: + return klass + raise KeyError(name) + + def extract_relationships(self): + """extract relation ships between nodes in the diagram + """ + for obj in self.classes(): + node = obj.node + obj.attrs = self.get_attrs(node) + obj.methods = self.get_methods(node) + # shape + if is_interface(node): + obj.shape = 'interface' + else: + obj.shape = 'class' + # inheritance link + for par_node in node.ancestors(recurs=False): + try: + par_obj = self.object_from_node(par_node) + self.add_relationship(obj, par_obj, 'specialization') + except KeyError: + continue + # implements link + for impl_node in node.implements: + try: + impl_obj = self.object_from_node(impl_node) + self.add_relationship(obj, impl_obj, 'implements') + except KeyError: + continue + # associations link + for name, values in list(node.instance_attrs_type.items()) + \ + list(node.locals_type.items()): + for value in values: + if value is astroid.YES: + continue + if isinstance(value, astroid.Instance): + value = value._proxied + try: + ass_obj = self.object_from_node(value) + self.add_relationship(ass_obj, obj, 'association', name) + except KeyError: + continue + + +class PackageDiagram(ClassDiagram): + """package diagram handling + """ + TYPE = 'package' + + def modules(self): + """return all module nodes in the diagram""" + return [o for o in self.objects if isinstance(o.node, astroid.Module)] + + def module(self, name): + """return a module by its name, raise KeyError if not found + """ + for mod in self.modules(): + if mod.node.name == name: + return mod + raise KeyError(name) + + def get_module(self, name, node): + """return a module by its name, looking also for relative imports; + raise KeyError if not found + """ + for mod in self.modules(): + mod_name = mod.node.name + if mod_name == name: + return mod + #search for fullname of relative import modules + package = node.root().name + if mod_name == "%s.%s" % (package, name): + return mod + if mod_name == "%s.%s" % (package.rsplit('.', 1)[0], name): + return mod + raise KeyError(name) + + def add_from_depend(self, node, from_module): + """add dependencies created by from-imports + """ + mod_name = node.root().name + obj = self.module(mod_name) + if from_module not in obj.node.depends: + obj.node.depends.append(from_module) + + def extract_relationships(self): + """extract relation ships between nodes in the diagram + """ + ClassDiagram.extract_relationships(self) + for obj in self.classes(): + # ownership + try: + mod = self.object_from_node(obj.node.root()) + self.add_relationship(obj, mod, 'ownership') + except KeyError: + continue + for obj in self.modules(): + obj.shape = 'package' + # dependencies + for dep_name in obj.node.depends: + try: + dep = self.get_module(dep_name, obj.node) + except KeyError: + continue + self.add_relationship(obj, dep, 'depends') |