Support loading of packages.  (I had this coded up for a while but
didn't want to commit until it had been tested.  I presume that it
works in Grail.)
diff --git a/Lib/ihooks.py b/Lib/ihooks.py
index 3675978..5c9adfe 100644
--- a/Lib/ihooks.py
+++ b/Lib/ihooks.py
@@ -8,18 +8,17 @@
 the built-in function __import__ in order to change the semantics of
 the import statement, until now it has been difficult to combine the
 effect of different __import__ hacks, like loading modules from URLs
-(rimport.py), implementing a hierarchical module namespace (newimp.py)
-or restricted execution (rexec.py).
+by rimport.py, or restricted execution by rexec.py.
 
 This module defines three new concepts:
 
-(1) A "file system hooks" class provides an interface to a filesystem.
+1) A "file system hooks" class provides an interface to a filesystem.
 
 One hooks class is defined (Hooks), which uses the interface provided
 by standard modules os and os.path.  It should be used as the base
 class for other hooks classes.
 
-(2) A "module loader" class provides an interface to to search for a
+2) A "module loader" class provides an interface to to search for a
 module in a search path and to load it.  It defines a method which
 searches for a module in a single directory; by overriding this method
 one can redefine the details of the search.  If the directory is None,
@@ -31,7 +30,7 @@
 uses a file system hooks class to interact with the file system.  Both
 use the imp module's load_* interfaces to actually load the module.
 
-(3) A "module importer" class provides an interface to import a
+3) A "module importer" class provides an interface to import a
 module, as well as interfaces to reload and unload a module.  It also
 provides interfaces to install and uninstall itself instead of the
 default __import__ and reload (and unload) functions.
@@ -49,9 +48,6 @@
 by the way the __import__ hook is used by the Python interpreter.)  It
 would also do wise to install a different version of reload().
 
-XXX Should the imp.load_* functions also be called via the hooks
-instance?
-
 """
 
 
@@ -59,16 +55,21 @@
 import imp
 import os
 import sys
+import string
+
+
+VERBOSE = 0
 
 
 from imp import C_EXTENSION, PY_SOURCE, PY_COMPILED
-BUILTIN_MODULE = 32
-FROZEN_MODULE = 33
+from imp import C_BUILTIN, PY_FROZEN, PKG_DIRECTORY
+BUILTIN_MODULE = C_BUILTIN
+FROZEN_MODULE = PY_FROZEN
 
 
 class _Verbose:
 
-    def __init__(self, verbose = 0):
+    def __init__(self, verbose = VERBOSE):
         self.verbose = verbose
 
     def get_verbose(self):
@@ -84,7 +85,10 @@
             apply(self.message, args)
 
     def message(self, format, *args):
-        print format%args
+        if args:
+            print format%args
+        else:
+            print format
 
 
 class BasicModuleLoader(_Verbose):
@@ -125,6 +129,7 @@
                 return None
 
     def find_builtin_module(self, name):
+        # XXX frozen packages?
         if imp.is_builtin(name):
             return None, '', ('', '', BUILTIN_MODULE)
         if imp.is_frozen(name):
@@ -132,22 +137,11 @@
         return None
 
     def load_module(self, name, stuff):
-        file, filename, (suff, mode, type) = stuff
+        file, filename, info = stuff
         try:
-            if type == BUILTIN_MODULE:
-                return imp.init_builtin(name)
-            if type == FROZEN_MODULE:
-                return imp.init_frozen(name)
-            if type == C_EXTENSION:
-                return imp.load_dynamic(name, filename, file)
-            if type == PY_SOURCE:
-                return imp.load_source(name, filename, file)
-            if type == PY_COMPILED:
-                return imp.load_compiled(name, filename, file)
+            return imp.load_module(name, file, filename, info)
         finally:
             if file: file.close()
-        raise ImportError, "Unrecognized module type (%s) for %s" % \
-                           (`type`, name)
 
 
 class Hooks(_Verbose):
@@ -175,6 +169,8 @@
         return imp.load_compiled(name, filename, file)
     def load_dynamic(self, name, filename, file=None):
         return imp.load_dynamic(name, filename, file)
+    def load_package(self, name, filename, file=None):
+        return imp.load_module(name, file, filename, ("", "", PKG_DIRECTORY))
 
     def add_module(self, name):
         d = self.modules_dict()
@@ -214,7 +210,7 @@
 
     """
 
