[Zope-Checkins] CVS: Zope3/lib/python/Zope/Configuration - Action.py:1.2 ConfigurationDirectiveInterfaces.py:1.2 Exceptions.py:1.2 HookRegistry.py:1.2 __init__.py:1.2 configuration-meta.zcml:1.2 meta.py:1.2 metaConfigure.py:1.2 name.py:1.2 xmlconfig.py:1.2

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:29:55 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/Configuration
In directory cvs.zope.org:/tmp/cvs-serv20468/lib/python/Zope/Configuration

Added Files:
	Action.py ConfigurationDirectiveInterfaces.py Exceptions.py 
	HookRegistry.py __init__.py configuration-meta.zcml meta.py 
	metaConfigure.py name.py xmlconfig.py 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.

=== Zope3/lib/python/Zope/Configuration/Action.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+
+$Id$
+"""
+
+def Action(discriminator, callable, args=(), kw={}):
+    return discriminator, callable, args, kw
+


=== Zope3/lib/python/Zope/Configuration/ConfigurationDirectiveInterfaces.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""Configuration directives
+
+$Id$
+"""
+from Interface import Interface
+
+class IEmptyDirective(Interface):
+
+    def __call__(**kw):
+        """Compute configuration actions
+
+        Return a sequence of configuration actions. Each action is a
+        tuple with:
+
+        - A discriminator, value used to identify conflicting
+          actions. Actions conflict if they have the same values
+          for their discriminators.
+
+        - callable object
+
+        - argument tuple
+
+        - and, optionally, a keyword argument dictionary.
+
+        The callable object will be called with the argument tuple and
+        keyword arguments to perform the action.
+        """
+
+class INonEmptyDirective(Interface):
+
+    def __call__(**kw):
+        """Compute complex directive handler
+
+        Return an IComplexDirectiveHandler
+        """
+
+class ISubdirectiveHandler(Interface):
+    """Handle subdirectives
+
+    Provide mehods for registered subdirectives.
+
+    Also provide a call that can provide additional configuration actions.
+    """
+
+    def __call__():
+        """Return a sequence of configuration actions."""


