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