[Zope3-checkins] CVS: Zope3/src/zope/configuration - README.txt:1.1 backward.py:1.1 config.py:1.1 fields.py:1.1 __init__.py:1.3 interfaces.py:1.2 xmlconfig.py:1.10 meta.py:NONE metameta.zcml:NONE metametaconfigure.py:NONE metametaconfigurefordocgen.py:NONE metametafordocgen.zcml:NONE

Jim Fulton jim@zope.com
Mon, 28 Jul 2003 18:23:20 -0400


Update of /cvs-repository/Zope3/src/zope/configuration
In directory cvs.zope.org:/tmp/cvs-serv30798/src/zope/configuration

Modified Files:
	__init__.py interfaces.py xmlconfig.py 
Added Files:
	README.txt backward.py config.py fields.py 
Removed Files:
	meta.py metameta.zcml metametaconfigure.py 
	metametaconfigurefordocgen.py metametafordocgen.zcml 
Log Message:
Major refactoring of the configuration system.  Now use a totally new
architecture based on:

- Using schema to describe directive attributes

- Treating most configuration handlers as decorators of configuration
  contexts.

The benefits of this are:

- Conversion of directive input can be automated.

- Much better error reporting. We can now tell people
  what attributes are missing, are extra, or have invalid
  values.

- It's possible to extend existing directives without modifying them.
  Extending subdirectives can even be in different namespaces, if
  desired.

- There is a includeOverrides directive for including files that
  contain directives overriding directives in other files.

- It is possible to have many more sorts of "grouping" directives.
  This will allow things like directives that provide defaults for
  other directives and perhaps even some sort of macro system.

The basic refactoring is done, but there are a number of things to be
done:

- Write new documentation on how to write directives the new and
  better way.

- Update the documentation generation tool to use the new
  configuration framework.

- Add i18n support. This was the motivation for the refactoring. Now
  this is easy. :)


=== Added File Zope3/src/zope/configuration/README.txt ===
==========================
Zope configuration system
==========================

The zope configuration system provides an extensible system for
supporting variouse kinds of configurations.

It is based on the idea of configuration directives. Users of the
configuration system provide configuration directives in some
language that express configuration choices. The intent is that the
language be pluggable.  An XML language is provided by default.

Configuration is performed in three stages. In the first stage,
directives are processed to compute configuration actions.
Configuration actions consist of:

- A discriminator

- A callable

- Positional arguments

- Keyword arguments

The actions are essentially delayed function calls.  Two or more
actions conflict if they have the same discriminator.  The
configuration system has rules for resolving conflicts. If conflicts
cannot be resolved, an error will result.  Conflict resolution
typically discards all but one of the conflicting actions, so that
the remaining action of the originally-conflicting actions no longer
conflicts.  Non-conflicting actions are executed in the order that
they were created by passing the positional and non-positional
arguments to the action callable.

The system is extensible. There is a meta-configuration language for
defining configuration directives. A directive is defined by
providing meta data about the directive and handler code to process
the directive.  There are four kinds of directives:

- Simple directives compute configuration actions.  Their handlers
  are typically functions that take a context and zero or more
  keyword arguments and return a sequence of configuration actions.

- Grouping directives collect information to be used by nested
  directives. They are called with a context object which they adapt
  to some interface that extends IConfigurationContext.

  Other directives can be nested in grouping directives.

- Complex directives are directives that have subdirectives.  
  Subdirectives have handlers that are simply methods of complex
  directives. Complex diretives are handled by factories, typically
  classes, that create objects that have methods for handling
  subdirectives. These objects also have __call__ methods that are
  called when processing of subdirectives is finished.

- Subdirectives are nested in complex directives. They are like
  simple directives except that they hane handlers that are complex
  directive methods.

Directives are defined for an interface, which is
IConfigurationContext by default.


=== Added File Zope3/src/zope/configuration/backward.py ===
##############################################################################
#
# Copyright (c) 2003 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.
#
##############################################################################
"""Pre-zcml-geddon backward compatability

Rather than revisit all of the old meta configurations, we'll
support the old configurations for a time until they can be converted.
There are two aspects of this:

1. Supporting complex directives (as opposed to simple and grouping
   directives). This support is actually provided in config.py.
   We'll probably support complex directives indefinately, as there
   are some pretty complicated handlers in place now that we don't
   want to take time to rewrite any time soon.

2. Supporting the old-style meta-configuration ZCML directives:
   zope:directives, zope:directive, zope:description, and
   zope:attribute.  Hopefully, we can get rid of these sooner by
   converting the existing meta configurations to use the new
   meta: directives and schema. Certainly directives with message ids
   will need to be converted.

This file contains the implementations of the old-style meta
configurations.

$Id: backward.py,v 1.1 2003/07/28 22:22:39 jim Exp $
"""

from keyword import iskeyword
from zope.configuration import config, xmlconfig
from zope import interface
from zope import schema


class IDescribed(interface.Interface):

    name = schema.TextLine(
        __doc__=
        """Directive name

        The name of the directive being defined
        """,
        )

    description = schema.Text(
        __doc__=
        """Directive discription

        This should document how the directive is used.
        """,
        default=u"",
        )

class ISubdirectiveInfo(IDescribed):
    """Information common to all directive definitions have
    """

    attributes = schema.Bytes(
        __doc__=
        """Attribute names

        This is a space-speratated list of attribute names. This is
        used to provide a mimimal specification the attributes used.
        """,
        default="",
        )

class IDirectiveInfo(ISubdirectiveInfo):
    """Information common to all directive definitions have
    """
    
    handler = config.fields.GlobalObject(
        __doc__=
        """Directive handler

        The dotted name of the directive handler
        """,
        )

class ISubdirectiveContext(ISubdirectiveInfo, config.IConfigurationContext):
    pass

class IDirectiveContext(IDirectiveInfo, ISubdirectiveContext):
    pass


class Described:

    interface.implements(IDescribed)

    description = u''
            
    def _merge_description_and_info(self):
        r"""Combind a description, given as an attribute with info text

        >>> d = Described()
        >>> d.info = Described() # Any object with attributes will do
        >>> d.info.text = u''
        >>> d._merge_description_and_info()
        >>> d.info.text
        u''

        >>> d.info.text = u'   \n  '
        >>> d._merge_description_and_info()
        >>> d.info.text
        u'   \n  '

        >>> d.description = u'test directive'
        >>> d._merge_description_and_info()
        >>> d.info.text
        u'test directive'

        >>> d.info.text = u'blah\nblah'
        >>> d._merge_description_and_info()
        >>> d.info.text
        u'test directive\n\nblah\nblah'
        
        """
        if self.description:
            if self.info.text.strip():
                self.info.text = self.description + u"\n\n" + self.info.text
            else:
                self.info.text = self.description

class Attributed(config.GroupingContextDecorator):
    """Compute schema definitions from simple attribute specifications

    The attribute specifications can be given as simple names in the
    constructor:

    >>> context = config.ConfigurationMachine()
    >>> x = Attributed(config, attributes=u"a b c")

    Or the can be provides as keys added to the attributes disctionary:

    >>> x.attributes['foo'] = schema.Int(title=u"Foo")

    When tha _schema_from_attrs method is called, a schema is computed:

    >>> x._schema_from_attrs()
    >>> for name in x.schema:
    ...   f = x.schema[name]
    ...   print f.__class__.__name__, f.__name__, f.title, int(f.required)
    Text a a 0
    Text c c 0
    Text b b 0
    Int foo Foo 1

    """

    interface.implementsOnly(IDescribed)

    def attribute(self, name, required=False, description=u''):
        aname = str(name)
        if iskeyword(name):
            aname += '_'
        self.attributes[aname] = schema.Text(
            title = unicode(aname),
            required = required,
            description = description,
            )

    def __init__(self, context, attributes=u'', **kw):
        config.GroupingContextDecorator.__init__(self, context, **kw)
        self.attributes = attrs = {}
        for name in attributes.strip().split():
            self.attribute(name)

    def _schema_from_attrs(self):
        schema = interface.Interface.__class__(
            "schema generated from attributes",
            (interface.Interface, ),
            self.attributes,
            )
        if not self.attributes:
            # No attribute definitions, allow keyword args
            schema.setTaggedValue('keyword_arguments', True)
        self.schema = schema
    

class Directive(Attributed, Described):
    """Handler for the directive directive

    Actual definition of the directive is delayed until
    sub(meta)directives have been handled.

    See the test in tests/test_backward
    """
    interface.implements(IDirectiveContext)
    usedIn = config.IConfigurationContext

    def __init__(self, context, **kw):
        Attributed.__init__(self, context, **kw)
        self.subdirectives = {}

    def after(self):
        self._schema_from_attrs()
        self._merge_description_and_info()
        if self.subdirectives:
            # we have subdirectives, so set up a complex directive
            complex = config.ComplexDirectiveDefinition(self)
            complex.handler = self.handler
            complex.update(self.subdirectives)
            complex.before()
        else:
            config.defineSimpleDirective(self, self.name, self.schema,
                                         self.handler, self.namespace)


class Subdirective(Attributed, Described):
    """Handler for the directive directive

    Actual definition of the directive is delayed until
    sub(meta)directives have been handled.

    >>> context = config.ConfigurationMachine()
    >>> d = Directive(context)
    >>> len(d.subdirectives)
    0
    >>> s = Subdirective(d, name="foo", attributes=u"a b")
    >>> len(d.subdirectives)
    0
    >>> class Info:
    ...    text=u'spam'
    >>> s.info = Info()
    >>> s.after()
    >>> len(d.subdirectives)
    1
    >>> schema, info = d.subdirectives['foo']
    >>> list(schema)
    ['a', 'b']
    >>> info.text
    u'spam'

    """

    interface.implements(ISubdirectiveContext)

    def after(self):
        self._schema_from_attrs()
        self._merge_description_and_info()
        self.context.subdirectives[self.name] = self.schema, self.info

