# demandimport.py - global demand-loading of modules for Mercurial
#
# Copyright 2006, 2007 Matt Mackall <[email protected]>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

'''
demandimport - automatic demandloading of modules

To enable this module, do:

 import demandimport; demandimport.enable()

Imports of the following forms will be demand-loaded:

 import a, b.c
 import a.b as c
 from a import b,c # a will be loaded immediately

These imports will not be delayed:

 from a import *
 b = __import__(a)
'''

import __builtin__
_origimport = __import__

class _demandmod(object):
   """module demand-loader and proxy"""
   def __init__(self, name, globals, locals):
       if '.' in name:
           head, rest = name.split('.', 1)
           after = [rest]
       else:
           head = name
           after = []
       object.__setattr__(self, "_data", (head, globals, locals, after))
       object.__setattr__(self, "_module", None)
   def _extend(self, name):
       """add to the list of submodules to load"""
       self._data[3].append(name)
   def _load(self):
       if not self._module:
           head, globals, locals, after = self._data
           mod = _origimport(head, globals, locals)
           # load submodules
           def subload(mod, p):
               h, t = p, None
               if '.' in p:
                   h, t = p.split('.', 1)
               if not hasattr(mod, h):
                   setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__))
               elif t:
                   subload(getattr(mod, h), t)

           for x in after:
               subload(mod, x)

           # are we in the locals dictionary still?
           if locals and locals.get(head) == self:
               locals[head] = mod
           object.__setattr__(self, "_module", mod)

   def __repr__(self):
       if self._module:
           return "<proxied module '%s'>" % self._data[0]
       return "<unloaded module '%s'>" % self._data[0]
   def __call__(self, *args, **kwargs):
       raise TypeError("%s object is not callable" % repr(self))
   def __getattribute__(self, attr):
       if attr in ('_data', '_extend', '_load', '_module'):
           return object.__getattribute__(self, attr)
       self._load()
       return getattr(self._module, attr)
   def __setattr__(self, attr, val):
       self._load()
       setattr(self._module, attr, val)

def _demandimport(name, globals=None, locals=None, fromlist=None, level=None):
   if not locals or name in ignore or fromlist == ('*',):
       # these cases we can't really delay
       if level is None:
           return _origimport(name, globals, locals, fromlist)
       else:
           return _origimport(name, globals, locals, fromlist, level)
   elif not fromlist:
       # import a [as b]
       if '.' in name: # a.b
           base, rest = name.split('.', 1)
           # email.__init__ loading email.mime
           if globals and globals.get('__name__', None) == base:
               return _origimport(name, globals, locals, fromlist)
           # if a is already demand-loaded, add b to its submodule list
           if base in locals:
               if isinstance(locals[base], _demandmod):
                   locals[base]._extend(rest)
               return locals[base]
       return _demandmod(name, globals, locals)
   else:
       if level is not None:
           # from . import b,c,d or from .a import b,c,d
           return _origimport(name, globals, locals, fromlist, level)
       # from a import b,c,d
       mod = _origimport(name, globals, locals)
       # recurse down the module chain
       for comp in name.split('.')[1:]:
           if not hasattr(mod, comp):
               setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__))
           mod = getattr(mod, comp)
       for x in fromlist:
           # set requested submodules for demand load
           if not(hasattr(mod, x)):
               setattr(mod, x, _demandmod(x, mod.__dict__, locals))
       return mod

ignore = [
   '_hashlib',
   '_xmlplus',
   'fcntl',
   'win32com.gen_py',
   'pythoncom',
   # imported by tarfile, not available under Windows
   'pwd',
   'grp',
   # imported by profile, itself imported by hotshot.stats,
   # not available under Windows
   'resource',
   ]

def enable():
   "enable global demand-loading of modules"
   __builtin__.__import__ = _demandimport

def disable():
   "disable global demand-loading of modules"
   __builtin__.__import__ = _origimport