Initial revision
diff --git a/Lib/ni.py b/Lib/ni.py
new file mode 100644
index 0000000..70b1c22
--- /dev/null
+++ b/Lib/ni.py
@@ -0,0 +1,390 @@
+"""New import scheme with package support.
+
+A Package is a module that can contain other modules.  Packages can be
+nested.  Package introduce dotted names for modules, like P.Q.M, which
+could correspond to a file P/Q/M.py found somewhere on sys.path.  It
+is possible to import a package itself, though this makes little sense
+unless the package contains a module called __init__.
+
+A package has two variables that control the namespace used for
+packages and modules, both initialized to sensible defaults the first
+time the package is referenced.
+
+(1) A package's *module search path*, contained in the per-package
+variable __path__, defines a list of *directories* where submodules or
+subpackages of the package are searched.  It is initialized to the
+directory containing the package.  Setting this variable to None makes
+the module search path default to sys.path (this is not quite the same
+as setting it to sys.path, since the latter won't track later
+assignments to sys.path).
+
+(2) A package's *import domain*, contained in the per-package variable
+__domain__, defines a list of *packages* that are searched (using
+their respective module search paths) to satisfy imports.  It is
+initialized to the list cosisting of the package itself, its parent
+package, its parent's parent, and so on, ending with the root package
+(the nameless package containing all top-level packages and modules,
+whose module search path is None, implying sys.path).
+
+The default domain implements a search algorithm called "expanding
+search".  An alternative search algorithm called "explicit search"
+fixes the import search path to contain only the root package,
+requiring the modules in the package to name all imported modules by
+their full name.  The convention of using '__' to refer to the current
+package (both as a per-module variable and in module names) can be
+used by packages using explicit search to refer to modules in the same
+package; this combination is known as "explicit-relative search".
+
+The PackageImporter and PackageLoader classes together implement the
+following policies:
+
+- There is a root package, whose name is ''.  It cannot be imported
+  directly but may be referenced, e.g. by using '__' from a top-level
+  module.
+
+- In each module or package, the variable '__' contains a reference to
+  the parent package; in the root package, '__' points to itself.
+
+- In the name for imported modules (e.g. M in "import M" or "from M
+  import ..."), a leading '__' refers to the current package (i.e.
+  the package containing the current module); leading '__.__' and so
+  on refer to the current package's parent, and so on.  The use of
+  '__' elsewhere in the module name is not supported.
+
+- Modules are searched using the "expanding search" algorithm by
+  virtue of the default value for __domain__.
+
+- If A.B.C is imported, A is searched using __domain__; then
+  subpackage B is searched in A using its __path__, and so on.
+
+- Built-in modules have priority: even if a file sys.py exists in a
+  package, "import sys" imports the built-in sys module.
+
+- The same holds for frozen modules, for better or for worse.
+
+- Submodules and subpackages are not automatically loaded when their
+  parent packages is loaded.
+
+- The construct "from package import *" is illegal.  (It can still be
+  used to import names from a module.)
+
+- When "from package import module1, module2, ..." is used, those
+    modules are explicitly loaded.
+
+- When a package is loaded, if it has a submodule __init__, that
+  module is loaded.  This is the place where required submodules can
+  be loaded, the __path__ variable extended, etc.  The __init__ module
+  is loaded even if the package was loaded only in order to create a
+  stub for a sub-package: if "import P.Q.R" is the first reference to
+  P, and P has a submodule __init__, P.__init__ is loaded before P.Q
+  is even searched.
+
+Caveats:
+
+- It is possible to import a package that has no __init__ submodule;
+  this is not particularly useful but there may be useful applications
+  for it (e.g. to manipulate its search paths from the outside!).
+
+- There are no special provisions for os.chdir().  If you plan to use
+  os.chdir() before you have imported all your modules, it is better
+  not to have relative pathnames in sys.path.  (This could actually be
+  fixed by changing the implementation of path_join() in the hook to
+  absolutize paths.)
+
+- Packages and modules are introduced in sys.modules as soon as their
+  loading is started.  When the loading is terminated by an exception,
+  the sys.modules entries remain around.
+
+- There are no special measures to support mutually recursive modules,
+  but it will work under the same conditions where it works in the
+  flat module space system.
+
+- Sometimes dummy entries (whose value is None) are entered in
+  sys.modules, to indicate that a particular module does not exist --
+  this is done to speed up the expanding search algorithm when a
+  module residing at a higher level is repeatedly imported (Python
+  promises that importing a previously imported module is cheap!)
+
+- Although dynamically loaded extensions are allowed inside packages,
+  the current implementation (hardcoded in the interpreter) of their
+  initialization may cause problems if an extension invokes the
+  interpreter during its initialization.
+
+- reload() may find another version of the module only if it occurs on
+  the package search path.  Thus, it keeps the connection to the
+  package to which the module belongs, but may find a different file.
+
+XXX Need to have an explicit name for '', e.g. '__root__'.
+
+"""
+
+
+import imp
+import string
+import sys
+import __builtin__
+
+import ihooks
+from ihooks import ModuleLoader, ModuleImporter
+
+
+class PackageLoader(ModuleLoader):
+
+    """A subclass of ModuleLoader with package support.
+
+    find_module_in_dir() will succeed if there's a subdirectory with
+    the given name; load_module() will create a stub for a package and
+    load its __init__ module if it exists.
+
+    """
+
+    def find_module_in_dir(self, name, dir):
+	if dir is not None:
+	    dirname = self.hooks.path_join(dir, name)
+	    if self.hooks.path_isdir(dirname):
+		return None, dirname, ('', '', 'PACKAGE')
+	return ModuleLoader.find_module_in_dir(self, name, dir)
+
+    def load_module(self, name, stuff):
+	file, filename, info = stuff
+	suff, mode, type = info
+	if type == 'PACKAGE':
+	    return self.load_package(name, stuff)
+	if sys.modules.has_key(name):
+	    m = sys.modules[name]
+	else:
+	    sys.modules[name] = m = imp.new_module(name)
+	self.set_parent(m)
+	if type == imp.C_EXTENSION and '.' in name:
+	    return self.load_dynamic(name, stuff)
+	else:
+	    return ModuleLoader.load_module(self, name, stuff)
+
+    def load_dynamic(self, name, stuff):
+	file, filename, (suff, mode, type) = stuff
+	# Hack around restriction in imp.load_dynamic()
+	i = string.rfind(name, '.')
+	tail = name[i+1:]
+	if sys.modules.has_key(tail):
+	    save = sys.modules[tail]
+	else:
+	    save = None
+	sys.modules[tail] = imp.new_module(name)
+	try:
+	    m = imp.load_dynamic(tail, filename, file)
+	finally:
+	    if save:
+		sys.modules[tail] = save
+	    else:
+		del sys.modules[tail]
+	sys.modules[name] = m
+	return m
+
+    def load_package(self, name, stuff):
+	file, filename, info = stuff
+	if sys.modules.has_key(name):
+	    package = sys.modules[name]
+	else:
+	    sys.modules[name] = package = imp.new_module(name)
+	package.__path__ = [filename]
+	self.init_package(package)
+	return package
+
+    def init_package(self, package):
+	self.set_parent(package)
+	self.set_domain(package)
+	self.call_init_module(package)
+
+    def set_parent(self, m):
+	name = m.__name__
+	if '.' in name:
+	    name = name[:string.rfind(name, '.')]
+	else:
+	    name = ''
+	m.__ = sys.modules[name]
+
+    def set_domain(self, package):
+	name = package.__name__
+	package.__domain__ = domain = [name]
+	while '.' in name:
+	    name = name[:string.rfind(name, '.')]
+	    domain.append(name)
+	if name:
+	    domain.append('')
+
+    def call_init_module(self, package):
+	stuff = self.find_module('__init__', package.__path__)
+	if stuff:
+	    m = self.load_module(package.__name__ + '.__init__', stuff)
+	    package.__init__ = m
+
+
+class PackageImporter(ModuleImporter):
+
+    """Importer that understands packages and '__'."""
+
+    def __init__(self, loader = None, verbose = 0):
+	ModuleImporter.__init__(self,
+	loader or PackageLoader(None, verbose), verbose)
+
+    def import_module(self, name, globals={}, locals={}, fromlist=[]):
+	if globals.has_key('__'):
+	    package = globals['__']
+	else:
+	    # No calling context, assume in root package
+	    package = sys.modules['']
+	if name[:3] in ('__.', '__'):
+	    p = package
+	    name = name[3:]
+	    while name[:3] in ('__.', '__'):
+		p = package.__
+		name = name[3:]
+	    if not name:
+		return self.finish(package, p, '', fromlist)
+	    if '.' in name:
+		i = string.find(name, '.')
+		name, tail = name[:i], name[i:]
+	    else:
+		tail = ''
+	    mname = p.__name__ and p.__name__+'.'+name or name
+	    m = self.get1(mname)
+	    return self.finish(package, m, tail, fromlist)
+	if '.' in name:
+	    i = string.find(name, '.')
+	    name, tail = name[:i], name[i:]
+	else:
+	    tail = ''
+	for pname in package.__domain__:
+	    mname = pname and pname+'.'+name or name
+	    m = self.get0(mname)
+	    if m: break
+	else:
+	    raise ImportError, "No such module %s" % name
+	return self.finish(m, m, tail, fromlist)
+
+    def finish(self, module, m, tail, fromlist):
+	# Got ....A; now get ....A.B.C.D
+	yname = m.__name__
+	if tail and sys.modules.has_key(yname + tail): # Fast path
+	    yname, tail = yname + tail, ''
+	    m = self.get1(yname)
+	while tail:
+	    i = string.find(tail, '.', 1)
+	    if i > 0:
+		head, tail = tail[:i], tail[i:]
+	    else:
+		head, tail = tail, ''
+	    yname = yname + head
+	    m = self.get1(yname)
+
+	# Got ....A.B.C.D; now finalize things depending on fromlist
+	if not fromlist:
+	    return module
+	if '__' in fromlist:
+	    raise ImportError, "Can't import __ from anywhere"
+	if not hasattr(m, '__path__'): return m
+	if '*' in fromlist:
+	    raise ImportError, "Can't import * from a package"
+	for f in fromlist:
+	    if hasattr(m, f): continue
+	    fname = yname + '.' + f
+	    self.get1(fname)
+	return m
+
+    def get1(self, name):
+	m = self.get(name)
+	if not m:
+	    raise ImportError, "No module named %s" % name
+	return m
+
+    def get0(self, name):
+	m = self.get(name)
+	if not m:
+	    sys.modules[name] = None
+	return m
+
+    def get(self, name):
+	# Internal routine to get or load a module when its parent exists
+	if sys.modules.has_key(name):
+	    return sys.modules[name]
+	if '.' in name:
+	    i = string.rfind(name, '.')
+	    head, tail = name[:i], name[i+1:]
+	else:
+	    head, tail = '', name
+	path = sys.modules[head].__path__
+	stuff = self.loader.find_module(tail, path)
+	if not stuff:
+	    return None
+	sys.modules[name] = m = self.loader.load_module(name, stuff)
+	if head:
+	    setattr(sys.modules[head], tail, m)
+	return m
+
+    def reload(self, module):
+	name = module.__name__
+	if '.' in name:
+	    i = string.rfind(name, '.')
+	    head, tail = name[:i], name[i+1:]
+	    path = sys.modules[head].__path__
+	else:
+	    tail = name
+	    path = sys.modules[''].__path__
+	stuff = self.loader.find_module(tail, path)
+	if not stuff:
+	    raise ImportError, "No module named %s" % name
+	return self.loader.load_module(name, stuff)
+
+    def unload(self, module):
+	if hasattr(module, '__path__'):
+	    raise ImportError, "don't know how to unload packages yet"
+	PackageImporter.unload(self, module)
+
+    def install(self):
+	if not sys.modules.has_key(''):
+	    sys.modules[''] = package = imp.new_module('')
+	    package.__path__ = None
+	    self.loader.init_package(package)
+	    for m in sys.modules.values():
+		if not m: continue
+		if not hasattr(m, '__'):
+		    self.loader.set_parent(m)
+	ModuleImporter.install(self)
+
+
+def install(v = 0):
+    ihooks.install(PackageImporter(None, v))
+
+def uninstall():
+    ihooks.uninstall()
+
+def ni(v = 0):
+    install(v)
+
+def no():
+    uninstall()
+
+def test():
+    import pdb
+    try:
+	testproper()
+    except:
+	sys.last_type, sys.last_value, sys.last_traceback = (
+		sys.exc_type, sys.exc_value, sys.exc_traceback)
+	print
+	print sys.last_type, ':', sys.last_value
+	print
+	pdb.pm()
+
+def testproper():
+    install(1)
+    try:
+	import mactest
+	print dir(mactest)
+	raw_input('OK?')
+    finally:
+	uninstall()
+
+
+if __name__ == '__main__':
+    test()