"""Prototype of 'import' functionality enhanced to implement packages.

Why packages?  Packages enable module nesting and sibling module
imports.  'Til now, the python module namespace was flat, which
means every module had to have a unique name, in order to not
conflict with names of other modules on the load path.  Furthermore,
suites of modules could not be structurally affiliated with one
another.

With packages, a suite of, eg, email-oriented modules can include a
module named 'mailbox', without conflicting with the, eg, 'mailbox'
module of a shared-memory suite - 'email.mailbox' vs
'shmem.mailbox'.  Packages also enable modules within a suite to
load other modules within their package without having the package
name hard-coded.  Similarly, package suites of modules can be loaded
as a unit, by loading the package that contains them.

Usage: once installed (newimp.install(); newimp.revert() to revert to
the prior __import__ routine), 'import ...' and 'from ... import ...'
can be used to:

  - import modules from the search path, as before.

  - import modules from within other directory "packages" on the search
    path using a '.' dot-delimited nesting syntax.  The nesting is fully
    recursive.

    For example, 'import test.test_types' will import the test_types
    module within the 'test' package.  The calling environment would
    then access the module as 'test.test_types', which is the name of
    the fully-loaded 'test_types' module.  It is found contained within
    the stub (ie, only partially loaded) 'test' module, hence accessed as
    'test.test_types'.

  - import siblings from modules within a package, using '__.' as a shorthand
    prefix to refer to the parent package.  This enables referential
    transparency - package modules need not know their package name.

    The '__' package references are actually names assigned within
    modules, to refer to their containing package.  This means that
    variable references can be made to imported modules, or to variables
    defined via 'import ... from', also using the '__.var' shorthand
    notation.  This establishes a proper equivalence between the import
    reference '__.sibling' and the var reference '__.sibling'.  

  - import an entire package as a unit, by importing the package directory.
    If there is a module named '__main__.py' in the package, it controls the
    load.  Otherwise, all the modules in the dir, including packages, are
    inherently loaded into the package module's namespace.

    For example, 'import test' will load the modules of the entire 'test'
    package, at least until a test failure is encountered.

    In a package, a module with the name '__main__' has a special role.
    If present in a package directory, then it is loaded into the package
    module, instead of loading the contents of the directory.  This
    enables the __main__ module to control the load, possibly loading
    the entire directory deliberately (using 'import __', or even
    'from __ import *', to load all the module contents directly into the
    package module).

  - perform any combination of the above - have a package that contains
    packages, etc.

Modules have a few new attributes in support of packages.  As mentioned
above, '__' is a shorthand attribute denoting the modules' parent package,
also denoted in the module by '__package__'.  Additionally, modules have
associated with them a '__pkgpath__', a path by which sibling modules are
found."""

__version__ = "1.3"

# newimp.py,v 1.3 1995/04/07 09:06:50 guido Exp First release:
# Ken.Manheimer@nist.gov, 5-Apr-1995, for python 1.2

# Developers Notes:
#
# - 'sys.stub_modules' registers "incidental" (partially loaded) modules.
#   A stub module is promoted to the fully-loaded 'sys.modules' list when it is
#   explicitly loaded as a unit.
# - The __main__ loads of '__' have not yet been tested.
# - The test routines are cool, including a transient directory
#   hierarchy facility, and a means of skipping to later tests by giving
#   the test routine a numeric arg.
# - This could be substantially optimized, and there are many loose ends
#   lying around, since i wanted to get this released for 1.2.

VERBOSE = 0

import sys, string, regex, types, os, marshal, new, __main__
try:
    import imp				# Build on this recent addition
except ImportError:
    raise ImportError, 'Pkg import module depends on optional "imp" module'

from imp import SEARCH_ERROR, PY_SOURCE, PY_COMPILED, C_EXTENSION
PY_PACKAGE = 4				# In addition to above PY_*

modes = {SEARCH_ERROR: 'SEARCH_ERROR',
	 PY_SOURCE: 'PY_SOURCE',
	 PY_COMPILED: 'PY_COMPILED',
	 C_EXTENSION: 'C_EXTENSION',
	 PY_PACKAGE: 'PY_PACKAGE'}

# sys.stub_modules tracks modules partially loaded modules, ie loaded only
# incidental to load of nested components.

try: sys.stub_modules
except AttributeError:
    sys.stub_modules = {}

