Index: third_party/pylint/pyreverse/diagrams.py |
diff --git a/third_party/pylint/pyreverse/diagrams.py b/third_party/pylint/pyreverse/diagrams.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..23d23eff8bdaf32a6c7b2e38f6ff74051da95674 |
--- /dev/null |
+++ b/third_party/pylint/pyreverse/diagrams.py |
@@ -0,0 +1,247 @@ |
+# Copyright (c) 2004-2010 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., |
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
+"""diagram objects |
+""" |
+ |
+from logilab import astng |
+from pylint.pyreverse.utils import is_interface, FilterMixIn |
+ |
+def set_counter(value): |
+ """Figure counter (re)set""" |
+ Figure._UID_COUNT = value |
+ |
+class Figure: |
+ """base class for counter handling""" |
+ _UID_COUNT = 0 |
+ def __init__(self): |
+ Figure._UID_COUNT += 1 |
+ self.fig_id = Figure._UID_COUNT |
+ |
+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 astng 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 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 node.instance_attrs_type.items() + \ |
+ 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 attrs |
+ |
+ def get_methods(self, node): |
+ """return visible methods""" |
+ return [m for m in node.values() |
+ if isinstance(m, astng.Function) and self.show_attr(m.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, astng.Instance): |
+ ass_node = ass_node._proxied |
+ if isinstance(ass_node, astng.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, astng.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 node.instance_attrs_type.items() + \ |
+ node.locals_type.items(): |
+ for value in values: |
+ if value is astng.YES: |
+ continue |
+ if isinstance( value, astng.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, astng.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') |