PageRenderTime 49ms CodeModel.GetById 10ms app.highlight 32ms RepoModel.GetById 2ms app.codeStats 0ms

/logilab-astng-0.23.1/manager.py

#
Python | 299 lines | 269 code | 6 blank | 24 comment | 2 complexity | a79aeb41cbbf04cc4c5e4b8e25b918ac MD5 | raw file
  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
 21possible by providing a class responsible to get astng representation
 22from various source and using a cache of built modules)
 23"""
 24
 25__docformat__ = "restructuredtext en"
 26
 27import sys
 28import os
 29from os.path import dirname, basename, abspath, join, isdir, exists
 30
 31from 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
 34from logilab.common.configuration import OptionsProviderMixIn
 35
 36from logilab.astng.exceptions import ASTNGBuildingException
 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, exc:
 44        print exc
 45    except Exception, exc:
 46        import traceback
 47        traceback.print_exc()
 48
 49def _silent_no_wrap(func, modname):
 50    """silent wrapper that doesn't do anything; can be used for tests"""
 51    return func(modname)
 52
 53def safe_repr(obj):
 54    try:
 55        return repr(obj)
 56    except:
 57        return '???'
 58
 59
 60
 61class 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=False):
 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
272class 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