# Environment setup - "root" module, '__python__'

# Establish root package '__python__' in __main__ and newimp envs.

PKG_MAIN_NM = '__main__'		# 'pkg/__main__.py' master, if present.
PKG_NM = '__package__'			# Longhand for module's container.
PKG_SHORT_NM = '__'			# Shorthand for module's container.
PKG_SHORT_NM_LEN = len(PKG_SHORT_NM)
PKG_PATH = '__pkgpath__'		# Var holding package search path,
					# usually just the path of the pkg dir.
__python__ = __main__
sys.modules['__python__'] = __python__	# Register as an importable module.
__python__.__dict__[PKG_PATH] = sys.path

origImportFunc = None
def install():
    """Install newimp import_module() routine, for package support.

    newimp.revert() reverts to __import__ routine that was superceded."""
    import __builtin__
    global origImportFunc
    if not origImportFunc:
	try:
	    origImportFunc = __builtin__.__import__
	except AttributeError:
	    pass
    __builtin__.__import__ = import_module
    print 'Enhanced import functionality installed.'
def revert():
    """Revert to original __builtin__.__import__ func, if newimp.install() has
    been executed."""
    if origImportFunc:
	import __builtin__
	__builtin__.__import__ = origImportFunc
	print 'Original import routine back in place.'

def import_module(name,
		  envLocals=None, envGlobals=None,
		  froms=None,
		  inPkg=None):
    """Primary service routine implementing 'import' with package nesting."""

    # The job is divided into a few distinct steps:
    #
    # - Look for either an already loaded module or a file to be loaded.
    #   * if neither loaded module nor prospect file is found, raise an error.
    #   - If we have a file, not an already loaded module:
    #     - Load the file into a module.
    #     - Register the new module and intermediate package stubs.
    # (We have a module at this point...)
    # - Bind requested syms (module or specified 'from' defs) in calling env.
    # - Return the appropriate component.

    note("import_module: seeking '%s'%s" %
	 (name, ((inPkg and ' (in package %s)' % inPkg.__name__) or '')))

    # We need callers environment dict for local path and resulting module
    # binding.
    if not (envLocals or envGlobals):
	envLocals, envGlobals = exterior()

    modList = theMod = absNm = container = None

    # Get module obj if one already established, or else module file if not:

    if inPkg:
	# We've been invoked with a specific containing package:
	pkg, pkgPath, pkgNm = inPkg, inPkg.__dict__[PKG_PATH], inPkg.__name__
	relNm = name
	absNm = pkgNm + '.' + name
	
    elif name[:PKG_SHORT_NM_LEN+1] != PKG_SHORT_NM + '.':
	# name is NOT '__.something' - setup to seek according to specified
	# absolute name.
	pkg = __python__
	pkgPath = sys.path
	absNm = name
	relNm = absNm

    else:
	# name IS '__.' + something - setup to seek according to relative name,
	# in current package.

	relNm = name[len(PKG_SHORT_NM)+1:]	# Relative portion of name.
	try:
	    pkg = envGlobals[PKG_NM]	# The immediately containing package.
	    pkgPath = pkg.__dict__[PKG_PATH]
	    if pkg == __python__:	# At outermost package.
		absNm = relNm
	    else:
		absNm = (pkg.__name__ + '.' + relNm)
	except KeyError:		# Missing package, path, or name.
	    note("Can't identify parent package, package name, or pkgpath")
	    pass							# ==v

    # Try to find existing module:
    if sys.modules.has_key(absNm):
	note('found ' + absNm + ' already imported')
	theMod = sys.modules[absNm]
    else:
	# Try for builtin or frozen first:
	theMod = imp.init_builtin(absNm)
	if theMod:
	    note('found builtin ' + absNm)
	else:
	    theMod = imp.init_frozen(absNm)
	    if theMod:
		note('found frozen ' + absNm)
	if not theMod:
	    if type(pkgPath) == types.StringType:
		pkgPath = [pkgPath]
	    modList = find_module(relNm, pkgPath, absNm)
	    if not modList:
		raise ImportError, "module '%s' not found" % absNm	# ===X
	    # We have a list of successively nested files leading to the
	    # module, register them as stubs:
	    container = register_module_nesting(modList, pkg)

	    # Load from file if necessary and possible:
	    modNm, modf, path, ty = modList[-1]
	    note('found type ' + modes[ty[2]] + ' - ' + absNm)

	    # Do the load:
	    theMod = load_module(absNm, ty[2], modf, inPkg)

	    # Loaded successfully - promote module to full module status:
	    register_module(theMod, theMod.__name__, pkgPath, pkg)

    # Have a loaded module, impose designated components, and return
    # appropriate thing - according to guido:
    # "Note that for "from spam.ham import bacon" your function should
    #  return the object denoted by 'spam.ham', while for "import
    #  spam.ham" it should return the object denoted by 'spam' -- the
    #  STORE instructions following the import statement expect it this
    #  way."
    if not froms:
	# Establish the module defs in the importing name space:
	(envLocals or envGlobals)[name] = theMod
	return (container or theMod)
    else:
	# Implement 'from': Populate immediate env with module defs:
	if froms == '*':
	    froms = theMod.__dict__.keys()	# resolve '*'
	for item in froms:
	    (envLocals or envGlobals)[item] = theMod.__dict__[item]
	return theMod

