root / logilab.pylintinstaller / logilab / astng / manager.py

Revision 202:d67e86292521, 14.3 kB (checked in by tziade@…, 9 months ago)

added logilab.pylintinstaller

Line 
1# This program is free software; you can redistribute it and/or modify it under
2# the terms of the GNU General Public License as published by the Free Software
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 General Public License for more details.
9#
10# You should have received a copy of the GNU 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"""astng manager: avoid multible astng build of a same module when
14possible by providing a class responsible to get astng representation
15from various source and using a cache of built modules)
16
17:author:    Sylvain Thenault
18:copyright: 2003-2007 LOGILAB S.A. (Paris, FRANCE)
19:contact:   http://www.logilab.fr/ -- mailto:python-projects@logilab.org
20:copyright: 2003-2007 Sylvain Thenault
21:contact:   mailto:thenault@gmail.com
22"""
23
24__docformat__ = "restructuredtext en"
25
26import sys
27import os
28from os.path import dirname, basename, abspath, join, isdir, exists
29
30from logilab.common.cache import Cache
31from logilab.common.modutils import NoSourceFile, is_python_source, \
32     file_from_modpath, load_module_from_name, \
33     get_module_files, get_source_file
34from logilab.common.configuration import OptionsProviderMixIn
35
36from logilab.astng import ASTNGBuildingException, Instance, nodes
37
38def 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, ex:
44        print ex
45    except KeyboardInterrupt:
46        raise
47    except Exception, ex:
48        import traceback
49        traceback.print_exc()
50
51def safe_repr(obj):
52    try:
53        return repr(obj)
54    except:
55        return '???'
56   
57class ASTNGManager(OptionsProviderMixIn):
58    """the astng manager, responsible to build astng from files
59     or modules.
60
61    Use the Borg pattern.
62    """
63    name = 'astng loader'
64    options = (("ignore",
65                {'type' : "csv", 'metavar' : "<file>",
66                 'dest' : "black_list", "default" : ('CVS',),
67                 'help' : "add <file> (may be a directory) to the black list\
68. It should be a base name, not a path. You may set this option multiple times\
69."}),
70               ("project",
71                {'default': "No Name", 'type' : 'string', 'short': 'p',
72                 'metavar' : '<project name>',
73                 'help' : 'set the project name.'}),
74               )
75    brain = {}   
76    def __init__(self):
77        self.__dict__ = ASTNGManager.brain
78        if not self.__dict__:
79            OptionsProviderMixIn.__init__(self)
80            self._cache = None
81            self._mod_file_cache = None
82            self.set_cache_size(200)
83            self.load_defaults()
84           
85    def set_cache_size(self, cache_size):
86        """set the cache size (flush it as a side effect!)"""
87        self._cache = {} #Cache(cache_size)
88        self._mod_file_cache = {}
89
90    def from_directory(self, directory, modname=None):
91        """given a module name, return the astng object"""
92        modname = modname or basename(directory)
93        directory = abspath(directory)
94        return Package(directory, modname, self)
95
96    def astng_from_file(self, filepath, modname=None, fallback=True):
97        """given a module name, return the astng object"""
98        try:
99            filepath = get_source_file(filepath, include_no_ext=True)
100            source = True
101        except NoSourceFile:
102            source = False
103        try:
104            return self._cache[filepath]
105        except KeyError:
106            if source:
107                try:
108                    from logilab.astng.builder import ASTNGBuilder
109                    astng = ASTNGBuilder(self).file_build(filepath, modname)
110                except SyntaxError:
111                    raise
112                except Exception, ex:
113                    if __debug__:
114                        import traceback
115                        traceback.print_exc()
116                    msg = 'Unable to load module %s (%s)' % (modname, ex)
117                    raise ASTNGBuildingException(msg), None, sys.exc_info()[-1]
118            elif fallback and modname:
119                return self.astng_from_module_name(modname)
120            else:
121                raise ASTNGBuildingException('unable to get astng for file %s' %
122                                             filepath)
123        self._cache[filepath] = astng
124        return astng
125   
126    from_file = astng_from_file # backward compat
127   
128    def astng_from_module_name(self, modname, context_file=None):
129        """given a module name, return the astng object"""
130        old_cwd = os.getcwd()
131        if context_file:
132            os.chdir(dirname(context_file))
133        try:
134            filepath = self.file_from_module_name(modname, context_file)
135            if filepath is None or not is_python_source(filepath):
136                try:
137                    module = load_module_from_name(modname) 
138                except ImportError, ex:
139                    msg = 'Unable to load module %s (%s)' % (modname, ex)
140                    raise ASTNGBuildingException(msg)
141                return self.astng_from_module(module, modname)
142            return self.astng_from_file(filepath, modname, fallback=False)
143        finally:
144            os.chdir(old_cwd)
145           
146    def file_from_module_name(self, modname, contextfile):
147        try:
148            value = self._mod_file_cache[(modname, contextfile)]
149        except KeyError:
150            try:
151                value = file_from_modpath(modname.split('.'),
152                                          context_file=contextfile)
153            except ImportError, ex:
154                msg = 'Unable to load module %s (%s)' % (modname, ex)
155                value = ASTNGBuildingException(msg)
156            self._mod_file_cache[(modname, contextfile)] = value
157        if isinstance(value, ASTNGBuildingException):
158            raise value
159        return value
160       
161    def astng_from_module(self, module, modname=None):
162        """given an imported module, return the astng object"""
163        modname = modname or module.__name__
164        filepath = modname
165        try:
166            # some builtin modules don't have __file__ attribute
167            filepath = module.__file__
168            if is_python_source(filepath):
169                return self.astng_from_file(filepath, modname)
170        except AttributeError:
171            pass
172        try:
173            return self._cache[filepath]
174        except KeyError:
175            from logilab.astng.builder import ASTNGBuilder
176            astng = ASTNGBuilder(self).module_build(module, modname)
177            # update caches (filepath and astng.file are not necessarily  the
178            # same (.pyc pb))
179            self._cache[filepath] = self._cache[astng.file] = astng
180            return astng
181           
182    def astng_from_class(self, klass, modname=None):
183        """get astng for the given class"""
184        if modname is None:
185            try:
186                modname = klass.__module__
187            except AttributeError:
188                raise ASTNGBuildingException(
189                    'Unable to get module for class %s' % safe_repr(klass))
190        modastng = self.astng_from_module_name(modname)
191        return modastng.getattr(klass.__name__)[0] # XXX
192
193           
194    def infer_astng_from_something(self, obj, modname=None, context=None):
195        """infer astng for the given class"""
196        if hasattr(obj, '__class__') and not isinstance(obj, type):
197            klass = obj.__class__
198        else:
199            klass = obj
200        if modname is None:
201            try:
202                modname = klass.__module__
203            except AttributeError:
204                raise ASTNGBuildingException(
205                    'Unable to get module for %s' % safe_repr(klass))
206            except Exception, ex:
207                raise ASTNGBuildingException(
208                    'Unexpected error while retreiving module for %s: %s'
209                    % (safe_repr(klass), ex))
210        try:
211            name = klass.__name__
212        except AttributeError:
213            raise ASTNGBuildingException(
214                'Unable to get name for %s' % safe_repr(klass))
215        except Exception, ex:
216            raise ASTNGBuildingException(
217                'Unexpected error while retreiving name for %s: %s'
218                % (safe_repr(klass), ex))
219        # take care, on living object __module__ is regularly wrong :(
220        modastng = self.astng_from_module_name(modname)
221        for infered in modastng.igetattr(name, context):
222            if klass is not obj and isinstance(infered, nodes.Class):
223                infered = Instance(infered)
224            yield infered
225           
226    def project_from_files(self, files, func_wrapper=astng_wrapper,
227                           project_name=None, black_list=None):
228        """return a Project from a list of files or modules"""
229        # insert current working directory to the python path to have a correct
230        # behaviour
231        sys.path.insert(0, os.getcwd())
232        try:
233            # build the project representation
234            project_name = project_name or self.config.project
235            black_list = black_list or self.config.black_list
236            project = Project(project_name)
237            for something in files:
238                if not exists(something):
239                    fpath = file_from_modpath(something.split('.'))
240                elif isdir(something):
241                    fpath = join(something, '__init__.py')
242                else:
243                    fpath = something
244                astng = func_wrapper(self.astng_from_file, fpath)
245                if astng is None:
246                    continue
247                project.path = project.path or astng.file
248                project.add_module(astng)
249                base_name = astng.name
250                # recurse in package except if __init__ was explicitly given
251                if astng.package and something.find('__init__') == -1:
252                    # recurse on others packages / modules if this is a package
253                    for fpath in get_module_files(dirname(astng.file),
254                                                  black_list):
255                        astng = func_wrapper(self.astng_from_file, fpath)
256                        if astng is None or astng.name == base_name:
257                            continue
258                        project.add_module(astng)
259            return project
260        finally:
261            sys.path.pop(0)
262   
263
264
265class Package:
266    """a package using a dictionary like interface
267
268    load submodules lazily, as they are needed
269    """
270   
271    def __init__(self, path, name, manager):
272        self.name = name
273        self.path = abspath(path)
274        self.manager = manager
275        self.parent = None
276        self.lineno = 0
277        self.__keys = None
278        self.__subobjects = None
279
280    def fullname(self):
281        """return the full name of the package (i.e. prefix by the full name
282        of the parent package if any
283        """
284        if self.parent is None:
285            return self.name
286        return '%s.%s' % (self.parent.fullname(), self.name)
287   
288    def get_subobject(self, name):
289        """method used to get sub-objects lazily : sub package or module are
290        only build once they are requested
291        """
292        if self.__subobjects is None:
293            try:
294                self.__subobjects = dict.fromkeys(self.keys())
295            except AttributeError:
296                # python <= 2.3
297                self.__subobjects = dict([(k, None) for k in self.keys()])
298        obj = self.__subobjects[name]
299        if obj is None:
300            objpath = join(self.path, name)
301            if isdir(objpath):
302                obj = Package(objpath, name, self.manager)
303                obj.parent = self
304            else:
305                modname = '%s.%s' % (self.fullname(), name)
306                obj = self.manager.astng_from_file(objpath + '.py', modname)
307            self.__subobjects[name] = obj
308        return obj
309   
310    def get_module(self, modname):
311        """return the Module or Package object with the given name if any
312        """
313        path = modname.split('.')
314        if path[0] != self.name:
315            raise KeyError(modname)
316        obj = self
317        for part in path[1:]:
318            obj = obj.get_subobject(part)
319        return obj
320   
321    def keys(self):
322        if self.__keys is None:
323            self.__keys = []
324            for fname in os.listdir(self.path):
325                if fname.endswith('.py'):
326                    self.__keys.append(fname[:-3])
327                    continue
328                fpath = join(self.path, fname)
329                if isdir(fpath) and exists(join(fpath, '__init__.py')):
330                    self.__keys.append(fname)
331            self.__keys.sort()
332        return self.__keys[:]
333   
334    def values(self):
335        return [self.get_subobject(name) for name in self.keys()]
336       
337    def items(self):
338        return zip(self.keys(), self.values())
339   
340    def has_key(self, name):
341        return bool(self.get(name))
342   
343    def get(self, name, default=None):
344        try:
345            return self.get_subobject(name)
346        except KeyError:
347            return default
348       
349    def __getitem__(self, name):
350        return self.get_subobject(name)       
351    def __contains__(self, name):
352        return self.has_key(name)
353    def __iter__(self):
354        return iter(self.keys())
355   
356
357class Project:
358    """a project handle a set of modules / packages"""
359    def __init__(self, name=''):
360        self.name = name
361        self.path = None
362        self.modules = []
363        self.locals = {}
364        self.__getitem__ = self.locals.__getitem__
365        self.__iter__ = self.locals.__iter__
366        self.values = self.locals.values
367        self.keys = self.locals.keys
368        self.has_key = self.locals.has_key
369       
370    def add_module(self, node):
371        self.locals[node.name] = node
372        self.modules.append(node)
373       
374    def get_module(self, name):
375        return self.locals[name]
376   
377    def getChildNodes(self):
378        return self.modules
379
380    def __repr__(self):
381        return '<Project %r at %s (%s modules)>' % (self.name, id(self),
382                                                    len(self.modules))
Note: See TracBrowser for help on using the browser.