-    def __init__(self, hooks = None, verbose = 0):
+    def __init__(self, hooks = None, verbose = VERBOSE):
         BasicModuleLoader.__init__(self, verbose)
         self.hooks = hooks or Hooks(verbose)
 
@@ -231,15 +227,24 @@
         self.hooks = hooks
 
     def find_builtin_module(self, name):
+        # XXX frozen packages?
         if self.hooks.is_builtin(name):
             return None, '', ('', '', BUILTIN_MODULE)
         if self.hooks.is_frozen(name):
             return None, '', ('', '', FROZEN_MODULE)
         return None
 
-    def find_module_in_dir(self, name, dir):
+    def find_module_in_dir(self, name, dir, allow_packages=1):
         if dir is None:
             return self.find_builtin_module(name)
+        if allow_packages:
+            fullname = self.hooks.path_join(dir, name)
+            if self.hooks.path_isdir(fullname):
+                stuff = self.find_module_in_dir("__init__", fullname, 0)
+                if stuff:
+                    file = stuff[0]
+                    if file: file.close()
+                    return None, fullname, ('', '', PKG_DIRECTORY)
         for info in self.hooks.get_suffixes():
             suff, mode, type = info
             fullname = self.hooks.path_join(dir, name+suff)
@@ -251,7 +256,8 @@
         return None
 
     def load_module(self, name, stuff):
-        file, filename, (suff, mode, type) = stuff
+        file, filename, info = stuff
+        (suff, mode, type) = info
         try:
             if type == BUILTIN_MODULE:
                 return self.hooks.init_builtin(name)
@@ -263,6 +269,8 @@
                 m = self.hooks.load_source(name, filename, file)
             elif type == PY_COMPILED:
                 m = self.hooks.load_compiled(name, filename, file)
+            elif type == PKG_DIRECTORY:
+                m = self.hooks.load_package(name, filename, file)
             else:
                 raise ImportError, "Unrecognized module type (%s) for %s" % \
                       (`type`, name)
@@ -278,6 +286,25 @@
 
     def load_module(self, name, stuff):
         file, filename, (suff, mode, type) = stuff
+        realfilename = filename
+        path = None
+
+        if type == PKG_DIRECTORY:
+            initstuff = self.find_module_in_dir("__init__", filename, 0)
+            if not initstuff:
+                raise ImportError, "No __init__ module in package %s" % name
+            initfile, initfilename, initinfo = initstuff
+            initsuff, initmode, inittype = initinfo
+            if inittype not in (PY_COMPILED, PY_SOURCE):
+                if initfile: initfile.close()
+                raise ImportError, \
+                    "Bad type (%s) for __init__ module in package %s" % (
+                    `inittype`, name)
+            path = [filename]
+            file = initfile
+            realfilename = initfilename
+            type = inittype
+
         if type == FROZEN_MODULE:
             code = self.hooks.get_frozen_object(name)
         elif type == PY_COMPILED:
@@ -286,25 +313,27 @@
             code = marshal.load(file)
         elif type == PY_SOURCE:
             data = file.read()
-            code = compile(data, filename, 'exec')
+            code = compile(data, realfilename, 'exec')
         else:
             return ModuleLoader.load_module(self, name, stuff)
+
         m = self.hooks.add_module(name)
+        if path:
+            m.__path__ = path
         m.__file__ = filename
         exec code in m.__dict__
         return m
 
 