def unload(module):
    """Remove registration for a module, so import will do a fresh load."""
    if type(module) == types.ModuleType:
	module = module.__name__
    for m in [sys.modules, sys.stub_modules]:
	try:
	    del m[module]
	except KeyError:
	    pass

def find_module(name, path, absNm=''):
    """Locate module NAME on PATH.  PATH is pathname string or a list of them.

    Note that up-to-date compiled versions of a module are preferred to plain
    source, and compilation is automatically performed when necessary and
    possible.

    Returns a list of the tuples returned by 'find_module_file' (cf), one for
    each nested level, deepest last."""

    checked = []			# For avoiding redundant dir lists.

    if not absNm: absNm = name

    # Parse name into list of nested components, 
    expNm = string.splitfields(name, '.')

    for curPath in path:

	if (type(curPath) != types.StringType) or (curPath in checked):
	    # Disregard bogus or already investigated path elements:
	    continue							# ==^
	else:
	    # Register it for subsequent disregard.
	    checked.append(curPath)

	if len(expNm) == 1:

	    # Non-nested module name:

	    got = find_module_file(curPath, absNm)
	    if got:
		note('using %s' % got[2], 2)
		return [got]						# ===>

	else:

	    # Composite name specifying nested module:

	    gotList = []; nameAccume = expNm[0]

	    got = find_module_file(curPath, nameAccume)
	    if not got:			# Continue to next prospective path.
		continue						# ==^
	    else:
		gotList.append(got)
		nm, file, fullPath, ty = got

	    # Work on successively nested components:
	    for component in expNm[1:]:
		# 'ty'pe of containing component must be package:
		if ty[2] != PY_PACKAGE:
		    gotList, got = [], None
		    break						# ==v^
		if nameAccume:
		    nameAccume = nameAccume + '.' + component
		else:
		    nameAccume = component
		got = find_module_file(fullPath, nameAccume)
		if got:
		    gotList.append(got)
		    # ** have to return the *full* name here:
		    nm, file, fullPath, ty = got
		else:
		    # Clear state vars:
		    gotList, got, nameAccume = [], None, ''
		    break						# ==v^
	    # Found nesting all the way to the specified tip:
	    if got:
		return gotList						# ===>

    # Failed.
    return None

