OLD | NEW |
| (Empty) |
1 # This program is free software; you can redistribute it and/or modify it under | |
2 # the terms of the GNU Lesser General Public License as published by the Free So
ftware | |
3 # Foundation; either version 2 of the License, or (at your option) any later | |
4 # version. | |
5 # | |
6 # This program is distributed in the hope that it will be useful, but WITHOUT | |
7 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
8 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more d
etails. | |
9 # | |
10 # You should have received a copy of the GNU Lesser General Public License along
with | |
11 # this program; if not, write to the Free Software Foundation, Inc., | |
12 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
13 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | |
14 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
15 # copyright 2003-2010 Sylvain Thenault, all rights reserved. | |
16 # contact mailto:thenault@gmail.com | |
17 # | |
18 # This file is part of logilab-astng. | |
19 # | |
20 # logilab-astng is free software: you can redistribute it and/or modify it | |
21 # under the terms of the GNU Lesser General Public License as published by the | |
22 # Free Software Foundation, either version 2.1 of the License, or (at your | |
23 # option) any later version. | |
24 # | |
25 # logilab-astng is distributed in the hope that it will be useful, but | |
26 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
27 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | |
28 # for more details. | |
29 # | |
30 # You should have received a copy of the GNU Lesser General Public License along | |
31 # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. | |
32 """visitor doing some postprocessing on the astng tree. | |
33 Try to resolve definitions (namespace) dictionary, relationship... | |
34 | |
35 This module has been imported from pyreverse | |
36 """ | |
37 | |
38 __docformat__ = "restructuredtext en" | |
39 | |
40 from os.path import dirname | |
41 | |
42 from logilab.common.modutils import get_module_part, is_relative, \ | |
43 is_standard_module | |
44 | |
45 from logilab import astng | |
46 from logilab.astng.exceptions import InferenceError | |
47 from logilab.astng.utils import LocalsVisitor | |
48 | |
49 class IdGeneratorMixIn: | |
50 """ | |
51 Mixin adding the ability to generate integer uid | |
52 """ | |
53 def __init__(self, start_value=0): | |
54 self.id_count = start_value | |
55 | |
56 def init_counter(self, start_value=0): | |
57 """init the id counter | |
58 """ | |
59 self.id_count = start_value | |
60 | |
61 def generate_id(self): | |
62 """generate a new identifier | |
63 """ | |
64 self.id_count += 1 | |
65 return self.id_count | |
66 | |
67 | |
68 class Linker(IdGeneratorMixIn, LocalsVisitor): | |
69 """ | |
70 walk on the project tree and resolve relationships. | |
71 | |
72 According to options the following attributes may be added to visited nodes: | |
73 | |
74 * uid, | |
75 a unique identifier for the node (on astng.Project, astng.Module, | |
76 astng.Class and astng.locals_type). Only if the linker has been instantiat
ed | |
77 with tag=True parameter (False by default). | |
78 | |
79 * Function | |
80 a mapping from locals names to their bounded value, which may be a | |
81 constant like a string or an integer, or an astng node (on astng.Module, | |
82 astng.Class and astng.Function). | |
83 | |
84 * instance_attrs_type | |
85 as locals_type but for klass member attributes (only on astng.Class) | |
86 | |
87 * implements, | |
88 list of implemented interface _objects_ (only on astng.Class nodes) | |
89 """ | |
90 | |
91 def __init__(self, project, inherited_interfaces=0, tag=False): | |
92 IdGeneratorMixIn.__init__(self) | |
93 LocalsVisitor.__init__(self) | |
94 # take inherited interface in consideration or not | |
95 self.inherited_interfaces = inherited_interfaces | |
96 # tag nodes or not | |
97 self.tag = tag | |
98 # visited project | |
99 self.project = project | |
100 | |
101 | |
102 def visit_project(self, node): | |
103 """visit an astng.Project node | |
104 | |
105 * optionally tag the node with a unique id | |
106 """ | |
107 if self.tag: | |
108 node.uid = self.generate_id() | |
109 for module in node.modules: | |
110 self.visit(module) | |
111 | |
112 def visit_package(self, node): | |
113 """visit an astng.Package node | |
114 | |
115 * optionally tag the node with a unique id | |
116 """ | |
117 if self.tag: | |
118 node.uid = self.generate_id() | |
119 for subelmt in node.values(): | |
120 self.visit(subelmt) | |
121 | |
122 def visit_module(self, node): | |
123 """visit an astng.Module node | |
124 | |
125 * set the locals_type mapping | |
126 * set the depends mapping | |
127 * optionally tag the node with a unique id | |
128 """ | |
129 if hasattr(node, 'locals_type'): | |
130 return | |
131 node.locals_type = {} | |
132 node.depends = [] | |
133 if self.tag: | |
134 node.uid = self.generate_id() | |
135 | |
136 def visit_class(self, node): | |
137 """visit an astng.Class node | |
138 | |
139 * set the locals_type and instance_attrs_type mappings | |
140 * set the implements list and build it | |
141 * optionally tag the node with a unique id | |
142 """ | |
143 if hasattr(node, 'locals_type'): | |
144 return | |
145 node.locals_type = {} | |
146 if self.tag: | |
147 node.uid = self.generate_id() | |
148 # resolve ancestors | |
149 for baseobj in node.ancestors(recurs=False): | |
150 specializations = getattr(baseobj, 'specializations', []) | |
151 specializations.append(node) | |
152 baseobj.specializations = specializations | |
153 # resolve instance attributes | |
154 node.instance_attrs_type = {} | |
155 for assattrs in node.instance_attrs.values(): | |
156 for assattr in assattrs: | |
157 self.handle_assattr_type(assattr, node) | |
158 # resolve implemented interface | |
159 try: | |
160 node.implements = list(node.interfaces(self.inherited_interfaces)) | |
161 except InferenceError: | |
162 node.implements = () | |
163 | |
164 def visit_function(self, node): | |
165 """visit an astng.Function node | |
166 | |
167 * set the locals_type mapping | |
168 * optionally tag the node with a unique id | |
169 """ | |
170 if hasattr(node, 'locals_type'): | |
171 return | |
172 node.locals_type = {} | |
173 if self.tag: | |
174 node.uid = self.generate_id() | |
175 | |
176 link_project = visit_project | |
177 link_module = visit_module | |
178 link_class = visit_class | |
179 link_function = visit_function | |
180 | |
181 def visit_assname(self, node): | |
182 """visit an astng.AssName node | |
183 | |
184 handle locals_type | |
185 """ | |
186 # avoid double parsing done by different Linkers.visit | |
187 # running over the same project: | |
188 if hasattr(node, '_handled'): | |
189 return | |
190 node._handled = True | |
191 if node.name in node.frame(): | |
192 frame = node.frame() | |
193 else: | |
194 # the name has been defined as 'global' in the frame and belongs | |
195 # there. Btw the frame is not yet visited as the name is in the | |
196 # root locals; the frame hence has no locals_type attribute | |
197 frame = node.root() | |
198 try: | |
199 values = node.infered() | |
200 try: | |
201 already_infered = frame.locals_type[node.name] | |
202 for valnode in values: | |
203 if not valnode in already_infered: | |
204 already_infered.append(valnode) | |
205 except KeyError: | |
206 frame.locals_type[node.name] = values | |
207 except astng.InferenceError: | |
208 pass | |
209 | |
210 def handle_assattr_type(self, node, parent): | |
211 """handle an astng.AssAttr node | |
212 | |
213 handle instance_attrs_type | |
214 """ | |
215 try: | |
216 values = list(node.infer()) | |
217 try: | |
218 already_infered = parent.instance_attrs_type[node.attrname] | |
219 for valnode in values: | |
220 if not valnode in already_infered: | |
221 already_infered.append(valnode) | |
222 except KeyError: | |
223 parent.instance_attrs_type[node.attrname] = values | |
224 except astng.InferenceError: | |
225 pass | |
226 | |
227 def visit_import(self, node): | |
228 """visit an astng.Import node | |
229 | |
230 resolve module dependencies | |
231 """ | |
232 context_file = node.root().file | |
233 for name in node.names: | |
234 relative = is_relative(name[0], context_file) | |
235 self._imported_module(node, name[0], relative) | |
236 | |
237 | |
238 def visit_from(self, node): | |
239 """visit an astng.From node | |
240 | |
241 resolve module dependencies | |
242 """ | |
243 basename = node.modname | |
244 context_file = node.root().file | |
245 if context_file is not None: | |
246 relative = is_relative(basename, context_file) | |
247 else: | |
248 relative = False | |
249 for name in node.names: | |
250 if name[0] == '*': | |
251 continue | |
252 # analyze dependencies | |
253 fullname = '%s.%s' % (basename, name[0]) | |
254 if fullname.find('.') > -1: | |
255 try: | |
256 # XXX: don't use get_module_part, missing package precedence | |
257 fullname = get_module_part(fullname) | |
258 except ImportError: | |
259 continue | |
260 if fullname != basename: | |
261 self._imported_module(node, fullname, relative) | |
262 | |
263 | |
264 def compute_module(self, context_name, mod_path): | |
265 """return true if the module should be added to dependencies""" | |
266 package_dir = dirname(self.project.path) | |
267 if context_name == mod_path: | |
268 return 0 | |
269 elif is_standard_module(mod_path, (package_dir,)): | |
270 return 1 | |
271 return 0 | |
272 | |
273 # protected methods ######################################################## | |
274 | |
275 def _imported_module(self, node, mod_path, relative): | |
276 """notify an imported module, used to analyze dependencies | |
277 """ | |
278 module = node.root() | |
279 context_name = module.name | |
280 if relative: | |
281 mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), | |
282 mod_path) | |
283 if self.compute_module(context_name, mod_path): | |
284 # handle dependencies | |
285 if not hasattr(module, 'depends'): | |
286 module.depends = [] | |
287 mod_paths = module.depends | |
288 if not mod_path in mod_paths: | |
289 mod_paths.append(mod_path) | |
OLD | NEW |