OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2004-2010 LOGILAB S.A. (Paris, FRANCE). |
| 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| 3 # |
| 4 # This program is free software; you can redistribute it and/or modify it under |
| 5 # the terms of the GNU General Public License as published by the Free Software |
| 6 # Foundation; either version 2 of the License, or (at your option) any later |
| 7 # version. |
| 8 # |
| 9 # This program is distributed in the hope that it will be useful, but WITHOUT |
| 10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 12 # |
| 13 # You should have received a copy of the GNU General Public License along with |
| 14 # this program; if not, write to the Free Software Foundation, Inc., |
| 15 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 16 """diagram objects |
| 17 """ |
| 18 |
| 19 from logilab import astng |
| 20 from pylint.pyreverse.utils import is_interface, FilterMixIn |
| 21 |
| 22 def set_counter(value): |
| 23 """Figure counter (re)set""" |
| 24 Figure._UID_COUNT = value |
| 25 |
| 26 class Figure: |
| 27 """base class for counter handling""" |
| 28 _UID_COUNT = 0 |
| 29 def __init__(self): |
| 30 Figure._UID_COUNT += 1 |
| 31 self.fig_id = Figure._UID_COUNT |
| 32 |
| 33 class Relationship(Figure): |
| 34 """a relation ship from an object in the diagram to another |
| 35 """ |
| 36 def __init__(self, from_object, to_object, relation_type, name=None): |
| 37 Figure.__init__(self) |
| 38 self.from_object = from_object |
| 39 self.to_object = to_object |
| 40 self.type = relation_type |
| 41 self.name = name |
| 42 |
| 43 |
| 44 class DiagramEntity(Figure): |
| 45 """a diagram object, i.e. a label associated to an astng node |
| 46 """ |
| 47 def __init__(self, title='No name', node=None): |
| 48 Figure.__init__(self) |
| 49 self.title = title |
| 50 self.node = node |
| 51 |
| 52 class ClassDiagram(Figure, FilterMixIn): |
| 53 """main class diagram handling |
| 54 """ |
| 55 TYPE = 'class' |
| 56 def __init__(self, title, mode): |
| 57 FilterMixIn.__init__(self, mode) |
| 58 Figure.__init__(self) |
| 59 self.title = title |
| 60 self.objects = [] |
| 61 self.relationships = {} |
| 62 self._nodes = {} |
| 63 self.depends = [] |
| 64 |
| 65 def add_relationship(self, from_object, to_object, |
| 66 relation_type, name=None): |
| 67 """create a relation ship |
| 68 """ |
| 69 rel = Relationship(from_object, to_object, relation_type, name) |
| 70 self.relationships.setdefault(relation_type, []).append(rel) |
| 71 |
| 72 def get_relationship(self, from_object, relation_type): |
| 73 """return a relation ship or None |
| 74 """ |
| 75 for rel in self.relationships.get(relation_type, ()): |
| 76 if rel.from_object is from_object: |
| 77 return rel |
| 78 raise KeyError(relation_type) |
| 79 |
| 80 def get_attrs(self, node): |
| 81 """return visible attributes, possibly with class name""" |
| 82 attrs = [] |
| 83 for node_name, ass_nodes in node.instance_attrs_type.items() + \ |
| 84 node.locals_type.items(): |
| 85 if not self.show_attr(node_name): |
| 86 continue |
| 87 names = self.class_names(ass_nodes) |
| 88 if names: |
| 89 node_name = "%s : %s" % (node_name, ", ".join(names)) |
| 90 attrs.append(node_name) |
| 91 return attrs |
| 92 |
| 93 def get_methods(self, node): |
| 94 """return visible methods""" |
| 95 return [m for m in node.values() |
| 96 if isinstance(m, astng.Function) and self.show_attr(m.name)] |
| 97 |
| 98 def add_object(self, title, node): |
| 99 """create a diagram object |
| 100 """ |
| 101 assert node not in self._nodes |
| 102 ent = DiagramEntity(title, node) |
| 103 self._nodes[node] = ent |
| 104 self.objects.append(ent) |
| 105 |
| 106 def class_names(self, nodes): |
| 107 """return class names if needed in diagram""" |
| 108 names = [] |
| 109 for ass_node in nodes: |
| 110 if isinstance(ass_node, astng.Instance): |
| 111 ass_node = ass_node._proxied |
| 112 if isinstance(ass_node, astng.Class) \ |
| 113 and hasattr(ass_node, "name") and not self.has_node(ass_node): |
| 114 if ass_node.name not in names: |
| 115 ass_name = ass_node.name |
| 116 names.append(ass_name) |
| 117 return names |
| 118 |
| 119 def nodes(self): |
| 120 """return the list of underlying nodes |
| 121 """ |
| 122 return self._nodes.keys() |
| 123 |
| 124 def has_node(self, node): |
| 125 """return true if the given node is included in the diagram |
| 126 """ |
| 127 return node in self._nodes |
| 128 |
| 129 def object_from_node(self, node): |
| 130 """return the diagram object mapped to node |
| 131 """ |
| 132 return self._nodes[node] |
| 133 |
| 134 def classes(self): |
| 135 """return all class nodes in the diagram""" |
| 136 return [o for o in self.objects if isinstance(o.node, astng.Class)] |
| 137 |
| 138 def classe(self, name): |
| 139 """return a class by its name, raise KeyError if not found |
| 140 """ |
| 141 for klass in self.classes(): |
| 142 if klass.node.name == name: |
| 143 return klass |
| 144 raise KeyError(name) |
| 145 |
| 146 def extract_relationships(self): |
| 147 """extract relation ships between nodes in the diagram |
| 148 """ |
| 149 for obj in self.classes(): |
| 150 node = obj.node |
| 151 obj.attrs = self.get_attrs(node) |
| 152 obj.methods = self.get_methods(node) |
| 153 # shape |
| 154 if is_interface(node): |
| 155 obj.shape = 'interface' |
| 156 else: |
| 157 obj.shape = 'class' |
| 158 # inheritance link |
| 159 for par_node in node.ancestors(recurs=False): |
| 160 try: |
| 161 par_obj = self.object_from_node(par_node) |
| 162 self.add_relationship(obj, par_obj, 'specialization') |
| 163 except KeyError: |
| 164 continue |
| 165 # implements link |
| 166 for impl_node in node.implements: |
| 167 try: |
| 168 impl_obj = self.object_from_node(impl_node) |
| 169 self.add_relationship(obj, impl_obj, 'implements') |
| 170 except KeyError: |
| 171 continue |
| 172 # associations link |
| 173 for name, values in node.instance_attrs_type.items() + \ |
| 174 node.locals_type.items(): |
| 175 for value in values: |
| 176 if value is astng.YES: |
| 177 continue |
| 178 if isinstance( value, astng.Instance): |
| 179 value = value._proxied |
| 180 try: |
| 181 ass_obj = self.object_from_node(value) |
| 182 self.add_relationship(ass_obj, obj, 'association', name) |
| 183 except KeyError: |
| 184 continue |
| 185 |
| 186 |
| 187 class PackageDiagram(ClassDiagram): |
| 188 """package diagram handling |
| 189 """ |
| 190 TYPE = 'package' |
| 191 |
| 192 def modules(self): |
| 193 """return all module nodes in the diagram""" |
| 194 return [o for o in self.objects if isinstance(o.node, astng.Module)] |
| 195 |
| 196 def module(self, name): |
| 197 """return a module by its name, raise KeyError if not found |
| 198 """ |
| 199 for mod in self.modules(): |
| 200 if mod.node.name == name: |
| 201 return mod |
| 202 raise KeyError(name) |
| 203 |
| 204 def get_module(self, name, node): |
| 205 """return a module by its name, looking also for relative imports; |
| 206 raise KeyError if not found |
| 207 """ |
| 208 for mod in self.modules(): |
| 209 mod_name = mod.node.name |
| 210 if mod_name == name: |
| 211 return mod |
| 212 #search for fullname of relative import modules |
| 213 package = node.root().name |
| 214 if mod_name == "%s.%s" % (package, name): |
| 215 return mod |
| 216 if mod_name == "%s.%s" % (package.rsplit('.', 1)[0], name): |
| 217 return mod |
| 218 raise KeyError(name) |
| 219 |
| 220 def add_from_depend(self, node, from_module): |
| 221 """add dependencies created by from-imports |
| 222 """ |
| 223 mod_name = node.root().name |
| 224 obj = self.module( mod_name ) |
| 225 if from_module not in obj.node.depends: |
| 226 obj.node.depends.append(from_module) |
| 227 |
| 228 def extract_relationships(self): |
| 229 """extract relation ships between nodes in the diagram |
| 230 """ |
| 231 ClassDiagram.extract_relationships(self) |
| 232 for obj in self.classes(): |
| 233 # ownership |
| 234 try: |
| 235 mod = self.object_from_node(obj.node.root()) |
| 236 self.add_relationship(obj, mod, 'ownership') |
| 237 except KeyError: |
| 238 continue |
| 239 for obj in self.modules(): |
| 240 obj.shape = 'package' |
| 241 # dependencies |
| 242 for dep_name in obj.node.depends: |
| 243 try: |
| 244 dep = self.get_module(dep_name, obj.node) |
| 245 except KeyError: |
| 246 continue |
| 247 self.add_relationship(obj, dep, 'depends') |
OLD | NEW |