def find_module_file(pathNm, modname):
    """Find module file given dir PATHNAME and module NAME.

    If successful, returns quadruple consisting of a mod name, file object,
    PATHNAME for the found file, and a description triple as contained in the
    list returned by get_suffixes.

    Otherwise, returns None.

    Note that up-to-date compiled versions of a module are preferred to plain
    source, and compilation is automatically performed, when necessary and
    possible."""

    relNm = string.splitfields(modname,'.')[-1]

    if pathNm[-1] != '/': pathNm = pathNm + '/'

    for suff, mode, ty in get_suffixes():
	note('trying ' + pathNm + relNm + suff + '...', 3)
	fullPath = pathNm + relNm + suff
	try:
	    modf = open(fullPath, mode)
	except IOError:
	    # ?? Skip unreadable ones.
	    continue							# ==^

	if ty == PY_PACKAGE:
	    # Enforce directory characteristic:
	    if not os.path.isdir(fullPath):
		note('Skipping non-dir match ' + fullPath)
		continue						# ==^
	    else:
		return (modname, modf, fullPath, (suff, mode, ty))	# ===>
	    

	elif ty == PY_SOURCE:
	    # Try for a compiled version:
	    note('found source ' + fullPath, 2)
	    pyc = fullPath + 'c'	# Sadly, we're presuming '.py' suff.
	    if (not os.path.exists(pyc) or
		(os.stat(fullPath)[8] > os.stat(pyc)[8])):
		# Try to compile:
		pyc = compile_source(fullPath, modf)
	    if pyc and (os.stat(fullPath)[8] < os.stat(pyc)[8]):
		# Either pyc was already newer or we just made it so; in either
		# case it's what we crave:
		return (modname, open(pyc, 'rb'), pyc,			# ===>
			('.pyc', 'rb', PY_COMPILED))
	    # Couldn't get a compiled version - return the source:
	    return (modname, modf, fullPath, (suff, mode, ty))		# ===>

	elif ty == PY_COMPILED:
	    # Make sure it is current, trying to compile if necessary, and
	    # prefer source failing that:
	    note('found compiled ' + fullPath, 2)
	    py = fullPath[:-1]		# Sadly again, presuming '.pyc' suff.
	    if not os.path.exists(py):
		note('found pyc sans py: ' + fullPath)
		return (modname, modf, fullPath, (suff, mode, ty))	# ===>
	    elif (os.stat(py)[8] > os.stat(fullPath)[8]):
		note('forced to try compiling: ' + py)
		pyc = compile_source(py, modf)
		if pyc:
		    return (modname, modf, fullPath, (suff, mode, ty))	# ===>
		else:
		    note('failed compile - must use more recent .py')
		    return (modname,					# ===>
			    open(py, 'r'), py, ('.py', 'r', PY_SOURCE))
	    else:
		return (modname, modf, fullPath, (suff, mode, ty))	# ===>

	elif ty == C_EXTENSION:
	    note('found extension ' + fullPath, 2)
	    return (modname, modf, fullPath, (suff, mode, ty))		# ===>

	else:
	    raise SystemError, 'Unanticipated (new?) module type encountered'

    return None


def load_module(name, ty, theFile, fromMod=None):
    """Load module NAME, type TYPE, from file FILE.

    Optional arg fromMod indicated the module from which the load is being done
    - necessary for detecting import of __ from a package's __main__ module.

    Return the populated module object."""

    # Note: we mint and register intermediate package directories, as necessary
    
    # Determine packagepath extension:

    # Establish the module object in question:
    theMod = procure_module(name)
    nameTail = string.splitfields(name, '.')[-1]
    thePath = theFile.name

    if ty == PY_SOURCE:
	exec_into(theFile, theMod, theFile.name)

    elif ty == PY_COMPILED:
	pyc = open(theFile.name, 'rb').read()
	if pyc[0:4] != imp.get_magic():
	    raise ImportError, 'bad magic number: ' + theFile.name	# ===>
	code = marshal.loads(pyc[8:])
	exec_into(code, theMod, theFile.name)

    elif ty == C_EXTENSION:
	try:
	    theMod = imp.load_dynamic(nameTail, thePath, theFile)
	except:
	    # ?? Ok to embellish the error message?
	    raise sys.exc_type, ('%s (from %s)' %
				 (str(sys.exc_value), theFile.name))

    elif ty == PY_PACKAGE:
	# Load constituents:
	if (os.path.exists(thePath + '/' + PKG_MAIN_NM) and
	    # pkg has a __main__, and this import not already from __main__, so
	    # __main__ can 'import __', or even better, 'from __ import *'
	    ((theMod.__name__ != PKG_MAIN_NM) and (fromMod.__ == theMod))):
	    exec_into(thePath + '/' + PKG_MAIN_NM, theMod, theFile.name)
	else:
	    # ... or else recursively load constituent modules.
	    prospects = mod_prospects(thePath)
	    for item in prospects:
		theMod.__dict__[item] = import_module(item,
						      theMod.__dict__,
						      theMod.__dict__,
						      None,
						      theMod)
		
    else:
	raise ImportError, 'Unimplemented import type: %s' % ty		# ===>
	
    return theMod
    
