[Zope-Checkins] CVS: Zope3/lib/python/Zope/Configuration - Action.py:1.2 ConfigurationDirectiveInterfaces.py:1.2 Exceptions.py:1.2 HookRegistry.py:1.2 __init__.py:1.2 configuration-meta.zcml:1.2 meta.py:1.2 metaConfigure.py:1.2 name.py:1.2 xmlconfig.py:1.2
Jim Fulton
jim@zope.com
Mon, 10 Jun 2002 19:29:55 -0400
Update of /cvs-repository/Zope3/lib/python/Zope/Configuration
In directory cvs.zope.org:/tmp/cvs-serv20468/lib/python/Zope/Configuration
Added Files:
Action.py ConfigurationDirectiveInterfaces.py Exceptions.py
HookRegistry.py __init__.py configuration-meta.zcml meta.py
metaConfigure.py name.py xmlconfig.py
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.
=== Zope3/lib/python/Zope/Configuration/Action.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+def Action(discriminator, callable, args=(), kw={}):
+ return discriminator, callable, args, kw
+
=== Zope3/lib/python/Zope/Configuration/ConfigurationDirectiveInterfaces.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Configuration directives
+
+$Id$
+"""
+from Interface import Interface
+
+class IEmptyDirective(Interface):
+
+ def __call__(**kw):
+ """Compute configuration actions
+
+ Return a sequence of configuration actions. Each action is a
+ tuple with:
+
+ - A discriminator, value used to identify conflicting
+ actions. Actions conflict if they have the same values
+ for their discriminators.
+
+ - callable object
+
+ - argument tuple
+
+ - and, optionally, a keyword argument dictionary.
+
+ The callable object will be called with the argument tuple and
+ keyword arguments to perform the action.
+ """
+
+class INonEmptyDirective(Interface):
+
+ def __call__(**kw):
+ """Compute complex directive handler
+
+ Return an IComplexDirectiveHandler
+ """
+
+class ISubdirectiveHandler(Interface):
+ """Handle subdirectives
+
+ Provide mehods for registered subdirectives.
+
+ Also provide a call that can provide additional configuration actions.
+ """
+
+ def __call__():
+ """Return a sequence of configuration actions."""
=== Zope3/lib/python/Zope/Configuration/Exceptions.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Standard configuration errors
+"""
+
+class ConfigurationError(Exception):
+ """There was an error in a configuration
+ """
+
=== Zope3/lib/python/Zope/Configuration/HookRegistry.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+
+from types import ModuleType
+from Zope.Exceptions import DuplicationError, NotFoundError, ZopeError
+import name
+
+class MissingHookableError(NotFoundError):
+ """the stated hook has not been registered"""
+
+class DuplicateHookError(DuplicationError):
+ """an implementation for the given hook has already been registered"""
+
+class BadHookableError(ZopeError):
+ """hookable cannot be found or is not usable"""
+
+class BadHookError(ZopeError):
+ """hook cannot be set"""
+
+class HookRegistry:
+ def __init__(self):
+ self._reg = {}
+
+ def addHookable(self, hname):
+ if hname in self._reg:
+ raise DuplicationError(hname)
+ try:
+ defaultimpl = name.resolve(hname)
+ except ImportError:
+ raise BadHookableError("hookable %s cannot be found" % hname)
+
+ parent, last=self._getParentAndLast(hname)
+ implfunc="%s_hook" % last
+
+ if getattr(parent, implfunc, self) is self:
+ raise BadHookableError(
+ """default hookable implementation (%s) cannot be found;
+ note it must be in the same module as the hookable""" %
+ implfunc)
+
+ self._reg[hname] = 0
+
+ def addHook(self, hookablename, hookname):
+
+ if not (hookablename in self._reg):
+ raise MissingHookableError(hookablename)
+ if self._reg[hookablename]:
+ raise DuplicateHookError(hookablename, hookname)
+ try:
+ implementation = name.resolve(hookname)
+ except ImportError:
+ raise BadHookError('cannot find implementation', hookname)
+ try:
+ hookableDefault=name.resolve(hookablename)
+ except:
+ raise BadHookableError(
+ 'hookable cannot be found, but was found earlier: '
+ 'some code has probably masked the hookable',
+ hookablename)
+
+ # This won't work as is: I'd have to create a NumberTypes and do
+ # various annoying checks
+ #if type(implementation) is not type (hookableDefault):
+ # raise BadHookError(
+ # 'hook and hookable must be same type')
+
+ # if they are functions, could check to see if signature is same
+ # (somewhat tricky because functions and methods could be
+ # interchangable but would have a different signature because
+ # of 'self')
+
+ # for now I'll leave both of the above to the sanity of the site
+ # configuration manager...
+
+ # find and import immediate parent
+
+ parent,last = self._getParentAndLast(hookablename)
+
+ # set parent.last to implementation
+ setattr(parent, "%s_hook" % last, implementation)
+
+ self._reg[hookablename] = hookname
+
+ def _getParentAndLast(self, hookablename):
+ if hookablename.endswith('.') or hookablename.endswith('+'):
+ hookablename = hookablename[:-1]
+ repeat = 1
+ else:
+ repeat = 0
+ names = hookablename.split(".")
+ last = names.pop()
+ importname = ".".join(names)
+ if not importname:
+ if not repeat:
+ raise BadHookableError(
+ 'hookable cannot be on top level of Python namespace',
+ hookablename)
+ importname = last
+ parent = __import__(importname, {}, {}, ('__doc__',))
+ child = getattr(parent, last, self)
+ if child is self:
+ raise BadHookableError(
+ 'hookable cannot be on top level of Python namespace',
+ hookablename)
+ while repeat:
+ grand = getattr(child, last, self)
+ if grand is self:
+ break
+ parent = child
+ child = grand
+
+ if type(parent) is not ModuleType:
+ raise BadHookableError("parent of hookable must be a module")
+
+ return parent, last
+
+ def getHooked(self):
+ return [(key, self._reg[key])
+ for key in self._reg
+ if self._reg[key]]
+
+ def getUnhooked(self):
+ return [(key, self._reg[key])
+ for key in self._reg
+ if not self._reg[key]]
+
+ def getHookables(self):
+ return [(key, self._reg[key])
+ for key in self._reg]
=== Zope3/lib/python/Zope/Configuration/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Zope configuration support
+
+Software that wants to provide new config directives calls
+Zope.Configuration.meta.register.
+"""
+
+def namespace(suffix):
+ return 'http://namespaces.zope.org/'+suffix
+
+import sys, os
+from Zope.Configuration.xmlconfig import XMLConfig
+
+def config(dir):
+ try:
+ XMLConfig(os.path.join(dir, 'site.zcml'))()
+ except:
+ # Use the ExceptionFormatter to provide XMLconfig debug info
+ from Zope.Exceptions.ExceptionFormatter import format_exception
+ exc_info = ['='*72, '\nZope Configuration Error\n', '='*72, '\n'] \
+ + apply(format_exception, sys.exc_info())
+ sys.stderr.write(''.join(exc_info))
+ sys.exit(0) # Fatal config error
+
+__all__ = ["namespace", "config"]
=== Zope3/lib/python/Zope/Configuration/configuration-meta.zcml 1.1 => 1.2 ===
+
+ <!-- Zope.Configure -->
+ <directives namespace="http://namespaces.zope.org/zope">
+ <directive name="hookable" attributes="name module"
+ handler="Zope.Configuration.metaConfigure.provideHookable" />
+ <directive name="hook" attributes="name implementation module"
+ handler="Zope.Configuration.metaConfigure.provideHook" />
+ </directives>
+
+</zopeConfigure>
=== Zope3/lib/python/Zope/Configuration/meta.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Registration of registration directives
+
+See ConfigurationDirectiveInterfaces
+
+"""
+
+#
+
+from ConfigurationDirectiveInterfaces import INonEmptyDirective
+from ConfigurationDirectiveInterfaces import ISubdirectiveHandler
+
+
+_directives = {}
+
+class InvalidDirective(Exception):
+ "An invalid directive was used"
+
+class BrokenDirective(Exception):
+ "A directive is implemented incorrectly"
+
+def register(name, callable):
+ """Register a top-level directive
+
+ The name argument is a tuple with a namespace URI and an
+ name string.
+
+ The callable must be am IEmptyDirective or an INonEmptyDirective.
+
+ INonEmptyDirective directives may have subdirectives. The
+ subdirectives will be registered in a registry that is stored with
+ the directive. The sub-directive registry is returned so that
+ it can be used for subsequent sub-directive registration.
+
+ """
+
+ subdirs = {}
+ _directives[name] = callable, subdirs
+ return subdirs
+
+def registersub(directives, name, handler_method=None):
+ """Register a subdirective
+
+ directives is the subdirective registry for the containing
+ directive, which may be either a top-level directive or an
+ intermediate sub-directive (if subdirectives are nested more than
+ two deep.
+
+ The name argument is a tuple with a namespace URI and an
+ name string.
+
+ The handler is not passed as it normally is for top-level
+ directives. Rather, the handler is looked up as an attribute of
+ the top-level directive object using the name string that is the
+ second element in the name tuple. An optional handler attribute
+ can be used to specify the method to be used.
+
+ Subdirectives may have subdirectives. The subdirectives will be
+ registered in a registry that is stored with the containing
+ subdirective. The sub-directive registry is returned so that it
+ can be used for subsequent sub-directive registration.
+
+ """
+ if not handler_method:
+ handler_method = name[1]
+ subdirs = {}
+ directives[name] = subdirs, handler_method
+ return subdirs
+
+def _exe(callable, subs, context, kw):
+ r = callable(context, **kw)
+
+ if subs or INonEmptyDirective.isImplementedBy(callable):
+ return r, subs
+ else:
+ return (
+ # We already have our list of actions, but we're expected to
+ # provide a callable that returns one.
+ (lambda: r),
+
+ subs,
+ )
+
+def begin(_custom_directives, _name, _context, **kw):
+ """Begin executing a top-level directive
+
+ A custom registry is provided to provides specialized directive
+ handlers in addition to the globally registered directives. For
+ example, the XML configuration mechanism uses these to provide XML
+ configuration file directives.
+
+ The _name argument is a tuple with a namespace URI and an
+ name string.
+
+ Thje _context argument is an execution context objects that
+ directives use for functions like resolving names. It will be
+ passed as the first argument to the directive handler.
+
+ kw are the directive arguments.
+
+ The return value is a tuple that contains:
+
+ - An object to be called to finish directive processing. This
+ object will return a sequence of actions. The object must be
+ called after sub-directives are processes.
+
+ - A registry for looking up subdirectives.
+
+ """
+
+ if _custom_directives and (_name in _custom_directives):
+ callable, subs = _custom_directives[_name]
+ else:
+ try:
+ callable, subs = _directives[_name]
+ except KeyError:
+ raise InvalidDirective(_name)
+
+ return _exe(callable, subs, _context, kw)
+
+def sub(subs, _name, _context, **kw):
+ """Begin executing a subdirective
+
+ The first argument, subs, is a registry of allowable subdirectives
+ for the containing directive or subdirective.
+
+ The _name argument is a tuple with a namespace URI and an
+ name string.
+
+ Thje _context argument is an execution context objects that
+ directives use for functions like resolving names. It will be
+ passed as the first argument to the directive handler.
+
+ kw are the directive arguments.
+
+ The return value is a tuple that contains:
+
+ - An object to be called to finish directive processing. This
+ object will return a sequence of actions. The object must be
+ called after sub-directives are processes.
+
+ - A registry for looking up subdirectives.
+
+ """
+
+ base, subdirs = subs
+ try:
+ subs = subdirs[_name]
+ except KeyError:
+ raise InvalidDirective(_name)
+
+ # this is crufty.
+ # if this is a tuple, it means we created it as such in
+ # registersub, and so we grab item 1 as the handler_method
+ # and rebind subs as item 0
+
+ if isinstance(subs, tuple):
+ handler_method = subs[1]
+ subs = subs[0]
+ else:
+ handler_method = _name[1]
+ callable = getattr(base, handler_method)
+
+ return _exe(callable, subs, _context, kw)
+
+defaultkw = ({},)
+def end(base):
+ """Finish processing a directive or subdirective
+
+ The argument is a return value from begin or sub. It's first
+ element is called to get a sequence of actions.
+
+ The actions are normalized to a 4-element tuple with a
+ descriminator, a callable, positional arguments, and keyword
+ arguments.
+ """
+
+ actions = base[0]()
+ ractions = []
+ for action in actions:
+ if len(action) < 3 or len(action) > 4:
+ raise BrokenDirective(action)
+ if len(action) == 3:
+ action += defaultkw
+ ractions.append(action)
+ return ractions
+
+class DirectiveNamespace:
+
+ def __init__(self, _context, namespace):
+ self._namespace = namespace
+
+ def directive(self, _context, name, handler, attributes='',
+ namespace=None):
+ namespace = namespace or self._namespace
+ subs = register((namespace, name), _context.resolve(handler))
+ return Subdirective(subs, namespace=namespace)
+
+ def __call__(self):
+ return ()
+
+def Directive(_context, namespace, name, handler, attributes=''):
+ subs = register((namespace, name), _context.resolve(handler))
+ return Subdirective(subs, namespace=namespace)
+
+Directive.__implements__ = INonEmptyDirective
+
+class InvaliDirectiveDefinition(Exception): pass
+
+class Subdirective:
+ """This is the meta-meta-directive"""
+ #
+ # Unlike other directives, it doesn't return any actions, but
+ # takes action right away, since it's actions are needed to process other
+ # directives.
+ #
+ # For this reason, this isn't a good directive example.
+
+ __implements__ = ISubdirectiveHandler
+
+ def __init__(self, subs, namespace=None):
+ self._subs = subs
+ self._namespace = namespace
+
+ def subdirective(self, _context, name, attributes='',
+ namespace=None, handler_method=None):
+ namespace = namespace or self._namespace
+ if not namespace:
+ raise InvaliDirectiveDefinition(name)
+ #if not handler_method:
+ # handler_method = name
+ subs = registersub(self._subs, (namespace, name), handler_method)
+ return Subdirective(subs)
+
+ def __call__(self):
+ return ()
+
+def _clear():
+ "To support unit tests"
+ _directives.clear()
+ _directives[('http://namespaces.zope.org/zope', 'directive')] = (
+ Directive, {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ }}}})
+ _directives[('http://namespaces.zope.org/zope', 'directives')] = (
+ DirectiveNamespace, {
+ ('http://namespaces.zope.org/zope', 'directive'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ ('http://namespaces.zope.org/zope', 'subdirective'): {
+ }}}}})
+
+_clear()
+
+# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
+from Zope.Testing.CleanUp import addCleanUp
+addCleanUp(_clear)
+del addCleanUp
=== Zope3/lib/python/Zope/Configuration/metaConfigure.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+from Action import Action
+from HookRegistry import HookRegistry
+
+# one could make hookRegistry a service and
+# theoretically use it TTW, but that doesn't immediately seem like a
+# great idea
+hookRegistry = HookRegistry()
+
+addHookable = hookRegistry.addHookable
+addHook = hookRegistry.addHook
+
+def provideHookable(_context, name, module=None):
+ if module:
+ name = "%s.%s" % (module, name)
+ name = _context.getNormalizedName(name)
+ return [
+ Action(
+ discriminator=('addHookable', name),
+ callable=addHookable,
+ args=(name,)
+ )
+ ]
+
+
+def provideHook(_context, name, implementation, module=None):
+ if module:
+ name = "%s.%s" % (module, name)
+ name = _context.getNormalizedName(name)
+ implementation = _context.getNormalizedName(implementation)
+ return [
+ Action(
+ discriminator=('addHook', name),
+ callable=addHook,
+ args=(name, implementation)
+ )
+ ]
+
+
+
=== Zope3/lib/python/Zope/Configuration/name.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Provide configuration object name resolution
+
+$Id$
+"""
+
+import os
+import sys
+from types import ModuleType
+
+def resolve(name, package='ZopeProducts', _silly=('__doc__',), _globals={}):
+ name = name.strip()
+
+ if name.startswith('.'):
+ name=package+name
+
+ if name.endswith('.') or name.endswith('+'):
+ name = name[:-1]
+ repeat = 1
+ else:
+ repeat = 0
+
+ names=name.split('.')
+ last=names[-1]
+ mod='.'.join(names[:-1])
+
+ if not mod:
+ return __import__(name, _globals, _globals, _silly)
+
+ while 1:
+ m=__import__(mod, _globals, _globals, _silly)
+ try:
+ a=getattr(m, last)
+ except AttributeError:
+ if not repeat:
+ return __import__(name, _globals, _globals, _silly)
+
+ else:
+ if not repeat or (not isinstance(a, ModuleType)):
+ return a
+ mod += '.' + last
+
+
+def getNormalizedName(name, package):
+ name=name.strip()
+ if name.startswith('.'):
+ name=package+name
+
+ if name.endswith('.') or name.endswith('+'):
+ name = name[:-1]
+ repeat = 1
+ else:
+ repeat = 0
+ name=name.split(".")
+ while len(name)>1 and name[-1]==name[-2]:
+ name.pop()
+ repeat=1
+ name=".".join(name)
+ if repeat:
+ name+="+"
+ return name
+
+def path(file='', package = 'ZopeProducts', _silly=('__doc__',), _globals={}):
+ try: package = __import__(package, _globals, _globals, _silly)
+ except ImportError:
+ if file and os.path.abspath(file) == file:
+ # The package didn't matter
+ return file
+ raise
+
+ path = os.path.split(package.__file__)[0]
+ if file:
+ path = os.path.join(path, file)
+ return path
=== Zope3/lib/python/Zope/Configuration/xmlconfig.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import os
+import name
+from xml.sax import make_parser
+from xml.sax.xmlreader import InputSource
+from xml.sax.handler import ContentHandler, feature_namespaces
+from meta import begin, sub, end
+from keyword import iskeyword
+import sys, os
+from types import StringType
+from Exceptions import ConfigurationError
+
+class ZopeXMLConfigurationError(ConfigurationError):
+ "Zope XML Configuration error"
+
+ def __init__(self, locator, mess, etype=None):
+ if etype is None:
+ if not isinstance(mess, StringType):
+ try:
+ mess = "\n%s: %s" % (mess.__class__.__name__, mess)
+ except AttributeError:
+ mess = str(mess)
+ else:
+ mess = "\n%s: %s" % (etype.__name__, mess)
+
+ self.lno = locator.getLineNumber()
+ self.cno = locator.getColumnNumber()
+ self.sid = locator.getSystemId()
+ self.mess = mess
+
+ def __str__(self):
+ return 'File "%s", line %s, column %s\n\t%s' % (
+ self.sid, self.lno, self.cno, self.mess)
+
+class ConfigurationExecutionError(ZopeXMLConfigurationError):
+ """An error occurred during execution of a configuration action
+ """
+
+ def __init__(self, locator, mess, etype=None):
+ if etype is None:
+ if isinstance(mess, StringType):
+ try:
+ mess = "%s: %s" % (mess.__class__.__name__, mess)
+ except AttributeError:
+ mess = str(mess)
+ else:
+ mess = "\n%s: %s" % (etype.__name__, mess)
+
+ self.lno, self.cno, self.sid = locator
+ self.mess = mess
+
+class ConfigurationHandler(ContentHandler):
+
+ __top_name = 'http://namespaces.zope.org/zope', 'zopeConfigure'
+
+ def __init__(self, actions, context, directives=None, testing=0):
+ self.__stack = []
+ self.__actions = actions
+ self.__directives = directives
+ self.__context = context
+ self.__testing = testing
+ context.resolve
+
+ def setDocumentLocator(self, locator):
+ self.__locator = locator
+
+ def startElementNS(self, name, qname, attrs):
+ stack = self.__stack
+ if not stack:
+ if name != self.__top_name:
+ raise ZopeXMLConfigurationError(
+ self.__locator, "Invalid top element: %s %s" % name)
+
+ for (ns, aname), value in attrs.items():
+ if ns is None:
+ self.__context.file_attr(aname, value)
+
+
+ stack.append(None)
+ return
+
+ kw = {}
+ for (ns, aname), value in attrs.items():
+ if ns is None:
+ aname = str(aname)
+ if iskeyword(aname): aname += '_'
+ kw[aname] = value
+
+ if len(stack) == 1:
+ try:
+ stack.append(
+ begin(self.__directives, name, self.__context, **kw)
+ )
+ except Exception, v:
+ if self.__testing:
+ raise
+ raise ZopeXMLConfigurationError, (
+ self.__locator, v), sys.exc_info()[2]
+
+ else:
+ subs = self.__stack[-1]
+ if subs is None:
+ raise ZopeXMLConfigurationError(self.__locator,
+ 'Invalid sub-directive')
+ try:
+ stack.append(sub(subs, name, self.__context, **kw))
+ except Exception, v:
+ if self.__testing:
+ raise
+ raise ZopeXMLConfigurationError, (
+ self.__locator, v), sys.exc_info()[2]
+
+ def endElementNS(self, name, qname):
+ subs = self.__stack.pop()
+ # to fool compiler that thinks actions is used before assignment:
+ actions = ()
+
+ if subs is not None:
+ try:
+ actions = end(subs)
+ except Exception, v:
+ if self.__testing:
+ raise
+ raise ZopeXMLConfigurationError, (
+ self.__locator, str(v)), sys.exc_info()[2]
+
+ append = self.__actions.append
+
+ try:
+ for des, callable, args, kw in actions:
+ append((self.__context,
+ (self.__locator.getLineNumber(),
+ self.__locator.getColumnNumber(),
+ self.__locator.getSystemId(),
+ ), des, callable, args, kw))
+ except:
+ print 'endElementNS', actions
+ raise
+
+class ZopeConflictingConfigurationError(ZopeXMLConfigurationError):
+ "Zope XML Configuration error"
+
+ def __init__(self, l1, l2, des):
+ self.l1 = l1
+ self.l2 = l2
+ self.des = des
+
+ def __str__(self):
+ return """Conflicting configuration action:
+ %s
+ at line %s column %s of %s
+ and% at line %s column %s of %s
+ """ % ((self.des,) + self.l1 + self.l2)
+
+class Context:
+ def __init__(self, stack, module=None):
+ self.__stackcopy = tuple(stack)
+ if module is None:
+ self.__package = 'ZopeProducts'
+ else:
+ self.__package = module.__name__
+
+ def _stackcopy(self):
+ return self.__stackcopy
+
+ def resolve(self, dottedname):
+ return name.resolve(dottedname, self.__package)
+
+ def getNormalizedName(self, dottedname):
+ return name.getNormalizedName(dottedname, self.__package)
+
+ def path(self, file=None):
+ return name.path(file, self.__package)
+
+ def file_attr(self, name, value):
+ if name == 'package':
+ self.__package = value
+ else:
+ raise TypeError, "Unrecognized config file attribute: %s" % name
+
+
+def xmlconfig(file, actions=None, context=None, directives=None,
+ testing=0):
+ if context is None:
+ context = name
+
+ if actions is None:
+ call = actions = []
+ else:
+ call = 0
+
+ src = InputSource(getattr(file, 'name', '<string>'))
+ src.setByteStream(file)
+ parser = make_parser()
+ parser.setContentHandler(
+ ConfigurationHandler(actions, context,directives,
+ testing=testing)
+ )
+ parser.setFeature(feature_namespaces, 1)
+ parser.parse(src)
+
+ if call:
+ descriptors = {}
+ for level, loc, des, callable, args, kw in call:
+ if des in descriptors:
+ raise ZopeConflictingConfigurationError(
+ descriptors[des], loc, des)
+ descriptors[des] = loc
+ callable(*args, **kw)
+
+def testxmlconfig(file, actions=None, context=None, directives=None):
+ """xmlconfig that doesn't raise configuration errors
+
+ This is useful for testing, as it doesn't mask exception types.
+ """
+ return xmlconfig(file, actions, context, directives, testing=1)
+
+class ZopeConfigurationConflictError(ZopeXMLConfigurationError):
+
+ def __init__(self, conflicts):
+ self._conflicts = conflicts
+
+ def __str__(self):
+ r = ["Conflicting configuration actions"]
+ for dis, locs in self._conflicts.items():
+ r.append('for: %s' % (dis,))
+ for loc in locs:
+ r.append(" at line %s column %s of %s" % loc)
+
+ return "\n".join(r)
+
+class XMLConfig:
+
+ def __init__(self, file_name):
+ self._actions = []
+ self._directives = {('http://namespaces.zope.org/zope', 'include'):
+ (self.include, {})}
+
+ f = open(file_name)
+ self._stack = [file_name]
+ xmlconfig(f, self._actions, Context(self._stack), self._directives)
+ f.close()
+
+ def include(self, _context, file='config.zcml', package=None):
+ if package is not None:
+ try:
+ package = _context.resolve(package)
+ if len(package.__path__) != 1:
+ print ("Module Path: '%s' has wrong number of elements"
+ % str(package.__path__))
+ # XXX: This should work for 99% of cases
+ # We may want to revisit this with a more robust
+ # mechanism later. Specifically, sometimes __path__
+ # will have more than one element. Also, we could
+ # use package.__file__, and lop the tail off that.
+ prefix = package.__path__[0]
+ except (ImportError, AttributeError, ValueError), v:
+ raise # XXX the raise below hides the real error
+ raise ValueError("Invalid package attribute: %s\n(%s)"
+ % (package, `v`))
+ else:
+ prefix = os.path.dirname(self._stack[-1])
+
+ file_name = os.path.join(prefix, file)
+
+ f = open(file_name)
+ self._stack.append(file_name)
+ xmlconfig(f, self._actions, Context(self._stack, package),
+ self._directives)
+ self._stack.pop()
+ f.close()
+ return ()
+
+ def __call__(self):
+ self.organize()
+
+ def __iter__(self): return iter(self._actions)
+
+ def organize(self):
+ actions = self._actions
+
+ # organize actions by discriminators
+ unique = {}
+ for i in range(len(actions)):
+ context, loc, des, callable, args, kw = actions[i]
+ a = unique.setdefault(des, [])
+ a.append((context._stackcopy(), i, loc, (callable, args, kw)))
+
+ # Check for conflicts
+ conflicts = {}
+ for des, actions in unique.items():
+ path, i, loc, f = actions[0]
+ for opath, i, oloc, f in actions[1:]:
+ if opath[:len(path)] != path:
+ if des not in conflicts:
+ conflicts[des] = [loc]
+ conflicts[des].append(oloc)
+
+ if conflicts:
+ raise ZopeConfigurationConflictError(conflicts)
+
+ # Now order the configuration directives
+ cactions = []
+ for des, actions in unique.items():
+ path, i, loc, f = actions.pop(0)
+ cactions.append((i, loc, f))
+
+ unique = None
+
+ cactions.sort()
+
+ # Call actions
+ for i, loc, f in cactions:
+ try:
+ callable, args, kw = f
+ callable(*args, **kw)
+ except Exception, v:
+ raise ConfigurationExecutionError, (
+ loc, v, sys.exc_info()[0]), sys.exc_info()[2]