summaryrefslogtreecommitdiff
path: root/manager.py
diff options
context:
space:
mode:
Diffstat (limited to 'manager.py')
-rw-r--r--manager.py345
1 files changed, 345 insertions, 0 deletions
diff --git a/manager.py b/manager.py
new file mode 100644
index 0000000..49bd514
--- /dev/null
+++ b/manager.py
@@ -0,0 +1,345 @@
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""astng manager: avoid multible astng build of a same module when
+possible by providing a class responsible to get astng representation
+from various source and using a cache of built modules)
+
+:version: $Revision: 1.49 $
+:author: Sylvain Thenault
+:copyright: 2003-2005 LOGILAB S.A. (Paris, FRANCE)
+:contact: http://www.logilab.fr/ -- mailto:python-projects@logilab.org
+:copyright: 2003-2005 Sylvain Thenault
+:contact: mailto:thenault@gmail.com
+"""
+
+__revision__ = "$Id: manager.py,v 1.49 2006-03-08 15:52:30 syt Exp $"
+__doctype__ = "restructuredtext en"
+
+import sys
+import os
+from os.path import dirname, basename, abspath, join, isdir, exists
+
+from logilab.common.cache import Cache
+from logilab.common.modutils import NoSourceFile, is_python_source, \
+ file_from_modpath, load_module_from_name, \
+ get_module_files, get_source_file
+from logilab.common.configuration import OptionsProviderMixIn
+
+from logilab.astng._exceptions import ASTNGBuildingException
+
+def astng_wrapper(func, modname):
+ """wrapper to give to ASTNGManager.project_from_files"""
+ print 'parsing %s...' % modname
+ try:
+ return func(modname)
+ except ASTNGBuildingException, ex:
+ print ex
+ except KeyboardInterrupt:
+ raise
+ except Exception, ex:
+ import traceback
+ traceback.print_exc()
+
+
+class ASTNGManager(OptionsProviderMixIn):
+ """the astng manager, responsible to build astng from files
+ or modules.
+
+ Use the Borg pattern.
+ """
+ name = 'astng loader'
+ options = (("ignore",
+ {'type' : "csv", 'metavar' : "<file>",
+ 'dest' : "black_list", "default" : ('CVS',),
+ 'help' : "add <file> (may be a directory) to the black list\
+. It should be a base name, not a path. You may set this option multiple times\
+."}),
+ ("project",
+ {'default': "No Name", 'type' : 'string', 'short': 'p',
+ 'metavar' : '<project name>',
+ 'help' : 'set the project name.'}),
+ )
+ brain = {}
+ def __init__(self):
+ self.__dict__ = ASTNGManager.brain
+ if not self.__dict__:
+ OptionsProviderMixIn.__init__(self)
+ self._cache = None
+ self._mod_file_cache = None
+ self.set_cache_size(200)
+
+ def set_cache_size(self, cache_size):
+ """set the cache size (flush it as a side effect!)"""
+ self._cache = {} #Cache(cache_size)
+ self._mod_file_cache = {}
+
+ def from_directory(self, directory, modname=None):
+ """given a module name, return the astng object"""
+ modname = modname or basename(directory)
+ directory = abspath(directory)
+ return Package(directory, modname, self)
+
+ def astng_from_file(self, filepath, modname=None, fallback=True):
+ """given a module name, return the astng object"""
+ try:
+ filepath = get_source_file(filepath, include_no_ext=True)
+ source = True
+ except NoSourceFile:
+ source = False
+ try:
+ return self._cache[filepath]
+ except KeyError:
+ if source:
+ try:
+ from logilab.astng.builder import ASTNGBuilder
+ astng = ASTNGBuilder().file_build(filepath, modname)
+ except SyntaxError:
+ raise
+ except Exception, ex:
+ #if __debug__:
+ # import traceback
+ # traceback.print_exc()
+ msg = 'Unable to load module %s (%s)' % (modname, ex)
+ raise ASTNGBuildingException(msg), None, sys.exc_info()[-1]
+ elif fallback and modname:
+ return self.astng_from_module_name(modname)
+ else:
+ raise ASTNGBuildingException('unable to get astng for file %s' %
+ filepath)
+ self._cache[filepath] = astng
+ return astng
+
+ from_file = astng_from_file # backward compat
+
+ def astng_from_module_name(self, modname, context_file=None):
+ """given a module name, return the astng object"""
+ old_cwd = os.getcwd()
+ if context_file:
+ os.chdir(dirname(context_file))
+ try:
+ filepath = self.file_from_module_name(modname, context_file)
+ if filepath is None or not is_python_source(filepath):
+ try:
+ module = load_module_from_name(modname)
+ except ImportError, ex:
+ msg = 'Unable to load module %s (%s)' % (modname, ex)
+ raise ASTNGBuildingException(msg)
+ return self.astng_from_module(module, modname)
+ return self.astng_from_file(filepath, modname, fallback=False)
+ finally:
+ os.chdir(old_cwd)
+
+ def file_from_module_name(self, modname, contextfile):
+ try:
+ value = self._mod_file_cache[(modname, contextfile)]
+ except KeyError:
+ try:
+ value = file_from_modpath(modname.split('.'),
+ context_file=contextfile)
+ except ImportError, ex:
+ msg = 'Unable to load module %s (%s)' % (modname, ex)
+ value = ASTNGBuildingException(msg)
+ self._mod_file_cache[(modname, contextfile)] = value
+ if isinstance(value, ASTNGBuildingException):
+ raise value
+ return value
+
+ def astng_from_module(self, module, modname=None):
+ """given an imported module, return the astng object"""
+ modname = modname or module.__name__
+ filepath = modname
+ try:
+ # some builtin modules don't have __file__ attribute
+ filepath = module.__file__
+ if is_python_source(filepath):
+ return self.astng_from_file(filepath, modname)
+ except AttributeError:
+ pass
+ try:
+ return self._cache[filepath]
+ except KeyError:
+ from logilab.astng.builder import ASTNGBuilder
+ astng = ASTNGBuilder().module_build(module, modname)
+ # update caches (filepath and astng.file are not necessarily the
+ # same (.pyc pb))
+ self._cache[filepath] = self._cache[astng.file] = astng
+ return astng
+
+ def astng_from_class(self, klass, modname=None):
+ """get astng for the given class"""
+ if modname is None:
+ try:
+ modname = klass.__module__
+ except AttributeError:
+ raise ASTNGBuildingException(
+ 'Unable to get module for class %s' % klass)
+ modastng = self.astng_from_module_name(modname)
+ return modastng.getattr(klass.__name__)[0] # XXX
+
+ def project_from_files(self, files, func_wrapper=astng_wrapper,
+ project_name=None, black_list=None):
+ """return a Project from a list of files or modules"""
+ # insert current working directory to the python path to have a correct
+ # behaviour
+ sys.path.insert(0, os.getcwd())
+ try:
+ # build the project representation
+ project_name = project_name or self.config.project
+ black_list = black_list or self.config.black_list
+ project = Project(project_name)
+ for something in files:
+ if not exists(something):
+ fpath = file_from_modpath(something.split('.'))
+ elif isdir(something):
+ fpath = join(something, '__init__.py')
+ else:
+ fpath = something
+ astng = func_wrapper(self.astng_from_file, fpath)
+ if astng is None:
+ continue
+ project.path = project.path or astng.file
+ project.add_module(astng)
+ base_name = astng.name
+ # recurse in package except if __init__ was explicitly given
+ if astng.package and something.find('__init__') == -1:
+ # recurse on others packages / modules if this is a package
+ for fpath in get_module_files(dirname(astng.file),
+ black_list):
+ astng = func_wrapper(self.astng_from_file, fpath)
+ if astng is None or astng.name == base_name:
+ continue
+ project.add_module(astng)
+ return project
+ finally:
+ sys.path.pop(0)
+
+
+
+class Package:
+ """a package using a dictionary like interface
+
+ load submodules lazily, as they are needed
+ """
+
+ def __init__(self, path, name, manager):
+ self.name = name
+ self.path = abspath(path)
+ self.manager = manager
+ self.parent = None
+ self.lineno = 0
+ self.__keys = None
+ self.__subobjects = None
+
+ def fullname(self):
+ """return the full name of the package (i.e. prefix by the full name
+ of the parent package if any
+ """
+ if self.parent is None:
+ return self.name
+ return '%s.%s' % (self.parent.fullname(), self.name)
+
+ def get_subobject(self, name):
+ """method used to get sub-objects lazily : sub package or module are
+ only build once they are requested
+ """
+ if self.__subobjects is None:
+ try:
+ self.__subobjects = dict.fromkeys(self.keys())
+ except AttributeError:
+ # python <= 2.3
+ self.__subobjects = dict([(k, None) for k in self.keys()])
+ obj = self.__subobjects[name]
+ if obj is None:
+ objpath = join(self.path, name)
+ if isdir(objpath):
+ obj = Package(objpath, name, self.manager)
+ obj.parent = self
+ else:
+ modname = '%s.%s' % (self.fullname(), name)
+ obj = self.manager.astng_from_file(objpath + '.py', modname)
+ self.__subobjects[name] = obj
+ return obj
+
+ def get_module(self, modname):
+ """return the Module or Package object with the given name if any
+ """
+ path = modname.split('.')
+ if path[0] != self.name:
+ raise KeyError(modname)
+ obj = self
+ for part in path[1:]:
+ obj = obj.get_subobject(part)
+ return obj
+
+ def keys(self):
+ if self.__keys is None:
+ self.__keys = []
+ for fname in os.listdir(self.path):
+ if fname.endswith('.py'):
+ self.__keys.append(fname[:-3])
+ continue
+ fpath = join(self.path, fname)
+ if isdir(fpath) and exists(join(fpath, '__init__.py')):
+ self.__keys.append(fname)
+ self.__keys.sort()
+ return self.__keys[:]
+
+ def values(self):
+ return [self.get_subobject(name) for name in self.keys()]
+
+ def items(self):
+ return zip(self.keys(), self.values())
+
+ def has_key(self, name):
+ return bool(self.get(name))
+
+ def get(self, name, default=None):
+ try:
+ return self.get_subobject(name)
+ except KeyError:
+ return default
+
+ def __getitem__(self, name):
+ return self.get_subobject(name)
+ def __contains__(self, name):
+ return self.has_key(name)
+ def __iter__(self):
+ return iter(self.keys())
+
+
+class Project:
+ """a project handle a set of modules / packages"""
+ def __init__(self, name=''):
+ self.name = name
+ self.path = None
+ self.modules = []
+ self.locals = {}
+ self.__getitem__ = self.locals.__getitem__
+ self.__iter__ = self.locals.__iter__
+ self.values = self.locals.values
+ self.keys = self.locals.keys
+ self.has_key = self.locals.has_key
+
+ def add_module(self, node):
+ self.locals[node.name] = node
+ self.modules.append(node)
+
+ def get_module(self, name):
+ return self.locals[name]
+
+ def getChildNodes(self):
+ return self.modules
+
+ def __repr__(self):
+ return '<Project %r at %s (%s modules)>' % (self.name, id(self),
+ len(self.modules))