def exec_into(obj, module, path):
    """Helper for load_module, execfile/exec path or code OBJ within MODULE."""

    # This depends on ability of exec and execfile to mutilate, erhm, mutate
    # the __dict__ of a module.  It will not work if/when this becomes
    # disallowed, as it is for normal assignments.

    try:
	if type(obj) == types.FileType:
	    execfile(path, module.__dict__, module.__dict__)
	elif type(obj) in [types.CodeType, types.StringType]:
	    exec obj in module.__dict__, module.__dict__
    except:
	# ?? Ok to embellish the error message?
	raise sys.exc_type, ('%s (from %s)' %
			     (str(sys.exc_value), path))
	

def mod_prospects(path):
    """Return a list of prospective modules within directory PATH.

    We actually return the distinct names resulting from stripping the dir
    entries (excluding '.' and '..') of their suffixes (as represented by
    'get_suffixes').

    (Note that matches for the PY_PACKAGE type with null suffix are
    implicitly constrained to be directories.)"""

    # We actually strip the longest matching suffixes, so eg 'dbmmodule.so'
    # mates with 'module.so' rather than '.so'.

    dirList = os.listdir(path)
    excludes = ['.', '..']
    sortedSuffs = sorted_suffixes()
    entries = []
    for item in dirList:
	if item in excludes: continue					# ==^
	for suff in sortedSuffs:
	    sub = -1 * len(suff)
	    if sub == 0:
		if os.path.isdir(os.path.join(path, item)):
		    entries.append(item)
	    elif item[sub:] == suff:
		it = item[:sub]
		if not it in entries:
		    entries.append(it)
		break							# ==v^
    return entries
		


def procure_module(name):
    """Return an established or else new module object having NAME.

    First checks sys.modules, then sys.stub_modules."""

    if sys.modules.has_key(name):
	it = sys.modules[name]
    elif sys.stub_modules.has_key(name):
	it = sys.stub_modules[name]
    else:
	it = new.module(name)
    return it								# ===>

def register_module_nesting(modList, pkg):
    """Given a find_module()-style NESTING and a parent PACKAGE, register
    components as stub modules."""
    container = None
    for stubModNm, stubModF, stubPath, stubTy in modList:
	relStubNm = string.splitfields(stubModNm, '.')[-1]
	if sys.modules.has_key(stubModNm):
	    # Nestle in containing package:
	    stubMod = sys.modules[stubModNm]
	    pkg.__dict__[relStubNm] = stubMod
	    pkg = stubMod	# will be parent for next in sequence.
	elif sys.stub_modules.has_key(stubModNm):
	    stubMod = sys.stub_modules[stubModNm]
	    pkg.__dict__[relStubNm] = stubMod
	    pkg = stubMod
	else:
	    stubMod = procure_module(stubModNm)
	    # Register as a stub:
	    register_module(stubMod, stubModNm, stubPath, pkg, 1)
	    pkg.__dict__[relStubNm] = stubMod
	    pkg = stubMod
	if not container:
	    container = stubMod
    return container

def register_module(theMod, name, path, package, stub=0):
    """Properly register MODULE, w/ name, path, package, opt, as stub."""
    
    if stub:
	sys.stub_modules[name] = theMod
    else:
	sys.modules[name] = theMod
	if sys.stub_modules.has_key(name):
	    del sys.stub_modules[name]
    theMod.__ = theMod.__dict__[PKG_NM] = package
    theMod.__dict__[PKG_PATH] = path


def compile_source(sourcePath, sourceFile):
    """Given python code source path and file obj, Create a compiled version.

    Return path of compiled version, or None if file creation is not
    successful.  (Compilation errors themselves are passed without restraint.)

    This is an import-private interface, and not well-behaved for general use.
    
    In particular, we presume the validity of the sourcePath, and that it
    includes a '.py' extension."""

    compiledPath = sourcePath[:-3] + '.pyc'
    try:
	compiledFile = open(compiledPath, 'wb')
    except IOError:
	note("write permission denied to " + compiledPath)
	return None
    mtime = os.stat(sourcePath)[8]
    sourceFile.seek(0)			# rewind
    try:
	compiledFile.write(imp.get_magic())		# compiled magic number
	compiledFile.seek(8, 0)				# mtime space holder
	# We let compilation errors go their own way...
	compiled = compile(sourceFile.read(), sourcePath, 'exec')
	marshal.dump(compiled, compiledFile)		# write the code obj
	compiledFile.seek(4, 0)				# position for mtime
	compiledFile.write(marshal.dumps(mtime)[1:])	# register mtime
	compiledFile.flush()
	compiledFile.close()
	return compiledPath
    except IOError:
	return None