=== Zope3/lib/python/Zope/Configuration/Exceptions.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""Standard configuration errors
+"""
+
+class ConfigurationError(Exception):
+    """There was an error in a configuration
+    """
+


=== Zope3/lib/python/Zope/Configuration/HookRegistry.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+
+from types import ModuleType
+from Zope.Exceptions import DuplicationError, NotFoundError, ZopeError
+import name
+
+class MissingHookableError(NotFoundError):
+    """the stated hook has not been registered"""
+    
+class DuplicateHookError(DuplicationError):
+    """an implementation for the given hook has already been registered"""
+
+class BadHookableError(ZopeError):
+    """hookable cannot be found or is not usable"""
+    
+class BadHookError(ZopeError):
+    """hook cannot be set"""
+    
+class HookRegistry:
+    def __init__(self):
+        self._reg = {}
+    
+    def addHookable(self, hname):
+        if hname in self._reg:
+            raise DuplicationError(hname)
+        try:
+            defaultimpl = name.resolve(hname)
+        except ImportError:
+            raise BadHookableError("hookable %s cannot be found" % hname)
+        
+        parent, last=self._getParentAndLast(hname)
+        implfunc="%s_hook" % last
+        
+        if getattr(parent, implfunc, self) is self:
+            raise BadHookableError(
+                """default hookable implementation (%s) cannot be found;
+                note it must be in the same module as the hookable""" %
+                implfunc)
+        
+        self._reg[hname] = 0
+    
+    def addHook(self, hookablename, hookname):
+        
+        if not (hookablename in self._reg):
+            raise MissingHookableError(hookablename)
+        if self._reg[hookablename]:
+            raise DuplicateHookError(hookablename, hookname)
+        try:
+            implementation = name.resolve(hookname)
+        except ImportError:
+            raise BadHookError('cannot find implementation', hookname)
+        try:
+            hookableDefault=name.resolve(hookablename)
+        except:
+            raise BadHookableError(
+                'hookable cannot be found, but was found earlier: '
+                'some code has probably masked the hookable',
+                hookablename)
+        
+        # This won't work as is: I'd have to create a NumberTypes and do
+        # various annoying checks
+        #if type(implementation) is not type (hookableDefault):
+        #    raise BadHookError(
+        #        'hook and hookable must be same type')
+        
+        # if they are functions, could check to see if signature is same
+        # (somewhat tricky because functions and methods could be
+        # interchangable but would have a different signature because
+        # of 'self')
+        
+        # for now I'll leave both of the above to the sanity of the site
+        # configuration manager...
+            
+        # find and import immediate parent
+        
+        parent,last = self._getParentAndLast(hookablename)
+        
+        # set parent.last to implementation
+        setattr(parent, "%s_hook" % last, implementation)
+        
+        self._reg[hookablename] = hookname
+    
+    def _getParentAndLast(self, hookablename):
+        if hookablename.endswith('.') or hookablename.endswith('+'):
+            hookablename = hookablename[:-1]
+            repeat = 1
+        else:
+            repeat = 0
+        names = hookablename.split(".")
+        last = names.pop()
+        importname = ".".join(names)
+        if not importname:
+            if not repeat:
+                raise BadHookableError(
+                    'hookable cannot be on top level of Python namespace',
+                    hookablename)
+            importname = last
+        parent = __import__(importname, {}, {}, ('__doc__',))
+        child = getattr(parent, last, self)
+        if child is self:
+            raise BadHookableError(
+                'hookable cannot be on top level of Python namespace',
+                hookablename)
+        while repeat:
+            grand = getattr(child, last, self)
+            if grand is self:
+                break
+            parent = child
+            child = grand
+        
+        if type(parent) is not ModuleType:
+            raise BadHookableError("parent of hookable must be a module")
+        
+        return parent, last
+    
+    def getHooked(self):
+        return [(key, self._reg[key])
+                for key in self._reg
+                if self._reg[key]]
+    
+    def getUnhooked(self):
+        return [(key, self._reg[key])
+                for key in self._reg
+                if not self._reg[key]]
+    
+    def getHookables(self):
+        return [(key, self._reg[key])
+                for key in self._reg]


=== Zope3/lib/python/Zope/Configuration/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""Zope configuration support
+
+Software that wants to provide new config directives calls
+Zope.Configuration.meta.register.
+"""
+
+def namespace(suffix):
+    return 'http://namespaces.zope.org/'+suffix
+
+import sys, os
+from Zope.Configuration.xmlconfig import XMLConfig
+
+def config(dir):
+    try:
+        XMLConfig(os.path.join(dir, 'site.zcml'))()
+    except:
+        # Use the ExceptionFormatter to provide XMLconfig debug info
+        from Zope.Exceptions.ExceptionFormatter import format_exception
+        exc_info = ['='*72, '\nZope Configuration Error\n', '='*72, '\n'] \
+                   + apply(format_exception, sys.exc_info())
+        sys.stderr.write(''.join(exc_info))
+        sys.exit(0) # Fatal config error
+
+__all__ = ["namespace", "config"]


=== Zope3/lib/python/Zope/Configuration/configuration-meta.zcml 1.1 => 1.2 ===
+
+  <!-- Zope.Configure -->
+  <directives namespace="http://namespaces.zope.org/zope">
+    <directive name="hookable" attributes="name module"
+       handler="Zope.Configuration.metaConfigure.provideHookable" />
+    <directive name="hook" attributes="name implementation module"
+       handler="Zope.Configuration.metaConfigure.provideHook" />
+  </directives>
+
+</zopeConfigure>