class IAttribute(IDescribed):

    required = config.fields.Bool(
        __doc__=
        """Required

        Is the attribute required?
        """,
        required=True,
        default=False,
        )

class Attribute(config.GroupingContextDecorator, Described):
    r"""Simple attribute specification

    Provide a very simple specification of an attribute and add it to
    the attributes dictionary of the containing context.

    >>> context = config.ConfigurationMachine()
    >>> d = Directive(context, attributes=u"a")
    >>> len(d.attributes)
    1

    >>> a = Attribute(d, name="a", description=u"blah")
    >>> class Info:
    ...    text=u'spam'
    >>> a.info = Info()
    >>> d.attributes['a'].description
    u''
    >>> a.after()
    >>> d.attributes['a'].description
    u'blah\n\nspam'
    >>> d.attributes['a'].required
    0
    >>> d.attributes['a'].__class__.__name__
    'Text'

    >>> a = Attribute(d, name="b", description=u"eek", required=True)
    >>> class Info:
    ...    text=u'spam'
    >>> a.info = Info()
    >>> a.after()
    >>> d.attributes['b'].description
    u'eek\n\nspam'
    >>> d.attributes['b'].required
    1

    >>> len(d.attributes)
    2
    
    """

    required = False

    def after(self):
        self._merge_description_and_info()
        self.context.attribute(self.name, self.required, self.info.text)

class Description(config.GroupingContextDecorator):
    r"""Provide descriptions for surrounding directives

    This works a bit hard to be an effective noop, since
    it has the same effect as providing text data.

    >>> context = config.ConfigurationMachine()
    >>> d = Directive(context, attributes=u"a")
    >>> class Info:
    ...    text=u'spam         \n'
    >>> d.info = Info()

    >>> des = Description(d)
    >>> des.info = Info()
    >>> des.info.text = u"blah\nblah"
    >>> des.after()

    >>> d.info.text
    u'spam\n\nblah\nblah'
    
    """
    
    def after(self):
        """Merge our info with containing directive's info
        """

        if not self.info.text.strip():
            return

        context = self.context
        old = context.info.text.rstrip()
        
        if old:
            context.info.text = old + u"\n\n" + self.info.text
        else:
            context.info.text += self.info.text


def bootstrap(context):

    # zope:directives
    context((config.metans, 'groupingDirective'),
            name='directives',
            namespace=config.zopens,
            handler="zope.configuration.config.DirectivesHandler",
            schema="zope.configuration.config.IDirectivesInfo"
            )

    # zope:directive
    context((config.metans, 'groupingDirective'),
            name='directive',
            namespace=config.zopens,
            usedIn="zope.configuration.config.IDirectivesContext",
            handler="zope.configuration.backward.Directive",
            schema="zope.configuration.backward.IDirectiveInfo"
            )

    # zope:subdirective
    context((config.metans, 'groupingDirective'),
            name='subdirective',
            namespace=config.zopens,
            usedIn="zope.configuration.backward.IDirectiveContext",
            handler="zope.configuration.backward.Subdirective",
            schema="zope.configuration.backward.ISubdirectiveInfo"
            )

    # zope:attribute
    context((config.metans, 'groupingDirective'),
            name='attribute',
            namespace=config.zopens,
            usedIn="zope.configuration.backward.ISubdirectiveContext",
            handler="zope.configuration.backward.Attribute",
            schema="zope.configuration.backward.IAttribute"
            )

    # zope:discription
    context((config.metans, 'groupingDirective'),
            name='description',
            namespace=config.zopens,
            usedIn="zope.configuration.backward.IDescribed",
            handler="zope.configuration.backward.Description",
            schema="zope.interface.Interface"
            )

    


=== Added File Zope3/src/zope/configuration/config.py ===
##############################################################################
#
# Copyright (c) 2003 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 processort

See README.txt and notes.txt.