def PathExtension(locals, globals):	# Probably obsolete.
    """Determine import search path extension vis-a-vis __pkgpath__ entries.

    local dict __pkgpath__ will preceed global dict __pkgpath__."""

    pathadd = []
    if globals and globals.has_key(PKG_PATH):
	pathadd = PrependPath(pathadd, globals[PKG_PATH], 'global')
    if locals and locals.has_key(PKG_PATH):
	pathadd = PrependPath(pathadd, locals[PKG_PATH], 'local')
    if pathadd:
	note(PKG_PATH + ' extension: ' + pathadd)
    return pathadd

def PrependPath(path, pre, whence):	# Probably obsolete
    """Return copy of PATH list with string or list-of-strings PRE prepended.

    If PRE is neither a string nor list-of-strings, print warning that
    locality WHENCE has malformed value."""

    # (There is probably a better way to handle malformed PREs, but raising an
    # error seems too severe - in that case, a bad setting for
    # sys.__pkgpath__ would prevent any imports!)

    if type(pre) == types.StringType: return [pre] + path[:]		# ===>
    elif type(pre) == types.ListType: return pre + path[:]		# ===>
    else:
	print "**Ignoring '%s' bad %s value**" % (whence, PKG_PATH)
	return path							# ===>

got_suffixes = None
def get_suffixes():
    """Produce a list of triples, each describing a type of import file.

    Triples have the form '(SUFFIX, MODE, TYPE)', where:

    SUFFIX is a string found appended to a module name to make a filename for
    that type of import file.

    MODE is the mode string to be passed to the built-in 'open' function - "r"
    for text files, "rb" for binary.

    TYPE is the file type:

     PY_SOURCE:		python source code,
     PY_COMPILED:	byte-compiled python source,
     C_EXTENSION:	compiled-code object file,
     PY_PACKAGE:	python library directory, or
     SEARCH_ERROR:	no module found. """

    # Note: sorted_suffixes() depends on this function's value being invariant.
    # sorted_suffixes() must be revised if this becomes untrue.
    
    global got_suffixes

    if got_suffixes:
	return got_suffixes
    else:
	# Ensure that the .pyc suffix precedes the .py:
	got_suffixes = [('', 'r', PY_PACKAGE)]
	py = pyc = None
	for suff in imp.get_suffixes():
	    if suff[0] == '.py':
		py = suff
	    elif suff[0] == '.pyc':
		pyc = suff
	    else:
		got_suffixes.append(suff)
	got_suffixes.append(pyc)
	got_suffixes.append(py)
	return got_suffixes
		

sortedSuffs = []			# State vars for sorted_suffixes().  Go
def sorted_suffixes():
    """Helper function ~efficiently~ tracks sorted list of module suffixes."""

    # Produce sortedSuffs once - this presumes that get_suffixes does not
    # change from call to call during a python session.  Needs to be
    # corrected if that becomes no longer true.

    global sortedsuffs
    if not sortedSuffs:			# do compute only the "first" time
	for item in get_suffixes():
	    sortedSuffs.append(item[0])
	# Sort them in descending order:
	sortedSuffs.sort(lambda x, y: (((len(x) > len(y)) and 1) or
				       ((len(x) < len(y)) and -1)))
	sortedSuffs.reverse()
    return sortedSuffs


# exterior(): Utility routine, obtain local and global dicts of environment
#	      containing/outside the callers environment, ie that of the
#	      caller's caller.  Routines can use exterior() to determine the
#	      environment from which they were called. 

def exterior():
    """Return dyad containing locals and globals of caller's caller.

    Locals will be None if same as globals, ie env is global env."""

    bogus = 'bogus'			# A locally usable exception
    try: raise bogus			# Force an exception object
    except bogus:
	at = sys.exc_traceback.tb_frame.f_back		# The external frame.
	if at.f_back: at = at.f_back			# And further, if any.
	globals, locals = at.f_globals, at.f_locals
	if locals == globals:				# Exterior is global?
	    locals = None
	return (locals, globals)

#########################################################################
#			      TESTING FACILITIES			#

def note(msg, threshold=1):
    if VERBOSE >= threshold: sys.stderr.write('(import: ' + msg + ')\n')

