OLD | NEW |
1 # Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2004-2010 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 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 15 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
16 """diagram objects | 16 """diagram objects |
17 """ | 17 """ |
18 | 18 |
19 import astroid | 19 from logilab import astng |
20 from pylint.pyreverse.utils import is_interface, FilterMixIn | 20 from pylint.pyreverse.utils import is_interface, FilterMixIn |
21 | 21 |
22 class Figure(object): | 22 def set_counter(value): |
| 23 """Figure counter (re)set""" |
| 24 Figure._UID_COUNT = value |
| 25 |
| 26 class Figure: |
23 """base class for counter handling""" | 27 """base class for counter handling""" |
24 | 28 _UID_COUNT = 0 |
| 29 def __init__(self): |
| 30 Figure._UID_COUNT += 1 |
| 31 self.fig_id = Figure._UID_COUNT |
| 32 |
25 class Relationship(Figure): | 33 class Relationship(Figure): |
26 """a relation ship from an object in the diagram to another | 34 """a relation ship from an object in the diagram to another |
27 """ | 35 """ |
28 def __init__(self, from_object, to_object, relation_type, name=None): | 36 def __init__(self, from_object, to_object, relation_type, name=None): |
29 Figure.__init__(self) | 37 Figure.__init__(self) |
30 self.from_object = from_object | 38 self.from_object = from_object |
31 self.to_object = to_object | 39 self.to_object = to_object |
32 self.type = relation_type | 40 self.type = relation_type |
33 self.name = name | 41 self.name = name |
34 | 42 |
35 | 43 |
36 class DiagramEntity(Figure): | 44 class DiagramEntity(Figure): |
37 """a diagram object, i.e. a label associated to an astroid node | 45 """a diagram object, i.e. a label associated to an astng node |
38 """ | 46 """ |
39 def __init__(self, title='No name', node=None): | 47 def __init__(self, title='No name', node=None): |
40 Figure.__init__(self) | 48 Figure.__init__(self) |
41 self.title = title | 49 self.title = title |
42 self.node = node | 50 self.node = node |
43 | 51 |
44 class ClassDiagram(Figure, FilterMixIn): | 52 class ClassDiagram(Figure, FilterMixIn): |
45 """main class diagram handling | 53 """main class diagram handling |
46 """ | 54 """ |
47 TYPE = 'class' | 55 TYPE = 'class' |
48 def __init__(self, title, mode): | 56 def __init__(self, title, mode): |
49 FilterMixIn.__init__(self, mode) | 57 FilterMixIn.__init__(self, mode) |
50 Figure.__init__(self) | 58 Figure.__init__(self) |
51 self.title = title | 59 self.title = title |
52 self.objects = [] | 60 self.objects = [] |
53 self.relationships = {} | 61 self.relationships = {} |
54 self._nodes = {} | 62 self._nodes = {} |
55 self.depends = [] | 63 self.depends = [] |
56 | 64 |
57 def get_relationships(self, role): | 65 def add_relationship(self, from_object, to_object, |
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, | |
63 relation_type, name=None): | 66 relation_type, name=None): |
64 """create a relation ship | 67 """create a relation ship |
65 """ | 68 """ |
66 rel = Relationship(from_object, to_object, relation_type, name) | 69 rel = Relationship(from_object, to_object, relation_type, name) |
67 self.relationships.setdefault(relation_type, []).append(rel) | 70 self.relationships.setdefault(relation_type, []).append(rel) |
68 | 71 |
69 def get_relationship(self, from_object, relation_type): | 72 def get_relationship(self, from_object, relation_type): |
70 """return a relation ship or None | 73 """return a relation ship or None |
71 """ | 74 """ |
72 for rel in self.relationships.get(relation_type, ()): | 75 for rel in self.relationships.get(relation_type, ()): |
73 if rel.from_object is from_object: | 76 if rel.from_object is from_object: |
74 return rel | 77 return rel |
75 raise KeyError(relation_type) | 78 raise KeyError(relation_type) |
76 | 79 |
77 def get_attrs(self, node): | 80 def get_attrs(self, node): |
78 """return visible attributes, possibly with class name""" | 81 """return visible attributes, possibly with class name""" |
79 attrs = [] | 82 attrs = [] |
80 for node_name, ass_nodes in node.instance_attrs_type.items() + \ | 83 for node_name, ass_nodes in node.instance_attrs_type.items() + \ |
81 node.locals_type.items(): | 84 node.locals_type.items(): |
82 if not self.show_attr(node_name): | 85 if not self.show_attr(node_name): |
83 continue | 86 continue |
84 names = self.class_names(ass_nodes) | 87 names = self.class_names(ass_nodes) |
85 if names: | 88 if names: |
86 node_name = "%s : %s" % (node_name, ", ".join(names)) | 89 node_name = "%s : %s" % (node_name, ", ".join(names)) |
87 attrs.append(node_name) | 90 attrs.append(node_name) |
88 return sorted(attrs) | 91 return attrs |
89 | 92 |
90 def get_methods(self, node): | 93 def get_methods(self, node): |
91 """return visible methods""" | 94 """return visible methods""" |
92 methods = [ | 95 return [m for m in node.values() |
93 m for m in node.values() | 96 if isinstance(m, astng.Function) and self.show_attr(m.name)] |
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, astroid.Instance): | 110 if isinstance(ass_node, astng.Instance): |
111 ass_node = ass_node._proxied | 111 ass_node = ass_node._proxied |
112 if isinstance(ass_node, astroid.Class) \ | 112 if isinstance(ass_node, astng.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, astroid.Class)] | 136 return [o for o in self.objects if isinstance(o.node, astng.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 astroid.YES: | 176 if value is astng.YES: |
177 continue | 177 continue |
178 if isinstance(value, astroid.Instance): | 178 if isinstance( value, astng.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, astroid.Module)] | 194 return [o for o in self.objects if isinstance(o.node, astng.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 |