OLD | NEW |
| (Empty) |
1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
3 # copyright 2003-2010 Sylvain Thenault, all rights reserved. | |
4 # contact mailto:thenault@gmail.com | |
5 # | |
6 # This file is part of logilab-astng. | |
7 # | |
8 # logilab-astng is free software: you can redistribute it and/or modify it | |
9 # under the terms of the GNU Lesser General Public License as published by the | |
10 # Free Software Foundation, either version 2.1 of the License, or (at your | |
11 # option) any later version. | |
12 # | |
13 # logilab-astng is distributed in the hope that it will be useful, but | |
14 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | |
16 # for more details. | |
17 # | |
18 # You should have received a copy of the GNU Lesser General Public License along | |
19 # with logilab-astng. If not, see <http://www.gnu.org/licenses/>. | |
20 """astng manager: avoid multiple astng build of a same module when | |
21 possible by providing a class responsible to get astng representation | |
22 from various source and using a cache of built modules) | |
23 """ | |
24 | |
25 __docformat__ = "restructuredtext en" | |
26 | |
27 import sys | |
28 import os | |
29 from os.path import dirname, basename, abspath, join, isdir, exists | |
30 | |
31 from logilab.common.modutils import NoSourceFile, is_python_source, \ | |
32 file_from_modpath, load_module_from_name, modpath_from_file, \ | |
33 get_module_files, get_source_file, zipimport | |
34 from logilab.common.configuration import OptionsProviderMixIn | |
35 | |
36 from logilab.astng.exceptions import ASTNGBuildingException | |
37 | |
38 def astng_wrapper(func, modname): | |
39 """wrapper to give to ASTNGManager.project_from_files""" | |
40 print 'parsing %s...' % modname | |
41 try: | |
42 return func(modname) | |
43 except ASTNGBuildingException, exc: | |
44 print exc | |
45 except Exception, exc: | |
46 import traceback | |
47 traceback.print_exc() | |
48 | |
49 def _silent_no_wrap(func, modname): | |
50 """silent wrapper that doesn't do anything; can be used for tests""" | |
51 return func(modname) | |
52 | |
53 def safe_repr(obj): | |
54 try: | |
55 return repr(obj) | |
56 except: | |
57 return '???' | |
58 | |
59 | |
60 | |
61 class ASTNGManager(OptionsProviderMixIn): | |
62 """the astng manager, responsible to build astng from files | |
63 or modules. | |
64 | |
65 Use the Borg pattern. | |
66 """ | |
67 | |
68 name = 'astng loader' | |
69 options = (("ignore", | |
70 {'type' : "csv", 'metavar' : "<file>", | |
71 'dest' : "black_list", "default" : ('CVS',), | |
72 'help' : "add <file> (may be a directory) to the black list\ | |
73 . It should be a base name, not a path. You may set this option multiple times\ | |
74 ."}), | |
75 ("project", | |
76 {'default': "No Name", 'type' : 'string', 'short': 'p', | |
77 'metavar' : '<project name>', | |
78 'help' : 'set the project name.'}), | |
79 ) | |
80 brain = {} | |
81 def __init__(self): | |
82 self.__dict__ = ASTNGManager.brain | |
83 if not self.__dict__: | |
84 OptionsProviderMixIn.__init__(self) | |
85 self.load_defaults() | |
86 # NOTE: cache entries are added by the [re]builder | |
87 self.astng_cache = {} | |
88 self._mod_file_cache = {} | |
89 self.transformers = [] | |
90 | |
91 def astng_from_file(self, filepath, modname=None, fallback=True, source=Fals
e): | |
92 """given a module name, return the astng object""" | |
93 try: | |
94 filepath = get_source_file(filepath, include_no_ext=True) | |
95 source = True | |
96 except NoSourceFile: | |
97 pass | |
98 if modname is None: | |
99 try: | |
100 modname = '.'.join(modpath_from_file(filepath)) | |
101 except ImportError: | |
102 modname = filepath | |
103 if modname in self.astng_cache: | |
104 return self.astng_cache[modname] | |
105 if source: | |
106 from logilab.astng.builder import ASTNGBuilder | |
107 return ASTNGBuilder(self).file_build(filepath, modname) | |
108 elif fallback and modname: | |
109 return self.astng_from_module_name(modname) | |
110 raise ASTNGBuildingException('unable to get astng for file %s' % | |
111 filepath) | |
112 | |
113 def astng_from_module_name(self, modname, context_file=None): | |
114 """given a module name, return the astng object""" | |
115 if modname in self.astng_cache: | |
116 return self.astng_cache[modname] | |
117 if modname == '__main__': | |
118 from logilab.astng.builder import ASTNGBuilder | |
119 return ASTNGBuilder(self).string_build('', modname) | |
120 old_cwd = os.getcwd() | |
121 if context_file: | |
122 os.chdir(dirname(context_file)) | |
123 try: | |
124 filepath = self.file_from_module_name(modname, context_file) | |
125 if filepath is not None and not is_python_source(filepath): | |
126 module = self.zip_import_data(filepath) | |
127 if module is not None: | |
128 return module | |
129 if filepath is None or not is_python_source(filepath): | |
130 try: | |
131 module = load_module_from_name(modname) | |
132 except Exception, ex: | |
133 msg = 'Unable to load module %s (%s)' % (modname, ex) | |
134 raise ASTNGBuildingException(msg) | |
135 return self.astng_from_module(module, modname) | |
136 return self.astng_from_file(filepath, modname, fallback=False) | |
137 finally: | |
138 os.chdir(old_cwd) | |
139 | |
140 def zip_import_data(self, filepath): | |
141 if zipimport is None: | |
142 return None | |
143 from logilab.astng.builder import ASTNGBuilder | |
144 builder = ASTNGBuilder(self) | |
145 for ext in ('.zip', '.egg'): | |
146 try: | |
147 eggpath, resource = filepath.rsplit(ext + '/', 1) | |
148 except ValueError: | |
149 continue | |
150 try: | |
151 importer = zipimport.zipimporter(eggpath + ext) | |
152 zmodname = resource.replace('/', '.') | |
153 if importer.is_package(resource): | |
154 zmodname = zmodname + '.__init__' | |
155 module = builder.string_build(importer.get_source(resource), | |
156 zmodname, filepath) | |
157 return module | |
158 except: | |
159 continue | |
160 return None | |
161 | |
162 def file_from_module_name(self, modname, contextfile): | |
163 try: | |
164 value = self._mod_file_cache[(modname, contextfile)] | |
165 except KeyError: | |
166 try: | |
167 value = file_from_modpath(modname.split('.'), | |
168 context_file=contextfile) | |
169 except ImportError, ex: | |
170 msg = 'Unable to load module %s (%s)' % (modname, ex) | |
171 value = ASTNGBuildingException(msg) | |
172 self._mod_file_cache[(modname, contextfile)] = value | |
173 if isinstance(value, ASTNGBuildingException): | |
174 raise value | |
175 return value | |
176 | |
177 def astng_from_module(self, module, modname=None): | |
178 """given an imported module, return the astng object""" | |
179 modname = modname or module.__name__ | |
180 if modname in self.astng_cache: | |
181 return self.astng_cache[modname] | |
182 try: | |
183 # some builtin modules don't have __file__ attribute | |
184 filepath = module.__file__ | |
185 if is_python_source(filepath): | |
186 return self.astng_from_file(filepath, modname) | |
187 except AttributeError: | |
188 pass | |
189 from logilab.astng.builder import ASTNGBuilder | |
190 return ASTNGBuilder(self).module_build(module, modname) | |
191 | |
192 def astng_from_class(self, klass, modname=None): | |
193 """get astng for the given class""" | |
194 if modname is None: | |
195 try: | |
196 modname = klass.__module__ | |
197 except AttributeError: | |
198 raise ASTNGBuildingException( | |
199 'Unable to get module for class %s' % safe_repr(klass)) | |
200 modastng = self.astng_from_module_name(modname) | |
201 return modastng.getattr(klass.__name__)[0] # XXX | |
202 | |
203 | |
204 def infer_astng_from_something(self, obj, context=None): | |
205 """infer astng for the given class""" | |
206 if hasattr(obj, '__class__') and not isinstance(obj, type): | |
207 klass = obj.__class__ | |
208 else: | |
209 klass = obj | |
210 try: | |
211 modname = klass.__module__ | |
212 except AttributeError: | |
213 raise ASTNGBuildingException( | |
214 'Unable to get module for %s' % safe_repr(klass)) | |
215 except Exception, ex: | |
216 raise ASTNGBuildingException( | |
217 'Unexpected error while retrieving module for %s: %s' | |
218 % (safe_repr(klass), ex)) | |
219 try: | |
220 name = klass.__name__ | |
221 except AttributeError: | |
222 raise ASTNGBuildingException( | |
223 'Unable to get name for %s' % safe_repr(klass)) | |
224 except Exception, ex: | |
225 raise ASTNGBuildingException( | |
226 'Unexpected error while retrieving name for %s: %s' | |
227 % (safe_repr(klass), ex)) | |
228 # take care, on living object __module__ is regularly wrong :( | |
229 modastng = self.astng_from_module_name(modname) | |
230 if klass is obj: | |
231 for infered in modastng.igetattr(name, context): | |
232 yield infered | |
233 else: | |
234 for infered in modastng.igetattr(name, context): | |
235 yield infered.instanciate_class() | |
236 | |
237 def project_from_files(self, files, func_wrapper=astng_wrapper, | |
238 project_name=None, black_list=None): | |
239 """return a Project from a list of files or modules""" | |
240 # build the project representation | |
241 project_name = project_name or self.config.project | |
242 black_list = black_list or self.config.black_list | |
243 project = Project(project_name) | |
244 for something in files: | |
245 if not exists(something): | |
246 fpath = file_from_modpath(something.split('.')) | |
247 elif isdir(something): | |
248 fpath = join(something, '__init__.py') | |
249 else: | |
250 fpath = something | |
251 astng = func_wrapper(self.astng_from_file, fpath) | |
252 if astng is None: | |
253 continue | |
254 # XXX why is first file defining the project.path ? | |
255 project.path = project.path or astng.file | |
256 project.add_module(astng) | |
257 base_name = astng.name | |
258 # recurse in package except if __init__ was explicitly given | |
259 if astng.package and something.find('__init__') == -1: | |
260 # recurse on others packages / modules if this is a package | |
261 for fpath in get_module_files(dirname(astng.file), | |
262 black_list): | |
263 astng = func_wrapper(self.astng_from_file, fpath) | |
264 if astng is None or astng.name == base_name: | |
265 continue | |
266 project.add_module(astng) | |
267 return project | |
268 | |
269 def register_transformer(self, transformer): | |
270 self.transformers.append(transformer) | |
271 | |
272 class Project: | |
273 """a project handle a set of modules / packages""" | |
274 def __init__(self, name=''): | |
275 self.name = name | |
276 self.path = None | |
277 self.modules = [] | |
278 self.locals = {} | |
279 self.__getitem__ = self.locals.__getitem__ | |
280 self.__iter__ = self.locals.__iter__ | |
281 self.values = self.locals.values | |
282 self.keys = self.locals.keys | |
283 self.items = self.locals.items | |
284 | |
285 def add_module(self, node): | |
286 self.locals[node.name] = node | |
287 self.modules.append(node) | |
288 | |
289 def get_module(self, name): | |
290 return self.locals[name] | |
291 | |
292 def get_children(self): | |
293 return self.modules | |
294 | |
295 def __repr__(self): | |
296 return '<Project %r at %s (%s modules)>' % (self.name, id(self), | |
297 len(self.modules)) | |
298 | |
299 | |
OLD | NEW |