$Id: config.py,v 1.1 2003/07/28 22:22:39 jim Exp $
"""

from keyword import iskeyword
from zope.configuration.exceptions import ConfigurationError
from zope.configuration.interfaces import IConfigurationContext
from zope.interface.adapter import AdapterRegistry
from zope.interface import Interface, implements, directlyProvides
from zope.interface.interfaces import IInterface
from zope.schema.errornames import WrongType
import zope.schema
from zope.schema.interfaces import IField, IFromUnicode
from zope.configuration import fields
import os.path
import sys


zopens = 'http://namespaces.zope.org/zope'
metans = 'http://namespaces.zope.org/meta'
testns = 'http://namespaces.zope.org/test'

_import_chickens = {}, {}, ("*",) # dead chickens needed by __import__
class ConfigurationContext(object):
    """Mix-in that implements IConfigurationContext

    Subclasses provide a ``package`` attribute and a ``basepath``
    attribute.  If the base path is not None, relative paths are
    converted to absolute paths using the the base path. If the
    package is not none, relative imports are performed relative to
    the package.

    In general, the basepath and package attributes should be
    consistent. When a package is provided, the base path should be
    set to the path of the package directory.

    Subclasses also provide an ``actions`` attribute, which is a list
    of actions, an ``includepath`` attribute, and an ``info``
    attribute.

    The include path is appended to each action and is used when
    resolving conflicts among actions.  Normally, only the a
    ConfigurationMachine provides the actions attribute. Decorators
    simply use the actions of the context they decorate. The
    ``includepath`` attribute is a tuple of names.  Each name is
    typically the name of an included configuration file.

    The ``info`` attribute contains descriptive information helpful
    when reporting errors.  If not set, it defaults to an empty string.

    The actions attribute is a sequence of tuples with items:

      - discriminator, a value that identifies the action. Two actions
        that have the same (non None) discriminator conflict.

      - an object that is called to execute the action,

      - positional arguments for the action

      - keyword arguments for the action

      - a tuple of include file names (defaults to ())

      - an object that has descriptive information about
        the action (defaults to '')

    For brevity, trailing items after the callable in the tuples are
    ommitted if they are empty.
    
    """

    def resolve(self, dottedname):
        """Resolve a dotted name to an object

        Examples:


        >>> c = ConfigurationContext()
        >>> import zope, zope.interface
        >>> c.resolve('zope') is zope
        1
        >>> c.resolve('zope.interface') is zope.interface
        1
        >>> c.resolve('zope.configuration.eek')
        Traceback (most recent call last):
        ...
        ConfigurationError: Module zope.configuration has no global eek
        >>> c.resolve('.config.ConfigurationContext')
        Traceback (most recent call last):
        ...
        AttributeError: 'ConfigurationContext' object has no attribute """ \
                                                                  """'package'
        >>> import zope.configuration
        >>> c.package = zope.configuration
        >>> c.resolve('.config.ConfigurationContext') is ConfigurationContext
        1
        >>> c.resolve('..interface') is zope.interface
        1
        """
        
        name = dottedname.strip()
        names = name.split('.')
        if not names[0]:
            # Got a relative name. Conver it to abs using package info
            if self.package is None:
                raise ConfigurationError(
                    "Can't use leading dots in dotted names, "
                    "no package has been set.")
            pnames = self.package.__name__.split(".")
            pnames.append('')
            while names and not names[0]:
                try:
                    names.pop(0)
                except IndexError:
                    raise ConfigurationError("Invalid global name", name)
                try:
                    pnames.pop()
                except IndexError:
                    raise ConfigurationError("Invalid global name", name)
            names[0:0] = pnames

        # Now we should have an absolute dotted name

        # Split off object name:
        oname, mname = names[-1], '.'.join(names[:-1])

        # Import the module
        if not mname:
            # Just got a single name. Must me a module
            return __import__(oname, *_import_chickens)
        
        try:
            mod = __import__(mname, *_import_chickens)
        except ImportError:
            raise ConfigurationError("Couldn't import %s" % mname)

        try:
            return getattr(mod, oname)
        except AttributeError:
            # No such name, maybe it's a module that we still need to import
            try:
                return __import__(mname+'.'+oname, *_import_chickens)
            except ImportError:
                raise ConfigurationError("Module %s has no global %s"
                                         % (mname, oname))
            
    def path(self, filename):
        """
        Examples:
        
        >>> c = ConfigurationContext()
        >>> c.path("/x/y/z") == os.path.normpath("/x/y/z")
        1
        >>> c.path("y/z")
        Traceback (most recent call last):
        ...
        AttributeError: 'ConfigurationContext' object has no attribute """ \
                                                                 """'package'
        >>> import zope.configuration
        >>> c.package = zope.configuration
        >>> import os
        >>> d = os.path.split(zope.configuration.__file__)[0]
        >>> c.path("y/z") == d + os.path.normpath("/y/z")
        1
        >>> c.path("y/./z") == d + os.path.normpath("/y/z")
        1
        >>> c.path("y/../z") == d + os.path.normpath("/z")
        1
        """

        filename = os.path.normpath(filename)
        if os.path.isabs(filename):
            return filename

        # Got a relative path, combine with base path.
        # If we have no basepath, compute the base path from the package
        # path.

        basepath = getattr(self, 'basepath', '')

        if not basepath:
            if self.package is None:
                basepath = os.getcwd()
            else:
                basepath = os.path.split(self.package.__file__)[0]
            self.basepath = basepath

        return os.path.join(basepath, filename)

    def action(self, discriminator, callable=None, args=(), kw={}):
        """Add an action with the given discriminator, callable and arguments

        For testing purposes, the callable and arguments may be ommitted.
        In that case, a default noop callable is used.

        The discriminator must be given, but it can be None, to indicate that
        the action never conflicts.

        Let's look at some examples:

        >>> c = ConfigurationContext()

        Normally, the context gets actions from subclasses. We'll provide
        an actions attribute ourselves:

        >>> c.actions = []

        We'll use a test callable that has a convenient string representation

        >>> from zope.configuration.tests.directives import f

        >>> c.action(1, f, (1, ), {'x': 1})
        >>> c.actions
        [(1, f, (1,), {'x': 1})]

        >>> c.action(None)
        >>> c.actions
        [(1, f, (1,), {'x': 1}), (None, None)]

        Now set the include path and info:

        >>> c.includepath = ('foo.zcml',)
        >>> c.info = "?"
        >>> c.action(None)
        >>> c.actions[-1]
        (None, None, (), {}, ('foo.zcml',), '?')
        
        """
        action = (discriminator, callable, args, kw,
                  getattr(self, 'includepath', ()),
                  getattr(self, 'info', ''),
                  )

        # remove trailing false items
        while (len(action) > 2) and not action[-1]:
            action = action[:-1]
                  
        self.actions.append(action)

class ConfigurationAdapterRegistry(object):
    """Simple adapter registry that manages directives as adapters

    >>> r = ConfigurationAdapterRegistry()
    >>> c = ConfigurationMachine()
    >>> r.factory(c, ('http://www.zope.com','xxx'))
    Traceback (most recent call last):
    ...
    ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'xxx')
    >>> from zope.configuration.interfaces import IConfigurationContext
    >>> def f(): 
    ...     pass

    >>> r.register(IConfigurationContext, ('http://www.zope.com', 'xxx'), f)
    >>> r.factory(c, ('http://www.zope.com','xxx')) is f
    1
    >>> r.factory(c, ('http://www.zope.com','yyy')) is f
    Traceback (most recent call last):
    ...
    ConfigurationError: ('Unknown directive', 'http://www.zope.com', 'yyy')
    >>> r.register(IConfigurationContext, 'yyy', f)
    >>> r.factory(c, ('http://www.zope.com','yyy')) is f
    1
    """


    def __init__(self):
        self._registry = {} 

    def register(self, interface, name, factory):
        r = self._registry.get(name)
        if r is None:
            r = AdapterRegistry()
            self._registry[name] = r

        r.register(interface, Interface, factory)

    def factory(self, context, name):
        r = self._registry.get(name)
        if r is None:
            # Try namespace-independent name
            ns, n = name
            r = self._registry.get(n)
            if r is None:
                raise ConfigurationError("Unknown directive", ns, n)
            
        f = r.getForObject(context, Interface)
        if f is None:
            raise ConfigurationError(
                "The directive %s cannot ne used in this context" % (name, ))
        return f
  
class ConfigurationMachine(ConfigurationAdapterRegistry, ConfigurationContext):
    """Configuration machine

    Example:

    >>> machine = ConfigurationMachine()
    >>> ns = "http://www.zope.org/testing"

    Register a directive:

    >>> machine((metans, "directive"),
    ...         namespace=ns, name="simple",
    ...         schema="zope.configuration.tests.directives.ISimple",
    ...         handler="zope.configuration.tests.directives.simple")

    and try it out:

    >>> machine((ns, "simple"), a=u"aa", c=u"cc")

    >>> machine.actions
    [(('simple', u'aa', u'xxx', 'cc'), f, (u'aa', u'xxx', 'cc'))]

    A more extensive example can be found in the unit tests.
    """

    implements(IConfigurationContext)

    package = None
    basepath = None
    includepath = ()
    info = ''

    def __init__(self):
        ConfigurationAdapterRegistry.__init__(self)
        self.actions = []
        self.stack = [RootStackItem(self)]
        _bootstrap(self)

    def begin(self, __name, __data=None, __info=None, **kw):
        if __data:
            if kw:
                raise TypeError("Can't provide a mapping object and keyword "
                                "arguments")
        else:
            __data = kw
        self.stack.append(self.stack[-1].contained(__name, __data, __info))

    def end(self):
        self.stack.pop().finish()

    def __call__(self, __name, __info=None, **__kw):
        self.begin(__name, __kw, __info)
        self.end()

    def getInfo(self):
        return self.stack[-1].context.info

    def setInfo(self, info):
        self.stack[-1].context.info = info

    def execute_actions(self, clear=True, testing=False):
        """Execute the configuration actions

        This calls the action callables after resolving conflicts

        For example:

        >>> output = []
        >>> def f(*a, **k):
        ...    output.append(('f', a, k))
        >>> context = ConfigurationMachine()
        >>> context.actions = [
        ...   (1, f, (1,)),
        ...   (1, f, (11,), {}, ('x', )),
        ...   (2, f, (2,)),
        ...   ]
        >>> context.execute_actions()
        >>> output
        [('f', (1,), {}), ('f', (2,), {})]

        If the action raises an error, we convert it to a
        ConfigurationExecutionError.

        >>> output = []
        >>> def bad():
        ...    bad.xxx
        >>> context.actions = [
        ...   (1, f, (1,)),
        ...   (1, f, (11,), {}, ('x', )),
        ...   (2, f, (2,)),
        ...   (3, bad, (), {}, (), 'oops')
        ...   ]
        >>> try:
        ...    v = context.execute_actions()
        ... except ConfigurationExecutionError, v:
        ...    pass
        >>> print v
        exceptions.AttributeError: 'function' object has no attribute 'xxx'
          in:
          oops


        Note that actions executed before the error still have an effect:
        
        >>> output
        [('f', (1,), {}), ('f', (2,), {})]
        
        
        """
        for action in resolveConflicts(self.actions):
            (discriminator, callable, args, kw, includepath, info
             ) = expand_action(*action)
            if callable is None:
                continue
            try:
                callable(*args, **kw)
            except:
                if testing:
                    raise
                t, v = sys.exc_info()[:2]
                v = ConfigurationExecutionError(t, v, info)
                t = ConfigurationExecutionError
                raise t, v, sys.exc_info()[2]

        if clear:
            del self.actions[:]
            

class ConfigurationExecutionError(ConfigurationError):
    """An error occurred during execution of a configuration action
    """

    def __init__(self, etype, evalue, info):
        self.etype, self.evalue, self.info = etype, evalue, info

    def __str__(self):
        return "%s: %s\n  in:\n  %s" % (self.etype, self.evalue, self.info)

##############################################################################
# Stack items

class IStackItem(Interface):
    """Configuration machine stack items

    Stack items are created when a directive if being processed.

    A stack item is created for each directive use.  
    """

    def contained(name, data, info):
        """Begin processing a contained directive

        The data are a dictionary of attribute names mapped to unicode
        strings.

        The info argument is an object that can be converted to a
        string and that contains information about the directive.

        The begin method returns the next item to be placed on the stack.
        """

    def finish():
        """Finish processing a directive
        """

class SimpleStackItem(object):
    """Simple stack item

    A simple stack item can't have anything added after it.  It can
    only be removed.  It is used for simple directives and
    subdirectives, which can't contain other directives.

    It also defers any computation until the end of the directive
    has been reached.
    """

    implements(IStackItem)

    def __init__(self, context, handler, info, *argdata):
        newcontext = GroupingContextDecorator(context)
        newcontext.info = info
        self.context = newcontext
        self.handler = handler
        self.argdata = argdata

    def contained(self, name, data, info):
        raise ConfigurationError("Invalid directive %s" % str(name))

    def finish(self):
        # We're going to use the context that was passed to us, which wasn't
        # created for the directive.  We want to set it's info to the one
        # passed to us while we make the call, so we'll save the old one
        # and restore it.
        context = self.context
        args = toargs(context, *self.argdata)
        actions = self.handler(context, **args)
        if actions:
            # we allow the handler to return nothing
            for action in actions:
                context.action(*action)

class RootStackItem(object):

    def __init__(self, context):
        self.context = context

    def contained(self, name, data, info):
        """Handle a contained directive
        
        We have to compute a new stack item by getting a named adapter
        for the current context object.
        
        """
        factory = self.context.factory(self.context, name)
        if factory is None:
            raise ConfigurationError("Invalid directive", name)
        adapter = factory(self.context, data, info)
        return adapter

    def finish(self):
        pass

class GroupingStackItem(RootStackItem):
    """Stack item for a grouping directive

    A grouping stack item is in the stack when a grouping directive is
    being processed.  Grouping directives group other directives.
    Often, they just manage common data, but they may also take
    actions, either before or after contained directives are executed.

    A grouping stack item us created with a grouping directive
    definition, a configuration context,
    and directive data.

    To see how this works, let's look at an example:

    We need a context. We'll just use a configuration machine

    >>> context = ConfigurationMachine()

    We need a callable to use in configuration actions.  We'll use a
    convenient one from the tests:
    
    >>> from zope.configuration.tests.directives import f

    We need a handler for the grouping directive. This is a class
    that implements a context decorator.  The decorator must also
    provide ``before`` and ``after`` methods that are called before
    and after any contained directives are processed.  We'll typically
    subclass ``GroupingContextDecorator``, which provides context
    decoration, and default ``before`` and ``after`` methods.


    >>> class SampleGrouping(GroupingContextDecorator):
    ...    def before(self):
    ...       self.context.action(('before', self.x, self.y), f)
    ...    def after(self):
    ...       self.context.action(('after'), f)

    We'll use our decorator to decorate out initial context, providing
    keyword arguments x and y:

    >>> dec = SampleGrouping(context, x=1, y=2)

    Note that the keyword arguments are made attributes of the
    decorator.

    Now we'll create the stack item.  

    >>> item = GroupingStackItem(dec)

    The before method was called, which we can verify by looking at
    the context actions:

    >>> context.actions
    [(('before', 1, 2), f)]

    Subdirectives will get looked up as adapters of the context.

    We'll create a simple handler:

    >>> def simple(context, data, info):
    ...     context.action(("simple", context.x, context.y, data), f)
    ...     return info

    and register it with the context:

    This handler isn't really a propert handler, because it doesn't
    return a new context.  It will do for this example.

    >>> context.register(IConfigurationContext, (testns, 'simple'), simple)

    Now we'll call the contained method on the stack item:

    >>> item.contained((testns, 'simple'), {'z': 'zope'}, "someinfo")
    'someinfo'
    
    we can verify thet the simple method was called by looking at the
    context actions:

    >>> from pprint import PrettyPrinter
    >>> pprint=PrettyPrinter(width=60).pprint

    >>> pprint(context.actions)
    [(('before', 1, 2), f),
     (('simple', 1, 2, {'z': 'zope'}), f)]

    Finally, we call finish, which calls the decorator after method:

    >>> item.finish()

    >>> pprint(context.actions)
    [(('before', 1, 2), f),
     (('simple', 1, 2, {'z': 'zope'}), f),
     ('after', f)]


    """
    
    implements(IStackItem)

    def __init__(self, context):
        RootStackItem.__init__(self, context)
        actions = context.before()
        if actions:
            for action in actions:
                context.action(*action)

    def finish(self):
        actions = self.context.after()
        if actions:
            for action in actions:
                self.context.action(*action)

class ComplexStackItem(object):
    """Complex stack item

    A complex stack item is in the stack when a complex directive is
    being processed.  It only allows subdirectives to be used.

    A complex stack item us created with a complex directive
    definition (IComplexDirectiveContext), a configuration context,
    and directive data.

    To see how this works, let's look at an example:

    We need a context. We'll just use a configuration machine

    >>> context = ConfigurationMachine()

    We need a callable to use in configuration actions.  We'll use a
    convenient one from the tests:
    
    >>> from zope.configuration.tests.directives import f

    We need a handler for the complex directive. This is a class
    with a method for each subdirective:
    
    >>> class Handler:
    ...   def __init__(self, context, x, y):
    ...      self.context, self.x, self.y = context, x, y
    ...      context.action('init', f)
    ...   def sub(self, context, a, b):
    ...      context.action(('sub', a, b), f)
    ...   def __call__(self):
    ...      self.context.action(('call', self.x, self.y), f)

    We need a complex directive definition:

    >>> class Ixy(Interface):
    ...    x = zope.schema.TextLine()
    ...    y = zope.schema.TextLine()
    >>> definition = ComplexDirectiveDefinition(
    ...        context, name="test", schema=Ixy,
    ...        handler=Handler)
    >>> class Iab(Interface):
    ...    a = zope.schema.TextLine()
    ...    b = zope.schema.TextLine()
    >>> definition['sub'] = Iab, ''

    OK, now that we have the context, handler and definition, we're
    ready to use a stack item.

    >>> item = ComplexStackItem(definition, context, {'x': u'xv', 'y': u'yv'},
    ...                         'foo')

    When we created the definition, the handler (factory) was called.

    >>> context.actions
    [('init', f, (), {}, (), 'foo')]

    If a subdirective is provided, the ``contained`` method of the stack item
    is called. It will lookup the subdirective schema and call the
    corresponding method on the handler instance:

    >>> simple = item.contained(('somenamespace', 'sub'),
    ...                         {'a': u'av', 'b': u'bv'}, 'baz')
    >>> simple.finish()

    Note that the name passed to ``contained`` is a 2-part name, consisting of
    a namespace and a name within the namespace.

    >>> from pprint import PrettyPrinter
    >>> pprint=PrettyPrinter(width=60).pprint

    >>> pprint(context.actions)
    [('init', f, (), {}, (), 'foo'),
     (('sub', u'av', u'bv'), f, (), {}, (), 'baz')]

    The new stack item returned by contained is one that doesn't allow
    any more subdirectives,

    When all of the subdirectives have been provided, the ``finish`` 
    method is called:

    >>> item.finish()

    The stack item will call the handler if it is callable.

    >>> pprint(context.actions)
    [('init', f, (), {}, (), 'foo'),
     (('sub', u'av', u'bv'), f, (), {}, (), 'baz'),
     (('call', u'xv', u'yv'), f, (), {}, (), 'foo')]
    
    
    """


    implements(IStackItem)

    def __init__(self, meta, context, data, info):
        self.meta = meta
        self.context = context
        self.info = info

        # Call the handler contructor

        # Need to save and restore old info
        oldinfo = context.info
        context.info = info
        
        args = toargs(context, meta.schema, data)
        self.handler = self.meta.handler(context, **args)

        context.info = oldinfo

    def contained(self, name, data, info):
        """Handle a subdirective
        """

        # Look up the subdirective meta data on our meta object
        ns, name = name
        schema = self.meta.get(name)
        if schema is None:
            raise ConfigurationError("Invalid directive", name)
        schema = schema[0] # strip off info
        handler = getattr(self.handler, name)
        return SimpleStackItem(self.context, handler, info, schema, data)

    def finish(self):

        # when we're done, we call the handler, which might return more actions

        # Need to save and restore old info
        oldinfo = self.context.info
        self.context.info = self.info

        try:
            actions = self.handler()
        except AttributeError, v:
            if v[0] == '__call__':
                return # noncallable
            raise
        except TypeError:
            return # non callable

        if actions:
            # we allow the handler to return nothing
            for action in actions:
                self.context.action(*action)

        self.context.info = oldinfo

##############################################################################
# Helper classes

class GroupingContextDecorator(ConfigurationContext):
    """Helper mix-in class for building grouping directives

    See the discussion (and test) id GroupingStackItem.
    """

    implements(IConfigurationContext)
    
    def __init__(self, context, **kw):
        self.context = context
        for name, v in kw.items():
            setattr(self, name, v)

        directlyProvides(self) 

    def __getattr__(self, name,
                    getattr=getattr, setattr=setattr):
        v = getattr(self.context, name)
        # cache result in self
        setattr(self, name, v)
        return v

    def before(self):
        pass

    def after(self):
        pass
    
##############################################################################
# Directive-definition

class DirectiveSchema(fields.GlobalObject):
    """A field that contains a global variable value that must be a schema
    """

    def _validate(self, value):
        super(fields.GlobalObject, self)._validate(value)
        if not IInterface.isImplementedBy(value):
            raise zope.schema.ValidationError(WrongType, value)


class IDirectivesInfo(Interface):
    """Schema for the ``directives`` directive
    """

    namespace = zope.schema.URI(
        title=u"Namespace",
        description=u"The namespace that directives' names will be defined in",
        )

class IDirectivesContext(IDirectivesInfo, IConfigurationContext):
    pass

class DirectivesHandler(GroupingContextDecorator):
    """Handler for the directives directive

    This is just a grouping directive that adds a namespace attribute
    to the normal directive context.
    
    """
    implements(IDirectivesContext)


class IDirectiveInfo(Interface):
    """Information common to all directive definitions have
    """

    name = zope.schema.TextLine(
        title = u"Directive name",
        description = u"The name of the directive being defined",
        )

    schema = DirectiveSchema(
        title = u"Directive handler",
        description = u"The dotted name of the directive handler",
        )

class IFullInfo(IDirectiveInfo):
    """Information that all top-level directives (not subdirectives) have
    """

    handler = fields.GlobalObject(
        title = u"Directive handler",
        description = u"The dotted name of the directive handler",
        )

    usedIn = fields.GlobalObject(
        title = u"The directive types the directive can be used in",
        description = (u"The interface of the directives that can contain "
                       u"the directive"
                       ),
        default = IConfigurationContext,
        value_type = zope.schema.InterfaceField(),
        )

class IStandaloneDirectiveInfo(IDirectivesInfo, IFullInfo):
    """Info for full directives defined outside a directives directives
    """

def defineSimpleDirective(context, name, schema, handler,
                          namespace='', usedIn=IConfigurationContext):
    """Define a simple directive

    Define and register a factory that invokes the simple directive
    and returns a new stack item, which is always the same simple stack item.

    If the namespace is '*', the directive is registered for all namespaces.

    for example:

    >>> context = ConfigurationMachine()
    >>> from zope.configuration.tests.directives import f
    >>> class Ixy(Interface):
    ...    x = zope.schema.TextLine()
    ...    y = zope.schema.TextLine()
    >>> def s(context, x, y):
    ...    context.action(('s', x, y), f)

    >>> defineSimpleDirective(context, 's', Ixy, s, testns)

    >>> context((testns, "s"), x=u"vx", y=u"vy")
    >>> context.actions
    [(('s', u'vx', u'vy'), f)]

    >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy")
    Traceback (most recent call last):
    ...
    ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 's')

    >>> context = ConfigurationMachine()
    >>> defineSimpleDirective(context, 's', Ixy, s, "*")

    >>> context(('http://www.zope.com/t1', "s"), x=u"vx", y=u"vy")
    >>> context.actions
    [(('s', u'vx', u'vy'), f)]

    """ 

    namespace = namespace or context.namespace
    if namespace != '*':
        name = namespace, name

    def factory(context, data, info):
        return SimpleStackItem(context, handler, info, schema, data)

    context.register(usedIn, name, factory)

def defineGroupingDirective(context, name, schema, handler,
                            namespace='', usedIn=IConfigurationContext):
    """Define a grouping directive

    Define and register a factory that sets up a grouping directive.

    If the namespace is '*', the directive is registered for all namespaces.

    for example:

    >>> context = ConfigurationMachine()
    >>> from zope.configuration.tests.directives import f
    >>> class Ixy(Interface):
    ...    x = zope.schema.TextLine()
    ...    y = zope.schema.TextLine()

    We won't bother creating a special grouping directive class. We'll
    just use GroupingContextDecorator, which simple sets up a grouping
    context that has extra attributes defined by a schema:

    >>> defineGroupingDirective(context, 'g', Ixy, 
    ...                         GroupingContextDecorator, testns)

    >>> context.begin((testns, "g"), x=u"vx", y=u"vy")
    >>> context.stack[-1].context.x
    u'vx'
    >>> context.stack[-1].context.y
    u'vy'

    >>> context(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy")
    Traceback (most recent call last):
    ...
    ConfigurationError: ('Unknown directive', 'http://www.zope.com/t1', 'g')

    >>> context = ConfigurationMachine()
    >>> defineGroupingDirective(context, 'g', Ixy, 
    ...                         GroupingContextDecorator, "*")

    >>> context.begin(('http://www.zope.com/t1', "g"), x=u"vx", y=u"vy")
    >>> context.stack[-1].context.x
    u'vx'
    >>> context.stack[-1].context.y
    u'vy'
    
    """

    namespace = namespace or context.namespace
    if namespace != '*':
        name = namespace, name

    def factory(context, data, info):
        args = toargs(context, schema, data)
        newcontext = handler(context, **args)
        newcontext.info = info
        return GroupingStackItem(newcontext)

    context.register(usedIn, name, factory)
    

class IComplexDirectiveContext(IFullInfo, IConfigurationContext):
    pass

class ComplexDirectiveDefinition(GroupingContextDecorator, dict):
    """Handler for defining complex directives

    See the description and tests for ComplexStackItem.
    """

    implements(IComplexDirectiveContext)

    def before(self):

        def factory(context, data, info):
            return ComplexStackItem(self, context, data, info)

        self.context.register(self.usedIn, (self.namespace, self.name),
                              factory)

def subdirective(context, name, schema):
    context.context[name] = schema, context.info


##############################################################################
# Argument conversion

def toargs(context, schema, data):
    """Marshal data to an argument dictionary using a schema

    Names that are python keywords have am underscore added
    as a suffix in the schema and in the argument list, but are used
    without the underscore in the data.

    The fields in the schema must all implement IFromUnicode.

    All of the items in the data must have corresponding fields in the
    schema unless the schema has a true tagged value named
    'keyword_arguments'.

    Here's an example:

    >>> from zope import schema

    >>> class schema(Interface):
    ...     in_ = zope.schema.Int(constraint=lambda v: v > 0)
    ...     f = zope.schema.Float()
    ...     n = zope.schema.TextLine(min_length=1, default=u"rob")
    ...     x = zope.schema.BytesLine(required=False)
    ...     u = zope.schema.URI()

    >>> context = ConfigurationMachine()
    >>> from pprint import PrettyPrinter
    >>> pprint=PrettyPrinter(width=50).pprint

    >>> pprint(toargs(context, schema,
    ...        {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
    ...          'u': u'http://www.zope.org' }))
    {'f': 1.2,
     'in_': 1,
     'n': u'bob',
     'u': 'http://www.zope.org',
     'x': 'x.y.z'}

    If we have extra data, we'll get an error:

    >>> toargs(context, schema,
    ...        {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
    ...          'u': u'http://www.zope.org', 'a': u'1'})
    Traceback (most recent call last):
    ...
    ConfigurationError: ('Unrecognized parameters:', 'a')

    Unless we set a tagged value to say that extra arguments are ok:

    >>> schema.setTaggedValue('keyword_arguments', True)

    >>> pprint(toargs(context, schema,
    ...        {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
    ...          'u': u'http://www.zope.org', 'a': u'1'}))
    {'a': u'1',
     'f': 1.2,
     'in_': 1,
     'n': u'bob',
     'u': 'http://www.zope.org',
     'x': 'x.y.z'}
    

    If we ommit required data we get an error telling us what was omitted:
    
    >>> pprint(toargs(context, schema,
    ...        {'in': u'1', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z'}))
    Traceback (most recent call last):
    ...
    ConfigurationError: ('Missing parameter:', 'u')

    Although we can omit not-required data:

    >>> pprint(toargs(context, schema,
    ...        {'in': u'1', 'f': u'1.2', 'n': u'bob',
    ...          'u': u'http://www.zope.org', 'a': u'1'}))
    {'a': u'1',
     'f': 1.2,
     'in_': 1,
     'n': u'bob',
     'u': 'http://www.zope.org'}

    And we can ommit required fields if they have valid defaults
    (defaults that are valid values):
    

    >>> pprint(toargs(context, schema,
    ...        {'in': u'1', 'f': u'1.2',
    ...          'u': u'http://www.zope.org', 'a': u'1'}))
    {'a': u'1',
     'f': 1.2,
     'in_': 1,
     'n': u'rob',
     'u': 'http://www.zope.org'}

    We also get an error if any data was invalid:
    
    >>> pprint(toargs(context, schema,
    ...        {'in': u'0', 'f': u'1.2', 'n': u'bob', 'x': u'x.y.z',
    ...          'u': u'http://www.zope.org', 'a': u'1'}))
    Traceback (most recent call last):
    ...
    ConfigurationError: ('Invalid value for', 'in',""" \
                                """ "(u'Constraint not satisfied', 0)")

    """

    data = dict(data)
    args = {}
    for name, field in schema.namesAndDescriptions(True):
        field = field.bind(context)
        n = name
        if n.endswith('_') and iskeyword(n[:-1]):
            n = n[:-1]

        s = data.get(n, data)
        if s is not data:
            s = unicode(s)
            del data[n]

            try:
                args[str(name)] = field.fromUnicode(s)
            except zope.schema.ValidationError, v:
                raise ConfigurationError("Invalid value for", n, str(v))
        elif field.required:
            # if the default is valid, we can use that:
            default = field.default
            try:
                field.validate(default)
            except zope.schema.ValidationError:
                raise ConfigurationError("Missing parameter:", n)
            args[str(name)] = default
          
    if data:
        # we had data left over
        try:
            keyword_arguments = schema.getTaggedValue('keyword_arguments')
        except KeyError:
            keyword_arguments = False
        if not keyword_arguments:
            raise ConfigurationError("Unrecognized parameters:", *data)

        for name in data:
            args[str(name)] = data[name]

    return args

##############################################################################
# Conflict resolution

def expand_action(discriminator, callable=None, args=(), kw={},
                   includepath=(), info=''):
    return (discriminator, callable, args, kw,
            includepath, info)

def resolveConflicts(actions):
    """Resolve conflicting actions

    Given an actions list, identify and try to resolve conflicting actions.
    Actions conflict if they have the same non-null discriminator.
    Conflicting actions can be resolved if the include path of one of
    the actions is a prefix of the includepaths of the other
    conflicting actions and is unequal to the include paths in the
    other conflicting actions.

    Here are some examples to illustrate how this works:

    >>> from zope.configuration.tests.directives import f
    >>> from pprint import PrettyPrinter
    >>> pprint=PrettyPrinter(width=60).pprint
    >>> pprint(resolveConflicts([
    ...    (None, f),
    ...    (1, f, (1,), {}, (), 'first'),
    ...    (1, f, (2,), {}, ('x',), 'second'),
    ...    (1, f, (3,), {}, ('y',), 'third'),
    ...    (4, f, (4,), {}, ('y',)),
    ...    (3, f, (3,), {}, ('y',)),
    ...    (None, f, (5,), {}, ('y',)),
    ... ]))
    [(None, f),
     (1, f, (1,), {}, (), 'first'),
     (4, f, (4,), {}, ('y',)),
     (3, f, (3,), {}, ('y',)),
     (None, f, (5,), {}, ('y',))]

    >>> try:
    ...     v = resolveConflicts([
    ...        (None, f),
    ...        (1, f, (2,), {}, ('x',), 'eek'),
    ...        (1, f, (3,), {}, ('y',), 'ack'),
    ...        (4, f, (4,), {}, ('y',)),
    ...        (3, f, (3,), {}, ('y',)),
    ...        (None, f, (5,), {}, ('y',)),
    ...     ])
    ... except ConfigurationConflictError, v:
    ...    pass
    >>> print v
    Conflicting configuration actions
      For: 1
        eek
        ack
     
    """

    # organize actions by discriminators
    unique = {}
    output = []
    for i in range(len(actions)):
        (discriminator, callable, args, kw, includepath, info
         ) = expand_action(*(actions[i]))
        if discriminator is None:
            # The discriminator is None, so this directive can
            # never conflict. We can add it directly to the
            # configuration actions.
            output.append(
                (i, discriminator, callable, args, kw, includepath, info)
                )
            continue

        
        a = unique.setdefault(discriminator, [])
        a.append(
            (includepath, i, callable, args, kw, info)
            )

    # Check for conflicts
    conflicts = {}
    for discriminator, dups in unique.items():

        # We need to sort the actions by the paths so that the shortest
        # path with a given prefix comes first:
        dups.sort()
        (basepath, i, callable, args, kw, baseinfo) = dups[0]
        output.append(
            (i, discriminator, callable, args, kw, basepath, baseinfo)
            )
        for includepath, i, callable, args, kw, info in dups[1:]:
            # Test whether path is a prefix of opath
            if (includepath[:len(basepath)] != basepath # not a prefix
                or
                (includepath == basepath)
                ):
                if discriminator not in conflicts:
                    conflicts[discriminator] = [baseinfo]
                conflicts[discriminator].append(info)
            

    if conflicts:
        raise ConfigurationConflictError(conflicts)

    # Now put the output back in the original order, and return it:
    output.sort()
    r = []
    for o in output:
        action = o[1:]
        while len(action) > 2 and not action[-1]:
            action = action[:-1]
        r.append(action)
        
    return r

class ConfigurationConflictError(ConfigurationError):

    def __init__(self, conflicts):
        self._conflicts = conflicts

    def __str__(self):
        r = ["Conflicting configuration actions"]
        for discriminator, infos in self._conflicts.items():
            r.append("  For: %s" % (discriminator, ))
            for info in infos:
                for line in unicode(info).rstrip().split(u'\n'):
                    r.append(u"    "+line)

        return "\n".join(r)


##############################################################################
# Bootstap code


def _bootstrap(context):

    # Set enough machinery to register other directives

    # Define the directive (simple directive) directive by calling it's
    # handler directly

    defineSimpleDirective(
        context,
        namespace=metans, name='directive',
        schema=IStandaloneDirectiveInfo,
        handler=defineSimpleDirective)

    # OK, now that we have that, we can use the machine to define the
    # other directives. This isn't the easiest way to proceed, but it lets
    # us eat our own dogfood. :)

    # Standalone groupingDirective
    context((metans, 'directive'),
            name='groupingDirective',
            namespace=metans,
            handler="zope.configuration.config.defineGroupingDirective",
            schema="zope.configuration.config.IStandaloneDirectiveInfo"
            )

    # Now we can use the grouping directive to define the directives directive
    context((metans, 'groupingDirective'),
            name='directives',
            namespace=metans,
            handler="zope.configuration.config.DirectivesHandler",
            schema="zope.configuration.config.IDirectivesInfo"
            )
    
    # directive and groupingDirective inside directives
    context((metans, 'directive'),
            name='directive',
            namespace=metans,
            usedIn="zope.configuration.config.IDirectivesContext",
            handler="zope.configuration.config.defineSimpleDirective",
            schema="zope.configuration.config.IFullInfo"
            )
    context((metans, 'directive'),
            name='groupingDirective',
            namespace=metans,
            usedIn="zope.configuration.config.IDirectivesContext",
            handler="zope.configuration.config.defineGroupingDirective",
            schema="zope.configuration.config.IFullInfo"
            )
                          
    # Setup complex directive directive, both standalone, and in
    # directives directive
    context((metans, 'groupingDirective'),
            name='complexDirective',
            namespace=metans,
            handler="zope.configuration.config.ComplexDirectiveDefinition",
            schema="zope.configuration.config.IStandaloneDirectiveInfo"
            )
    context((metans, 'groupingDirective'),
            name='complexDirective',
            namespace=metans,
            usedIn="zope.configuration.config.IDirectivesContext",
            handler="zope.configuration.config.ComplexDirectiveDefinition",
            schema="zope.configuration.config.IFullInfo"
            )

    # Finally, setup subdirective directive
    context((metans, 'directive'),
            name='subdirective',
            namespace=metans,
            usedIn="zope.configuration.config.IComplexDirectiveContext",
            handler="zope.configuration.config.subdirective",
            schema="zope.configuration.config.IDirectiveInfo"
            )

    import zope.configuration.backward
    zope.configuration.backward.bootstrap(context)


=== Added File Zope3/src/zope/configuration/fields.py ===
##############################################################################
#
# Copyright (c) 2003 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-specific schema fields

$Id: fields.py,v 1.1 2003/07/28 22:22:39 jim Exp $
"""

from zope import schema
from zope.schema.interfaces import IFromUnicode
from zope.configuration.exceptions import ConfigurationError
from zope.interface import implements

class GlobalObject(schema.Field):
    """An object that can be accesses as a module global
    """

    implements(IFromUnicode)

    def __init__(self, value_type=None, **kw):
        self.value_type = value_type
        super(GlobalObject, self).__init__(**kw)

    def _validate(self, value):
        super(GlobalObject, self)._validate(value)
        if self.value_type is not None:
            self.value_type.validate(value)

    def fromUnicode(self, u):
        """

        Examples:

        First, we need to set up a stub name resolver:
        >>> d = {'x': 1, 'y': 42, 'z': 'zope'}
        >>> class fakeresolver(dict):
        ...     def resolve(self, n):
        ...         return self[n]

        >>> fake = fakeresolver(d)


        >>> g = GlobalObject(value_type=schema.Int())
        >>> gg = g.bind(fake)
        >>> gg.fromUnicode("x")
        1
        >>> gg.fromUnicode("   x  \\n  ")
        1
        >>> gg.fromUnicode("y")
        42
        >>> gg.fromUnicode("z")
        Traceback (most recent call last):
        ...
        ValidationError: (u'Wrong type', 'zope', (<type 'int'>, <type 'long'>))

        >>> g = GlobalObject(constraint=lambda x: x%2 == 0)
        >>> gg = g.bind(fake)
        >>> gg.fromUnicode("x")
        Traceback (most recent call last):
        ...
        ValidationError: (u'Constraint not satisfied', 1)
        >>> gg.fromUnicode("y")
        42
        >>> 

        """
        name = str(u.strip())
        try:
            value = self.context.resolve(name)
        except ConfigurationError, v:
            raise schema.ValidationError(v)
            
        self.validate(value)
        return value

class GlobalObjects(schema.Sequence):
    """A sequence of global objects
    """

    implements(IFromUnicode)

    def fromUnicode(self, u):
        """
        Examples:

        First, we need to set up a stub name resolver:
        >>> d = {'x': 1, 'y': 42, 'z': 'zope', 'x.y.x': 'foo'}
        >>> class fakeresolver(dict):
        ...     def resolve(self, n):
        ...         return self[n]

        >>> fake = fakeresolver(d)


        >>> g = GlobalObjects()
        >>> gg = g.bind(fake)
        >>> gg.fromUnicode("  \\n  x y z  \\n")
        [1, 42, 'zope']

        >>> g = GlobalObjects(value_type=
        ...                   schema.Int(constraint=lambda x: x%2 == 0))
        >>> gg = g.bind(fake)
        >>> gg.fromUnicode("x y")
        Traceback (most recent call last):
        ...
        ValidationError: (u'Wrong contained type', """ \
                                    """[Constraint not satisfied 1])
        >>> gg.fromUnicode("z y")
        Traceback (most recent call last):
        ...
        ValidationError: (u'Wrong contained type', """ \
                     """[Wrong type zope (<type 'int'>, <type 'long'>)])
        >>> gg.fromUnicode("y y")
        [42, 42]
        >>> 

        """
        values = [self.context.resolve(name)
                  for name in str(u.strip()).split()]
        self.validate(values)
        return values

class Bool(schema.Bool):

    implements(IFromUnicode)

    def fromUnicode(self, u):
        u = u.lower()
        if u in ('1', 'true', 'yes', 't', 'y'):
            return True
        if u in ('0', 'false', 'no', 'f', 'n'):
            return False
        raise schema.ValidationError


=== Zope3/src/zope/configuration/__init__.py 1.2 => 1.3 ===
--- Zope3/src/zope/configuration/__init__.py:1.2	Wed Dec 25 09:13:33 2002
+++ Zope3/src/zope/configuration/__init__.py	Mon Jul 28 18:22:39 2003
@@ -20,18 +20,3 @@
 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/src/zope/configuration/interfaces.py 1.1 => 1.2 ===
--- Zope3/src/zope/configuration/interfaces.py:1.1	Mon Dec 30 21:52:10 2002
+++ Zope3/src/zope/configuration/interfaces.py	Mon Jul 28 18:22:39 2003
@@ -13,68 +13,61 @@
 ##############################################################################
 
 from zope.interface import Interface
+from zope.schema import BytesLine
 
-class INonEmptyDirective(Interface):
+class IConfigurationContext(Interface):
+    """Configuration Context
 
-    def __call__(context,**kw):
-        """Compute subdirective handler
-
-        context -- an execution context that the directive may use for
-          things like resolving names
-
-        kw -- a dictionary containing the values of any attributes
-          that were specified on the directive
+    The configuration context manages information about the state of
+    the configuration system, such as the package containing the
+    configuration file. More importantly, it provides methods for
+    importing objects and opening files relative to the package.
+    """
 
-        Return an ISubdirectiveHandler.
+    package = BytesLine(
+        title=u"The current package name",
+        description=u"""\
+          This is the name of the package containing the configuration
+          file being executed. If the configuration file was not
+          included by package, then this is None.
+          """,
+        required=False,
+        )
+
+    def resolve(dottedname):
+        """Resolve a dotted name to an object
+
+        A dotted name is constructed by concatenating a dotted module
+        name with a global name within the module using a dot.  For
+        example, the object named "spam" in the foo.bar module has a
+        dotted name of foo.bar.spam.  If the current package is a
+        prefix of a dotted name, then the package name can be relaced
+        with a leading dot, So, for example, if the configuration file
+        is in the foo package, then the dotted name foo.bar.spam can
+        be shortened to .bar.spam.
+
+        If the current package is multiple levels deel, multiple
+        leading dots can be used to refer to higher-level modules.
+        For example, if the current package is x.y.z, the dotted
+        object name ..foo refers to x.y.foo.
         """
 
-class IEmptyDirective(Interface):
-
-    def __call__(context,**kw):
-        """Compute configuration actions
-
-        context -- an execution context that the directive may use for
-          things like resolving names
-
-        kw -- a dictionary containing the values of any attributes
-          that were specified on the directive
-
-        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
+    def path(filename):
+        """Compute a full file name for the given file
 
-        - 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.
+        If the filename is relative to the package, then the returned
+        name will include the package path, otherwise, the original
+        file name is returned.
         """
 
+    def action(self, discriminator, callable, args=(), kw={}):
+        """Record a configuration action
 
-class ISubdirectiveHandler(Interface):
-    """Handle subdirectives
-
-    Provide methods for registered subdirectives. The methods are
-    typically IEmptyDirective objects. They could, theoretically be
-    INonEmptyDirective objects.
-
-    Also provide a call that can provide additional configuration
-    actions.
-
-    """
-
-    def __call__():
-        """Return a sequence of configuration actions.
-
-        See IEmptyDirective for a definition of configuration actions.
-
-        This method should be called *after* any subdirective methods are
-        called during the processing of the (sub)directive whose subdirectives
-        are being processed.  It may return an empty list.
+        The job of most directives is to compute actions for later processing.
+        The action method is used to record those actions.  The discriminator
+        is used to to find actions that conflict. Actions conflict if they
+        have the same discriminator. The exception to this is the
+        special case of the discriminator with the value None. An
+        actions with a discriminator of None never conflicts with
+        other actions.
         """


=== Zope3/src/zope/configuration/xmlconfig.py 1.9 => 1.10 ===
--- Zope3/src/zope/configuration/xmlconfig.py:1.9	Mon Jun 23 10:44:13 2003
+++ Zope3/src/zope/configuration/xmlconfig.py	Mon Jul 28 18:22:39 2003
@@ -11,7 +11,12 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""
+"""Support for the XML configuration file format
+
+Note, for a detailed description of the way that conflicting
+configuration actions are resolved, see the detailed example in
+test_includeOverrides in tests/text_xmlconfig.py
+
 $Id$
 """
 
@@ -19,50 +24,49 @@
 import os
 import sys
 import logging
+import zope.configuration.config as config
 from keyword import iskeyword
-from types import StringType
-from os.path import abspath
-
+from zope import schema
 from xml.sax import make_parser
 from xml.sax.xmlreader import InputSource
 from xml.sax.handler import ContentHandler, feature_namespaces
 from xml.sax import SAXParseException
-from zope.configuration import name
-from zope.configuration.meta import begin, sub, end
 from zope.configuration.exceptions import ConfigurationError
-
-# marker used in Context class and XMLConfig class to indicate
-# that a particular zcml file was given no "package" attribute
-# when included, and the same went for all of its parents.
-_NO_MODULE_GIVEN = object()
-
+from zope.interface import Interface, implements
 
 logger = logging.getLogger("config")
 
 class ZopeXMLConfigurationError(ConfigurationError):
-    """Zope XML Configuration error"""
+    """Zope XML Configuration error
 
-    def __init__(self, locator, mess, etype=None):
-        if etype is None:
-            if not isinstance(mess, StringType):
-                try:
-                    mess = "\n%s:\n  %s" % (mess.__class__.__name__, mess)
-                except AttributeError:
-                    mess = str(mess)
-        else:
-            mess = "\n%s: %s" % (etype.__name__, mess)
+    These errors are wrappers for other errors. The include configuration
+    info and the wrapped error type and value:
 
-        self.lno = locator.getLineNumber()
-        self.cno = locator.getColumnNumber()
-        self.sid = locator.getSystemId()
-        self.mess = mess
+    >>> v = ZopeXMLConfigurationError("blah", AttributeError, "xxx")
+    >>> print v
+    'blah'
+        AttributeError: xxx
+        
+    """
 
-    def __str__(self):
-        return 'File "%s", line %s.%s\n\t%s' % (
-            self.sid, self.lno, self.cno, self.mess)
+    def __init__(self, info, etype, evalue):
+        self.info, self.etype, self.evalue = info, etype, evalue
 
+    def __str__(self):
+        # Only use the repr of the info. This is because we expect to
+        # get a parse info and we only want the location information.
+        return "%s\n    %s: %s" % (
+            `self.info`, self.etype.__name__, self.evalue)
 
 class ZopeSAXParseException(ConfigurationError):
+    """Sax Parser errors, reformatted in an emacs friendly way
+
+    >>> v = ZopeSAXParseException("foo.xml:12:3:Not well formed")
+    >>> print v
+    File "foo.xml", line 12.3, Not well formed
+    
+    """
+    
     def __init__(self, v):
         self._v = v
 
@@ -74,237 +78,239 @@
         else:
             return str(v)
 
-class ConfigurationExecutionError(ZopeXMLConfigurationError):
-    """An error occurred during execution of a configuration action
-    """
+class ParserInfo:
+    """Information about a directive based on parser data
 
-    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)
+    This includes the directive location, as well as text data
+    contained in the directive.
+
+    >>> info = ParserInfo('tests//sample.zcml', 1, 1)
+    >>> info
+    File "tests//sample.zcml", line 1.1
+
+    >>> print info
+    File "tests//sample.zcml", line 1.1
+
+    >>> info.characters("blah\\n")
+    >>> info.characters("blah")
+    >>> info.text
+    u'blah\\nblah'
+
+    >>> info.end(7,16)
+    >>> info
+    File "tests//sample.zcml", line 1.1-7.16
+
+    >>> print info
+    File "tests//sample.zcml", line 1.1-7.16
+      <zopeConfigure xmlns='http://namespaces.zope.org/zope'>
+        <!-- zope.configure -->
+        <directives namespace="http://namespaces.zope.org/zope">
+          <directive name="hook" attributes="name implementation module"
+             handler="zope.configuration.metaconfigure.hook" />
+        </directives>
+      </zopeConfigure>
+    
+    
+    """
 
-        self.lno, self.cno, self.sid = locator
-        self.mess = mess
+    text = u''
 
+    def __init__(self, file, line, column):
+        self.file, self.line, self.column = file, line, column
+        self.eline, self.ecolumn = line, column
+
+    def end(self, line, column):
+        self.eline, self.ecolumn = line, column
+
+    def __repr__(self):
+        if (self.line, self.column) == (self.eline, self.ecolumn):
+            return 'File "%s", line %s.%s' % (
+                self.file, self.line, self.column)
+            
+        return 'File "%s", line %s.%s-%s.%s' % (
+            self.file, self.line, self.column, self.eline, self.ecolumn)
 
+    def __str__(self):
+        if (self.line, self.column) == (self.eline, self.ecolumn):
+            return 'File "%s", line %s.%s' % (
+                self.file, self.line, self.column)
+
+        file = self.file
+        if file == 'tests//sample.zcml':
+            # special case for testing
+            file = os.path.join(os.path.split(__file__)[0],
+                                'tests', 'sample.zcml')
+        
+        try:
+            f = open(file)
+        except IOError:
+            src = "  Could not read source."
+        else:
+            lines = f.readlines()[self.line-1:self.eline]
+            lines[0] = lines[0][self.column-1:]
+            lines[-1] = lines[-1][:self.ecolumn]
+            src = ''.join([u"  "+l for l in lines])
+
+        return "%s\n%s" % (`self`, src)
+
+    def characters(self, characters):
+        self.text += characters
+        
+    
 class ConfigurationHandler(ContentHandler):
+    """Interface toi the cml parser
 
-    __top_name = 'zopeConfigure'
+    Translate parser events into calls into the configuration system.
+    """
 
-    def __init__(self, actions, context, directives=None, testing=0):
-        self.__stack = []
-        self.__actions = actions
-        self.__directives = directives
-        self.__context = context
-        self.__testing = testing
+    def __init__(self, context, testing=0):
+        self.context = context
+        self.testing = testing
 
     def setDocumentLocator(self, locator):
-        self.__locator = locator
+        self.locator = locator
 
     def characters(self, text):
-        stack = self.__stack
-        if len(stack) > 1:
-            base = stack[-1][0]
-            if hasattr(base, 'zcmlText'):
-                base.zcmlText(text)
+        self.context.getInfo().characters(text)
 
     def startElementNS(self, name, qname, attrs):
-        stack = self.__stack
-        if not stack:
-            if name[1] != 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 = {}
+        
+        data = {}
         for (ns, aname), value in attrs.items():
             if ns is None:
                 aname = str(aname)
-                if iskeyword(aname): aname += '_'
-                kw[aname] = value
+                data[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
+        info = ParserInfo(
+            self.locator.getSystemId(),
+            self.locator.getLineNumber(),
+            self.locator.getColumnNumber(),
+            )
 
         try:
-            for des, callable, args, kw in actions:
-                append((self.__context,
-                        (self.__locator.getLineNumber(),
-                         self.__locator.getColumnNumber(),
-                         self.__locator.getSystemId(),
-                         ), des, callable, args, kw))
+            self.context.begin(name, data, info)
         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
-        File "%s", line %s column %s
-        File "%s", line %s column %s
-        """ % (self.des,
-               self.l1[2], self.l1[0], self.l1[1],
-               self.l2[2], self.l2[0], self.l2[1],
-               )
-
-
-class Context:
-    def __init__(self, stack, module):
-        self.__stackcopy = tuple(stack)
-        if module is _NO_MODULE_GIVEN:
-            self.__package = None
-        elif 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 packageWasSet(self):
-        return self.__package is not None
-
-    def package(self):
-        return self.__package
+            if self.testing:
+                raise
+            raise ZopeXMLConfigurationError, (
+                info, sys.exc_info()[0], sys.exc_info()[1]
+                ), sys.exc_info()[2]
 
+        self.context.setInfo(info)
+        
 
-def xmlconfig(file, actions=None, context=None, directives=None, testing=0):
-    if context is None:
-        context = name
+    def endElementNS(self, name, qname):
+        info = self.context.getInfo()
+        info.end(
+            self.locator.getLineNumber(),
+            self.locator.getColumnNumber(),
+            )
+        
+        try:
+            self.context.end()
+        except:
+            if self.testing:
+                raise
+            raise ZopeXMLConfigurationError, (
+                info, sys.exc_info()[0], sys.exc_info()[1]
+                ), sys.exc_info()[2]
+
+
+class IZopeConfigure(Interface):
+
+    package = config.fields.GlobalObject(__doc__="""Package
+
+        The package to be used for evaluating relative imports and file names.
+        """,
+        required=False)
+    
+    domain = schema.BytesLine(__doc__="""Internationalization domain
+
+        This is a name for the software project. It must be a legal file-system
+        name as it will be used to contruct names for directories containing
+        translation data.
+
+        The domain defines a namespace for the message ids used by a project.
+        """,
+        required=False)
+
+class ZopeConfigure(config.GroupingContextDecorator):
+
+    implements(config.IConfigurationContext)
+
+    def __init__(self, context, **kw):
+        super(ZopeConfigure, self).__init__(context, **kw)
+        if 'package' in kw:
+            # if we have a package, we want to also define basepath
+            # so we don't acquire one
+            self.basepath = os.path.split(self.package.__file__)[0]
+            
+
+def _register_configure(context):
+
+    # We have to use the direct definition function to define
+    # a directive for all namespaces.
+    config.defineGroupingDirective(
+        context,
+        name="zopeConfigure",
+        namespace="*",
+        schema=IZopeConfigure,
+        handler=ZopeConfigure,
+        )
+    config.defineGroupingDirective(
+        context,
+        name="configure",
+        namespace="*",
+        schema=IZopeConfigure,
+        handler=ZopeConfigure,
+        )
+    
 
-    if actions is None:
-        call = actions = []
-    else:
-        call = 0
+def processxmlfile(file, context, testing=0):
+    """Process a configuration file
 
+    See examples in tests/text_xmlconfig.py
+    """
     src = InputSource(getattr(file, 'name', '<string>'))
     src.setByteStream(file)
     parser = make_parser()
-    parser.setContentHandler(
-        ConfigurationHandler(actions, context,directives,
-                             testing=testing)
-        )
+    parser.setContentHandler(ConfigurationHandler(context, testing=testing))
     parser.setFeature(feature_namespaces, 1)
     try:
         parser.parse(src)
     except SAXParseException:
         raise ZopeSAXParseException, sys.exc_info()[1], sys.exc_info()[2]
 
-    if call:
-        descriptors = {}
-        for level, loc, des, callable, args, kw in call:
-            if des is not None:
-                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('  File "%s", line %s column %s' %
-                         (loc[2], loc[0], loc[1]))
-
-        return "\n".join(r)
-
-
-def inopen(filename):
-    # XXX I don't really like the name of this function
+def openInOrPlain(filename):
     """Open a file, falling back to filename.in.
 
     If the requested file does not exist and filename.in does, fall
     back to filename.in.  If opening the original filename fails for
     any other reason, allow the failure to propogate.
+
+    For example, the tests/samplepackage dirextory has files:
+
+       configure.zcml
+       configure.zcml.in
+       foo.zcml.in
+
+    If we open configure.zcml, we'll get that file:
+
+    >>> here = os.path.split(__file__)[0]
+    >>> path = os.path.join(here, 'tests', 'samplepackage', 'configure.zcml')
+    >>> f = openInOrPlain(path)
+    >>> f.name[-14:]
+    'configure.zcml'
+
+    But if we open foo.zcml, we'll get foo.zcml.in, since there isn't a
+    foo.zcml:
+
+    >>> path = os.path.join(here, 'tests', 'samplepackage', 'foo.zcml')
+    >>> f = openInOrPlain(path)
+    >>> f.name[-11:]
+    'foo.zcml.in'
+    
     """
     try:
         fp = open(filename)
@@ -317,133 +323,177 @@
                 raise
     return fp
 
+class IInclude(Interface):
+
+    file = schema.BytesLine(
+        __doc__=
+        """Configuration file name
+
+        The name of a configuration file to be included,
+        relative to the directive containing the
+        including configuration file.
+        
+        """,
+        default="configure.zcml",
+        )
+
+    package = config.fields.GlobalObject(
+        __doc__=
+        """Include packahe
+
+        Include the named file (or configure.zcml)
+        from the directory of this package.
+        """,
+        required=False,
+        )
+
+
+def include(_context, file, package=None):
+    """Include a zcml file
+    
+    See examples in tests/text_xmlconfig.py
+    """
+    
+    logger.debug("include %s" % file)
+        
+    # This is a tad tricky. We want to behave a a grouping directive.
+    context = config.GroupingContextDecorator(_context)
+    if package is not None:
+        context.package = package
+        context.basepath = None
+    path = context.path(file)
+    f = openInOrPlain(path)
+    
+    logger.debug("include %s" % f.name)
+
+    context.basepath = os.path.split(path)[0]
+    context.includepath = _context.includepath + (f.name, ) 
+    _context.stack.append(config.GroupingStackItem(context))
+
+    processxmlfile(f, context)
+    f.close()
+    assert _context.stack[-1].context is context
+    _context.stack.pop()
+
+def includeOverrides(_context, file, package=None):
+    """Include zcml file containing overrides
+
+    The actions in the included file are added to the context as if they
+    were in the including file directly.
+
+    See the detailed example in test_includeOverrides in
+    tests/text_xmlconfig.py
+    """
+
+    # We need to remember how many actions we had before
+    nactions = len(_context.actions)
+
+    # We'll give the new actions this include path
+    includepath = _context.includepath
+
+    # Now we'll include the file. We'll munge the actions after
+    include(_context, file, package)
+
+    # Now we'll grab the new actions, resolve conflicts,
+    # and munge the includepath:
+    newactions = []
+    for action in config.resolveConflicts(_context.actions[nactions:]):
+        (discriminator, callable, args, kw, oldincludepath, info
+         ) = config.expand_action(*action)
+        newactions.append(
+            (discriminator, callable, args, kw, includepath, info)
+            )
+
+    # and replace the new actions with the munched new actions:
+    _context.actions[nactions:] = newactions
+
+def _registerIncludes(context):
+    # Register the include directives
+    config.defineSimpleDirective(
+        context, "include", IInclude, include, namespace="*")
+    config.defineSimpleDirective(
+        context, "includeOverrides", IInclude, includeOverrides, namespace="*")
+    
+    _register_configure(context)
+
+def file(name, package=None, context=None, execute=True):
+    """Execute a zcml file
+    """
+
+    if context is None:
+        context = config.ConfigurationMachine()
+        _registerIncludes(context)
+        context.package = package
+        
+    include(context, name, package)
+    if execute:
+        context.execute_actions()
+        
+    return context
+
+def string(s, context=None, name="test.string", execute=True):
+    """Execute a zcml string
+    """
+    from StringIO import StringIO
+
+    if context is None:
+        context = config.ConfigurationMachine()
+        _registerIncludes(context)
+        
+    f = StringIO(s)
+    f.name = name
+    processxmlfile(f, context)
+
+    if execute:
+        context.execute_actions()
+        
+    return context
+
+
+##############################################################################
+# Backward compatability, mainly for tests
+
+
+_context = None
+def _clearContext():
+    global _context
+    _context = config.ConfigurationMachine()
+    _registerIncludes(_context)
+    
+def _getContext():
+    global _context
+    if _context is None:
+        _clearContext()
+        from zope.testing.cleanup import addCleanUp
+        addCleanUp(_clearContext)
+    return _context
 
 class XMLConfig:
+    """Provide high-level handling of configuration files.
 
-    def __init__(self, file_name, module=_NO_MODULE_GIVEN):
-        if module is not None and module is not _NO_MODULE_GIVEN:
-            module_dir = abspath(os.path.split(module.__file__)[0])
-            file_name = os.path.join(module_dir, file_name)
-
-        self._actions = []
-        self._directives = {('*', 'include'):
-                            (self.include, {})}
-
-        f = inopen(file_name)
-        self._stack = [file_name]
-        xmlconfig(f, self._actions,
-                  Context(self._stack, module=module),
-                  self._directives)
-        f.close()
-
-    def include(self, _context, file='configure.zcml', package=None):
-        if package is None and _context.packageWasSet():
-            package = _context.package()
-        subpackages = False
-        if package is not None:
-            if package.endswith('.*'):
-                # <include package="package.*" /> includes all subpackages
-                subpackages = True
-                parent = package = package[:-2]
-                if package == "":
-                    package = "."
-            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])
+    See examples in tests/text_xmlconfig.py    
+    """
 
-        if subpackages:
-            for subdir in os.listdir(prefix):
-                file_name = os.path.join(prefix, subdir, file)
-                if not os.access(file_name, os.F_OK):
-                    continue
-                subpackage = "%s.%s" % (parent, subdir)
-                subpackage = _context.resolve(subpackage)
-                self._include(file_name, subpackage)
-        else:
-            file_name = os.path.join(prefix, file)
-            self._include(file_name, package)
-        return ()
-
-    def _include(self, file_name, package):
-        logger.debug("include %s" % file_name)
-        f = inopen(file_name)
-        self._stack.append(file_name)
-        xmlconfig(f, self._actions, Context(self._stack, package),
-                  self._directives)
-        self._stack.pop()
-        f.close()
+    def __init__(self, file_name, module=None):
+        context = _getContext()
+        include(context, file_name, module)
+        self.context = context
 
     def __call__(self):
-        self.organize()
+        self.context.execute_actions()
+
+def xmlconfig(file, testing=False):
+    context = _getContext()
+    processxmlfile(file, context, testing=testing)
+    context.execute_actions(testing=testing)
+
 
-    def __iter__(self):
-        return iter(self._actions)
+def testxmlconfig(file, context=None):
+    """xmlconfig that doesn't raise configuration errors
 
-    def organize(self):
-        actions = self._actions
+    This is useful for testing, as it doesn't mask exception types.
+    """
+    context = _getContext()
+    processxmlfile(file, context, testing=True)
+    context.execute_actions(testing=True)
 
-        # organize actions by discriminators
-        unique = {}
-        cactions = []
-        for i in range(len(actions)):
-            context, loc, des, callable, args, kw = actions[i]
-            if des is None:
-                # The descriminator is None, so this directive can
-                # never conflict. We can add it directly to the
-                # configuration actions.
-                cactions.append((i, loc, (callable, args, kw)))
-                continue
-
-            a = unique.setdefault(des, [])
-            a.append((context._stackcopy(), i, loc, (callable, args, kw)))
-
-        # Check for conflicts
-        conflicts = {}
-        for des, actions in unique.items():
-
-            # We need to sort the actions by the paths so that the shortest
-            # path with a given prefix comes first:
-            actions.sort()
-
-            path, i, loc, f = actions[0]
-            for opath, i, oloc, f in actions[1:]:
-                # Test whether path is a prefix of opath
-                if opath[:len(path)] != path or (opath == path):
-                    if des not in conflicts:
-                        conflicts[des] = [loc]
-                    conflicts[des].append(oloc)
-
-        if conflicts:
-            raise ZopeConfigurationConflictError(conflicts)
-
-        # Now order the configuration directives
-        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]

=== Removed File Zope3/src/zope/configuration/meta.py ===

=== Removed File Zope3/src/zope/configuration/metameta.zcml ===

=== Removed File Zope3/src/zope/configuration/metametaconfigure.py ===

=== Removed File Zope3/src/zope/configuration/metametaconfigurefordocgen.py ===

=== Removed File Zope3/src/zope/configuration/metametafordocgen.zcml ===