class TestDirHier:
    """Populate a transient directory hierarchy according to a definition
    template - so we can create package/module hierarchies with which to
    exercise the new import facilities..."""

    def __init__(self, template, where='/var/tmp'):
	"""Establish a dir hierarchy, according to TEMPLATE, that will be
	deleted upon deletion of this object (or deliberate invocation of the
	__del__ method)."""
	self.PKG_NM = 'tdh_'
	rev = 0
	while os.path.exists(os.path.join(where, self.PKG_NM+str(rev))):
	    rev = rev + 1
	sys.exc_traceback = None	# Ensure Discard of try/except obj ref
	self.PKG_NM = self.PKG_NM + str(rev)
	self.root = os.path.join(where, self.PKG_NM)
	self.createDir(self.root)
	self.add(template)

    def __del__(self):
	"""Cleanup the test hierarchy."""
	self.remove()
    def add(self, template, root=None):
	"""Populate directory according to template dictionary.

	Keys indicate file names, possibly directories themselves.

	String values dictate contents of flat files.

	Dictionary values dictate recursively embedded dictionary templates."""
	if root == None: root = self.root
	for key, val in template.items():
	    name = os.path.join(root, key)
	    if type(val) == types.StringType:	# flat file
		self.createFile(name, val)
	    elif type(val) == types.DictionaryType:	# embedded dir
		self.createDir(name)
		self.add(val, name)
	    else:
		raise ValueError, 'invalid file-value type, %s' % type(val)
    def remove(self, name=''):
	"""Dispose of the NAME (or keys in dictionary), using 'rm -r'."""
	name = os.path.join(self.root, name)
	sys.exc_traceback = None	# Ensure Discard of try/except obj ref
	if os.path.exists(name):
	    print '(TestDirHier: deleting %s)' % name
	    os.system('rm -r ' + name)
	else:
	    raise IOError, "can't remove non-existent " + name
    def createFile(self, name, contents=None):
	"""Establish file NAME with CONTENTS.

	If no contents specfied, contents will be 'print NAME'."""
	f = open(name, 'w')
	if not contents:
	    f.write("print '" + name + "'\n")
	else:
	    f.write(contents)
	f.close
    def createDir(self, name):
	"""Create dir with NAME."""
	return os.mkdir(name, 0755)

skipToTest = 0
atTest = 1
def testExec(msg, execList, locals, globals):
    global skipToTest, atTest
    print 'Import Test:', '(' + str(atTest) + ')', msg, '...'
    atTest = atTest + 1
    if skipToTest > (atTest - 1):
	print ' ... skipping til test', skipToTest
	return
    else:
	print ''
    for stmt in execList:
	exec stmt in locals, globals