=== Zope3/lib/python/Zope/Configuration/meta.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""Registration of registration directives
+
+See ConfigurationDirectiveInterfaces
+
+"""
+
+# 
+
+from ConfigurationDirectiveInterfaces import INonEmptyDirective
+from ConfigurationDirectiveInterfaces import ISubdirectiveHandler
+
+
+_directives = {}
+
+class InvalidDirective(Exception):
+    "An invalid directive was used"
+
+class BrokenDirective(Exception):
+    "A directive is implemented incorrectly"
+
+def register(name, callable):
+    """Register a top-level directive
+
+    The name argument is a tuple with a namespace URI and an
+    name string.
+
+    The callable must be am IEmptyDirective or an INonEmptyDirective.
+
+    INonEmptyDirective directives may have subdirectives. The
+    subdirectives will be registered in a registry that is stored with
+    the directive. The sub-directive registry is returned so that
+    it can be used for subsequent sub-directive registration.
+
+    """
+    
+    subdirs = {}
+    _directives[name] = callable, subdirs
+    return subdirs
+
+def registersub(directives, name, handler_method=None):
+    """Register a subdirective
+
+    directives is the subdirective registry for the containing
+    directive, which may be either a top-level directive or an
+    intermediate sub-directive (if subdirectives are nested more than
+    two deep.
+
+    The name argument is a tuple with a namespace URI and an
+    name string.
+
+    The handler is not passed as it normally is for top-level
+    directives. Rather, the handler is looked up as an attribute of
+    the top-level directive object using the name string that is the
+    second element in the name tuple.  An optional handler attribute
+    can be used to specify the method to be used.
+
+    Subdirectives may have subdirectives. The subdirectives will be
+    registered in a registry that is stored with the containing
+    subdirective. The sub-directive registry is returned so that it
+    can be used for subsequent sub-directive registration.
+
+    """
+    if not handler_method:
+        handler_method = name[1]
+    subdirs = {}
+    directives[name] = subdirs, handler_method
+    return subdirs
+
+def _exe(callable, subs, context, kw):
+    r = callable(context, **kw)
+
+    if subs or INonEmptyDirective.isImplementedBy(callable):
+        return r, subs
+    else:
+        return (
+            # We already have our list of actions, but we're expected to
+            # provide a callable that returns one. 
+            (lambda: r),
+
+            subs,
+            )
+
+def begin(_custom_directives, _name, _context, **kw):
+    """Begin executing a top-level directive
+
+    A custom registry is provided to provides specialized directive
+    handlers in addition to the globally registered directives. For
+    example, the XML configuration mechanism uses these to provide XML
+    configuration file directives.
+
+    The _name argument is a tuple with a namespace URI and an
+    name string.
+
+    Thje _context argument is an execution context objects that
+    directives use for functions like resolving names. It will be
+    passed as the first argument to the directive handler.
+
+    kw are the directive arguments.
+
+    The return value is a tuple that contains:
+
+    - An object to be called to finish directive processing. This
+      object will return a sequence of actions. The object must be
+      called after sub-directives are processes.
+
+    - A registry for looking up subdirectives.
+
+    """
+    
+    if _custom_directives and (_name in _custom_directives):
+        callable, subs = _custom_directives[_name]
+    else:
+        try:
+            callable, subs = _directives[_name]
+        except KeyError:
+            raise InvalidDirective(_name)
+
+    return _exe(callable, subs, _context, kw)
+
+def sub(subs, _name, _context, **kw):
+    """Begin executing a subdirective
+
+    The first argument, subs, is a registry of allowable subdirectives
+    for the containing directive or subdirective.
+
+    The _name argument is a tuple with a namespace URI and an
+    name string.
+
+    Thje _context argument is an execution context objects that
+    directives use for functions like resolving names. It will be
+    passed as the first argument to the directive handler.
+
+    kw are the directive arguments.
+
+    The return value is a tuple that contains:
+
+    - An object to be called to finish directive processing. This
+      object will return a sequence of actions. The object must be
+      called after sub-directives are processes.
+
+    - A registry for looking up subdirectives.
+
+    """
+
+    base, subdirs = subs
+    try:
+        subs = subdirs[_name]
+    except KeyError:
+        raise InvalidDirective(_name)
+        
+    # this is crufty.
+    # if this is a tuple, it means we created it as such in 
+    # registersub, and so we grab item 1 as the handler_method
+    # and rebind subs as item 0
+        
+    if isinstance(subs, tuple):
+        handler_method = subs[1]
+        subs = subs[0]
+    else:
+        handler_method = _name[1]
+    callable = getattr(base, handler_method)
+
+    return _exe(callable, subs, _context, kw)
+
+defaultkw = ({},)
+def end(base):
+    """Finish processing a directive or subdirective
+
+    The argument is a return value from begin or sub. It's first
+    element is called to get a sequence of actions.
+
+    The actions are normalized to a 4-element tuple with a
+    descriminator, a callable, positional arguments, and keyword
+    arguments. 
+    """
+
+    actions = base[0]()
+    ractions = []
+    for action in actions:
+        if len(action) < 3 or len(action) > 4:
+            raise BrokenDirective(action)
+        if len(action) == 3:
+            action += defaultkw
+        ractions.append(action)
+    return ractions
+
+class DirectiveNamespace:
+
+    def __init__(self, _context, namespace):
+        self._namespace = namespace
+
+    def directive(self, _context, name, handler, attributes='',
+                  namespace=None):
+        namespace = namespace or self._namespace
+        subs = register((namespace, name), _context.resolve(handler))
+        return Subdirective(subs, namespace=namespace)
+
+    def __call__(self):
+        return ()
+
+def Directive(_context, namespace, name, handler, attributes=''):
+    subs = register((namespace, name), _context.resolve(handler))
+    return Subdirective(subs, namespace=namespace)
+
+Directive.__implements__ = INonEmptyDirective
+
+class InvaliDirectiveDefinition(Exception): pass
+
+class Subdirective:
+    """This is the meta-meta-directive"""
+    # 
+    # Unlike other directives, it doesn't return any actions, but
+    # takes action right away, since it's actions are needed to process other
+    # directives.
+    # 
+    # For this reason, this isn't a good directive example.
+
+    __implements__ = ISubdirectiveHandler
+
+    def __init__(self, subs, namespace=None):
+        self._subs = subs
+        self._namespace = namespace
+
+    def subdirective(self, _context, name, attributes='',
+                     namespace=None, handler_method=None):
+        namespace = namespace or self._namespace
+        if not namespace:
+            raise InvaliDirectiveDefinition(name)
+        #if not handler_method:
+        #    handler_method = name
+        subs = registersub(self._subs, (namespace, name), handler_method)
+        return Subdirective(subs)
+
+    def __call__(self):
+        return ()
+
+def _clear():
+    "To support unit tests"
+    _directives.clear()
+    _directives[('http://namespaces.zope.org/zope', 'directive')] = (
+        Directive, {
+        ('http://namespaces.zope.org/zope', 'subdirective'): {
+        ('http://namespaces.zope.org/zope', 'subdirective'): {
+        ('http://namespaces.zope.org/zope', 'subdirective'): {
+        }}}})
+    _directives[('http://namespaces.zope.org/zope', 'directives')] = (
+        DirectiveNamespace, {
+        ('http://namespaces.zope.org/zope', 'directive'): {
+        ('http://namespaces.zope.org/zope', 'subdirective'): {
+        ('http://namespaces.zope.org/zope', 'subdirective'): {
+        ('http://namespaces.zope.org/zope', 'subdirective'): {
+        }}}}})
+
+_clear()
+
+# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
+from Zope.Testing.CleanUp import addCleanUp
+addCleanUp(_clear)
+del addCleanUp


=== Zope3/lib/python/Zope/Configuration/metaConfigure.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+from Action import Action
+from HookRegistry import HookRegistry
+
+# one could make hookRegistry a service and
+# theoretically use it TTW, but that doesn't immediately seem like a
+# great idea
+hookRegistry = HookRegistry()
+
+addHookable = hookRegistry.addHookable
+addHook = hookRegistry.addHook
+
+def provideHookable(_context, name, module=None):
+    if module:
+        name = "%s.%s" % (module, name)
+    name = _context.getNormalizedName(name)
+    return [
+        Action(
+            discriminator=('addHookable', name),
+            callable=addHookable,
+            args=(name,)
+            )
+        ]
+
+
+def provideHook(_context, name, implementation, module=None):
+    if module:
+        name = "%s.%s" % (module, name)
+    name = _context.getNormalizedName(name)
+    implementation = _context.getNormalizedName(implementation)
+    return [
+        Action(
+            discriminator=('addHook', name),
+            callable=addHook,
+            args=(name, implementation)
+            )
+        ]
+
+
+


=== Zope3/lib/python/Zope/Configuration/name.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""Provide configuration object name resolution
+
+$Id$
+"""
+
+import os
+import sys
+from types import ModuleType
+
+def resolve(name, package='ZopeProducts', _silly=('__doc__',), _globals={}):
+    name = name.strip()
+    
+    if name.startswith('.'):
+        name=package+name
+
+    if name.endswith('.') or name.endswith('+'):
+        name = name[:-1]
+        repeat = 1
+    else:
+        repeat = 0
+
+    names=name.split('.')
+    last=names[-1]
+    mod='.'.join(names[:-1])
+
+    if not mod:
+        return __import__(name, _globals, _globals, _silly)
+                 
+    while 1:
+        m=__import__(mod, _globals, _globals, _silly)
+        try:
+            a=getattr(m, last)
+        except AttributeError:
+            if not repeat:
+                return __import__(name, _globals, _globals, _silly)
+                
+        else:
+            if not repeat or (not isinstance(a, ModuleType)):
+                return a
+        mod += '.' + last
+
+
+def getNormalizedName(name, package):
+    name=name.strip()
+    if name.startswith('.'):
+        name=package+name
+
+    if name.endswith('.') or name.endswith('+'):
+        name = name[:-1]
+        repeat = 1
+    else:
+        repeat = 0
+    name=name.split(".")
+    while len(name)>1 and name[-1]==name[-2]:
+        name.pop()
+        repeat=1
+    name=".".join(name)
+    if repeat:
+        name+="+"
+    return name
+
+def path(file='', package = 'ZopeProducts', _silly=('__doc__',), _globals={}):
+    try: package = __import__(package, _globals, _globals, _silly)
+    except ImportError:
+        if file and os.path.abspath(file) == file:
+            # The package didn't matter
+            return file
+        raise
+        
+    path = os.path.split(package.__file__)[0]
+    if file:
+        path = os.path.join(path, file)
+    return path


=== Zope3/lib/python/Zope/Configuration/xmlconfig.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+
+$Id$
+"""
+
+import os
+import name
+from xml.sax import make_parser
+from xml.sax.xmlreader import InputSource
+from xml.sax.handler import ContentHandler, feature_namespaces
+from meta import begin, sub, end
+from keyword import iskeyword
+import sys, os
+from types import StringType
+from Exceptions import ConfigurationError
+
+class ZopeXMLConfigurationError(ConfigurationError):
+    "Zope XML Configuration error"
+
+    def __init__(self, locator, mess, etype=None):
+        if etype is None:
+            if not isinstance(mess, StringType):
+                try:
+                    mess = "\n%s: %s" % (mess.__class__.__name__, mess)
+                except AttributeError:
+                    mess = str(mess)
+        else:
+            mess = "\n%s: %s" % (etype.__name__, mess)
+            
+        self.lno = locator.getLineNumber()
+        self.cno = locator.getColumnNumber()
+        self.sid = locator.getSystemId()
+        self.mess = mess
+
+    def __str__(self):
+        return 'File "%s", line %s, column %s\n\t%s' % (
+            self.sid, self.lno, self.cno, self.mess)
+
+class ConfigurationExecutionError(ZopeXMLConfigurationError):
+    """An error occurred during execution of a configuration action
+    """
+
+    def __init__(self, locator, mess, etype=None):
+        if etype is None:
+            if isinstance(mess, StringType):
+                try:
+                    mess = "%s: %s" % (mess.__class__.__name__, mess)
+                except AttributeError:
+                    mess = str(mess)
+        else:
+            mess = "\n%s: %s" % (etype.__name__, mess)
+
+        self.lno, self.cno, self.sid = locator
+        self.mess = mess
+
+class ConfigurationHandler(ContentHandler):
+
+    __top_name = 'http://namespaces.zope.org/zope', 'zopeConfigure' 
+
+    def __init__(self, actions, context, directives=None, testing=0):
+        self.__stack = []
+        self.__actions = actions
+        self.__directives = directives
+        self.__context = context
+        self.__testing = testing
+        context.resolve
+
+    def setDocumentLocator(self, locator):
+        self.__locator = locator
+
+    def startElementNS(self, name, qname, attrs):
+        stack = self.__stack
+        if not stack:
+            if name != self.__top_name:
+                raise ZopeXMLConfigurationError(
+                    self.__locator, "Invalid top element: %s %s" % name)
+
+            for (ns, aname), value in attrs.items():
+                if ns is None:
+                    self.__context.file_attr(aname, value)
+                    
+
+            stack.append(None)
+            return
+
+        kw = {}
+        for (ns, aname), value in attrs.items():
+            if ns is None:
+                aname = str(aname)
+                if iskeyword(aname): aname += '_'
+                kw[aname] = value
+
+        if len(stack) == 1:
+            try:
+                stack.append(
+                    begin(self.__directives, name, self.__context, **kw)
+                    )
+            except Exception, v:
+                if self.__testing:
+                    raise
+                raise ZopeXMLConfigurationError, (
+                    self.__locator, v), sys.exc_info()[2] 
+                
+        else:
+            subs = self.__stack[-1]
+            if subs is None:
+                raise ZopeXMLConfigurationError(self.__locator,
+                                                'Invalid sub-directive')
+            try:
+                stack.append(sub(subs, name, self.__context, **kw))
+            except Exception, v:
+                if self.__testing:
+                    raise
+                raise ZopeXMLConfigurationError, (
+                    self.__locator, v), sys.exc_info()[2] 
+
+    def endElementNS(self, name, qname):
+        subs = self.__stack.pop()
+        # to fool compiler that thinks actions is used before assignment:
+        actions = ()
+
+        if subs is not None:
+            try:
+                actions = end(subs)
+            except Exception, v:
+                if self.__testing:
+                    raise
+                raise ZopeXMLConfigurationError, (
+                    self.__locator, str(v)), sys.exc_info()[2] 
+
+        append = self.__actions.append
+
+        try:
+            for des, callable, args, kw in actions:
+                append((self.__context,
+                        (self.__locator.getLineNumber(),
+                         self.__locator.getColumnNumber(),
+                         self.__locator.getSystemId(),
+                         ), des, callable, args, kw))
+        except:
+            print 'endElementNS', actions
+            raise
+
+class ZopeConflictingConfigurationError(ZopeXMLConfigurationError):
+    "Zope XML Configuration error"
+
+    def __init__(self, l1, l2, des):
+        self.l1 = l1
+        self.l2 = l2
+        self.des = des
+
+    def __str__(self):
+        return """Conflicting configuration action:
+        %s
+        at line %s column %s of %s
+        and% at line %s column %s of %s
+        """ % ((self.des,) + self.l1 + self.l2)
+
+class Context:
+    def __init__(self, stack, module=None):
+        self.__stackcopy = tuple(stack)
+        if module is None:
+            self.__package = 'ZopeProducts'
+        else:
+            self.__package = module.__name__
+
+    def _stackcopy(self):
+        return self.__stackcopy
+
+    def resolve(self, dottedname):
+        return name.resolve(dottedname, self.__package)
+    
+    def getNormalizedName(self, dottedname):
+        return name.getNormalizedName(dottedname, self.__package)
+
+    def path(self, file=None):
+        return name.path(file, self.__package)
+
+    def file_attr(self, name, value):
+        if name == 'package':
+            self.__package = value
+        else:
+            raise TypeError, "Unrecognized config file attribute: %s" % name
+
+
+def xmlconfig(file, actions=None, context=None, directives=None,
+              testing=0):
+    if context is None:
+        context = name
+
+    if actions is None:
+        call = actions = []
+    else:
+        call = 0
+    
+    src = InputSource(getattr(file, 'name', '<string>'))
+    src.setByteStream(file)
+    parser = make_parser()
+    parser.setContentHandler(
+        ConfigurationHandler(actions, context,directives,
+                             testing=testing)
+        )
+    parser.setFeature(feature_namespaces, 1)
+    parser.parse(src)
+
+    if call:
+        descriptors = {}
+        for level, loc, des, callable, args, kw in call:
+            if des in descriptors:
+                raise ZopeConflictingConfigurationError(
+                    descriptors[des], loc, des)
+            descriptors[des] = loc
+            callable(*args, **kw)
+
+def testxmlconfig(file, actions=None, context=None, directives=None):
+    """xmlconfig that doesn't raise configuration errors
+
+    This is useful for testing, as it doesn't mask exception types.
+    """
+    return xmlconfig(file, actions, context, directives, testing=1)
+            
+class ZopeConfigurationConflictError(ZopeXMLConfigurationError):
+
+    def __init__(self, conflicts):
+        self._conflicts = conflicts
+
+    def __str__(self):
+        r = ["Conflicting configuration actions"]
+        for dis, locs in self._conflicts.items():
+            r.append('for: %s' % (dis,))
+            for loc in locs:
+                r.append("  at line %s column %s of %s" % loc)
+        
+        return "\n".join(r)
+    
+class XMLConfig:
+
+    def __init__(self, file_name):
+        self._actions = []
+        self._directives = {('http://namespaces.zope.org/zope', 'include'):
+                            (self.include, {})}
+
+        f = open(file_name)
+        self._stack = [file_name]
+        xmlconfig(f, self._actions, Context(self._stack), self._directives)
+        f.close()
+
+    def include(self, _context, file='config.zcml', package=None):
+        if package is not None:
+            try:
+                package = _context.resolve(package)
+                if len(package.__path__) != 1:
+                    print ("Module Path: '%s' has wrong number of elements"
+                            % str(package.__path__))
+                # XXX: This should work for 99% of cases
+                # We may want to revisit this with a more robust
+                # mechanism later. Specifically, sometimes __path__
+                # will have more than one element. Also, we could
+                # use package.__file__, and lop the tail off that.
+                prefix = package.__path__[0]
+            except (ImportError, AttributeError, ValueError), v:
+                raise # XXX the raise below hides the real error
+                raise ValueError("Invalid package attribute: %s\n(%s)"
+                                 % (package, `v`))
+        else:
+            prefix = os.path.dirname(self._stack[-1])
+            
+        file_name = os.path.join(prefix, file)
+
+        f = open(file_name)
+        self._stack.append(file_name)
+        xmlconfig(f, self._actions, Context(self._stack, package),
+                  self._directives)
+        self._stack.pop()
+        f.close()
+        return ()
+
+    def __call__(self):
+        self.organize()
+
+    def __iter__(self): return iter(self._actions)
+
+    def organize(self):
+        actions = self._actions
+
+        # organize actions by discriminators
+        unique = {}
+        for i in range(len(actions)):
+            context, loc, des, callable, args, kw = actions[i]
+            a = unique.setdefault(des, [])
+            a.append((context._stackcopy(), i, loc, (callable, args, kw)))
+
+        # Check for conflicts
+        conflicts = {}
+        for des, actions in unique.items():
+            path, i, loc, f = actions[0]
+            for opath, i, oloc, f in actions[1:]:
+                if opath[:len(path)] != path:
+                    if des not in conflicts:
+                        conflicts[des] = [loc]
+                    conflicts[des].append(oloc)
+
+        if conflicts:
+            raise ZopeConfigurationConflictError(conflicts)
+
+        # Now order the configuration directives
+        cactions = []
+        for des, actions in unique.items():
+            path, i, loc, f = actions.pop(0)
+            cactions.append((i, loc, f))
+
+        unique = None
+
+        cactions.sort()
+
+        # Call actions
+        for i, loc, f in cactions:
+            try:
+                callable, args, kw = f
+                callable(*args, **kw)
+            except Exception, v:
+                raise ConfigurationExecutionError, (
+                    loc, v, sys.exc_info()[0]), sys.exc_info()[2]