-class ModuleImporter(_Verbose):
+class BasicModuleImporter(_Verbose):
 
-    """Default module importer; uses module loader.
+    """Basic module importer; uses module loader.
 
-    This provides the same functionality as built-in import, when
-    combined with ModuleLoader.
+    This provides basic import facilities but no package imports.
 
     """
 
-    def __init__(self, loader = None, verbose = 0):
+    def __init__(self, loader = None, verbose = VERBOSE):
         _Verbose.__init__(self, verbose)
         self.loader = loader or ModuleLoader(None, verbose)
         self.modules = self.loader.modules_dict()
@@ -358,6 +387,115 @@
             del __builtin__.unload
 
 
+class ModuleImporter(BasicModuleImporter):
+
+    """A module importer that supports packages."""
+    
+    def import_module(self, name, globals=None, locals=None, fromlist=None):
+        parent = self.determine_parent(globals)
+        q, tail = self.find_head_package(parent, name)
+        m = self.load_tail(q, tail)
+        if not fromlist:
+            return q
+        if hasattr(m, "__path__"):
+            self.ensure_fromlist(m, fromlist)
+        return m
+
+    def determine_parent(self, globals):
+        if not globals or not globals.has_key("__name__"):
+            return None
+        pname = globals['__name__']
+        if globals.has_key("__path__"):
+            parent = self.modules[pname]
+            assert globals is parent.__dict__
+            return parent
+        if '.' in pname:
+            i = string.rfind(pname, '.')
+            pname = pname[:i]
+            parent = self.modules[pname]
+            assert parent.__name__ == pname
+            return parent
+        return None
+
+    def find_head_package(self, parent, name):
+        if '.' in name:
+            i = string.find(name, '.')
+            head = name[:i]
+            tail = name[i+1:]
+        else:
+            head = name
+            tail = ""
+        if parent:
+            qname = "%s.%s" % (parent.__name__, head)
+        else:
+            qname = head
+        q = self.import_it(head, qname, parent)
+        if q: return q, tail
+        if parent:
+            qname = head
+            parent = None
+            q = self.import_it(head, qname, parent)
+            if q: return q, tail
+        raise ImportError, "No module named " + qname
+
+    def load_tail(self, q, tail):
+        m = q
+        while tail:
+            i = string.find(tail, '.')
+            if i < 0: i = len(tail)
+            head, tail = tail[:i], tail[i+1:]
+            mname = "%s.%s" % (m.__name__, head)
+            m = self.import_it(head, mname, m)
+            if not m:
+                raise ImportError, "No module named " + mname
+        return m
+
+    def ensure_fromlist(self, m, fromlist, recursive=0):
+        for sub in fromlist:
+            if sub == "*":
+                if not recursive:
+                    try:
+                        all = m.__all__
+                    except AttributeError:
+                        pass
+                    else:
+                        self.ensure_fromlist(m, all, 1)
+                continue
+            if sub != "*" and not hasattr(m, sub):
+                subname = "%s.%s" % (m.__name__, sub)
+                submod = self.import_it(sub, subname, m)
+                if not submod:
+                    raise ImportError, "No module named " + subname
+
+    def import_it(self, partname, fqname, parent):
+        if not partname:
+            raise ValueError, "Empty module name"
+        try:
+            return self.modules[fqname]
+        except KeyError:
+            pass
+        try:
+            path = parent and parent.__path__
+        except AttributeError:
+            return None
+        stuff = self.loader.find_module(partname, path)
+        if not stuff:
+            return None
+        m = self.loader.load_module(fqname, stuff)
+        if parent:
+            setattr(parent, partname, m)
+        return m
+
+    def reload(self, module):
+        name = module.__name__
+        if '.' not in name:
+            return self.import_it(name, name, None)
+        i = string.rfind(name, '.')
+        pname = name[:i]
+        parent = self.modules[pname]
+        return self.import_it(name[i+1:], name, parent)
+
+
 default_importer = None
 current_importer = None