def test(number=0):
    """Exercise import functionality, creating a transient dir hierarchy for
    the purpose.

    We actually install the new import functionality, temporarily, resuming the
    existing function on cleanup."""

    import __builtin__

    global skipToTest, atTest
    skipToTest = number
    hier = None

    def confPkgVars(mod, locals, globals):
	if not sys.modules.has_key(mod):
	    print 'import test: missing module "%s"' % mod
	else:
	    modMod = sys.modules[mod]
	    if not modMod.__dict__.has_key(PKG_SHORT_NM):
		print ('import test: module "%s" missing %s pkg shorthand' %
		       (mod, PKG_SHORT_NM))
	    if not modMod.__dict__.has_key(PKG_PATH):
		print ('import test: module "%s" missing %s package path' %
		       (mod, PKG_PATH))
    def unloadFull(mod):
	"""Unload module and offspring submodules, if any."""
	modMod = ''
	if type(mod) == types.StringType:
	    modNm = mod
	elif type(mod) == types.ModuleType:
	    modNm = modMod.__name__
	for subj in sys.modules.keys() + sys.stub_modules.keys():
	    if subj[0:len(modNm)] == modNm:
		unload(subj)

    # First, get the globals and locals to pass to our testExec():
    exec 'import ' + __name__
    globals, locals = eval(__name__ + '.__dict__'), vars()

    try:
	__main__.testMods
    except AttributeError:
	__main__.testMods = []
    testMods = __main__.testMods
	

    # Install the newimp routines, within a try/finally:
    try:
	sys.exc_traceback = None
	wasImport = __builtin__.__import__	# Stash default
	wasPath = sys.path
    except AttributeError:
	wasImport = None
    try:
	hiers = []; modules = []
	global VERBOSE
	wasVerbose, VERBOSE = VERBOSE, 2
	__builtin__.__import__ = import_module	# Install new version

	if testMods:		# Clear out imports from previous tests
	    for m in testMods[:]:
		unloadFull(m)
		testMods.remove(m)

	testExec("already imported module: %s" % sys.modules.keys()[0],
		 ['import ' + sys.modules.keys()[0]],
		 locals, globals)
	try:
	    no_sirree = 'no_sirree_does_not_exist'
	    testExec("non-existent module: %s" % no_sirree,
		     ['import ' + no_sirree],
		     locals, globals)
	except ImportError:
	    testExec("ok", ['pass'], locals, globals)
	got = None
	for mod in ['Complex', 'UserDict', 'UserList', 'calendar',
		    'cmd', 'dis', 'mailbox', 'profile', 'random', 'rfc822']:
	    if not (mod in sys.modules.keys()):
		got = mod
		break							# ==v
	if got:
	    testExec("not-yet loaded module: %s" % mod,
		     ['import ' + mod, 'modules.append(got)'],
		     locals, globals)
	else:
	    print "Import Test: couldn't find unimported module from list"

	# Now some package stuff.
	
	# First change the path to include our temp dir, copying so the
	# addition can be revoked on cleanup in the finally, below:
	sys.path = ['/var/tmp'] + sys.path[:]
	# Now create a trivial package:
	stmts = ["hier1 = TestDirHier({'a.py': 'print \"a.py executing\"'})",
		 "hiers.append(hier1)",
		 "root = hier1.PKG_NM",
		 "exec 'import ' + root",
		 "testMods.append(root)",
		 "confPkgVars(sys.modules[root].__name__, locals, globals)",
		 "confPkgVars(sys.modules[root].__name__+'.a',locals,globals)"]
	testExec("trivial package, with one module, a.py",
		 stmts, locals, globals)
	# Slightly less trivial package - reference to '__':
	stmts = [("hier2 = TestDirHier({'ref.py': 'print \"Pkg __:\", __'})"),
		 "root = hier2.PKG_NM",
		 "hiers.append(hier2)",
		 "exec 'import ' + root",
		 "testMods.append(root)"]
	testExec("trivial package, with module that has pkg shorthand ref",
		 stmts, locals, globals)
	# Nested package, plus '__' references:

	complexTemplate = {'ref.py': 'print "ref.py loading..."',
			    'suite': {'s1.py': 'print "s1.py, in pkg:", __',
				      'subsuite': {'sub1.py':
						   'print "sub1.py"'}}}
	stmts = [('print """%s\n%s\n%s\n%s\n%s\n%s"""' %
		  ('.../',
		   '    ref.py\t\t\t"ref.py loading..."',
		   '    suite/',
		   '	    s1.py \t\t"s1.py, in pkg: xxxx.suite"',
		   '	    subsuite/',
		   '		sub1.py		"sub1.py" ')),
		 "hier3 = TestDirHier(complexTemplate)",
		 "root = hier3.PKG_NM",
		 "hiers.append(hier3)",
		 "exec 'import ' + root",
		 "testMods.append(root)"]
	testExec("Significantly nestled package:",
		 stmts, locals, globals)

	# Now try to do an embedded sibling import, using '__' shorthand -
	# alter our complexTemplate for a new dirHier:
	complexTemplate['suite']['s1.py'] = 'import __.subsuite'
	stmts = ["hier4 = TestDirHier(complexTemplate)",
		 "root = hier4.PKG_NM",
		 "testMods.append(root)",
		 "hiers.append(hier4)",
		 "exec 'import %s.suite.s1' % root",
		 "testMods.append(root)"]
	testExec("Similar structure, but suite/s1.py imports '__.subsuite'",
		 stmts, locals, globals)

	sys.exc_traceback = None	# Signify clean conclusion.

    finally:
	if sys.exc_traceback:
	    print ' ** Import test FAILURE... cleanup.'
	else:
	    print ' Import test SUCCESS... cleanup'
	VERBOSE = wasVerbose
	skipToTest = 0
	atTest = 1
	sys.path = wasPath
	for h in hiers: h.remove(); del h	# Dispose of test directories
	if wasImport:				# Resurrect prior routine
	    __builtin__.__import__ = wasImport
	else:
	    del __builtin__.__import__

if __name__ == '__main__':
	test()
