[Zope-Checkins] CVS: Zope3/lib/python/Zope/Configuration - Exceptions.py:1.1.4.1 Action.py:1.1.2.4 ConfigurationDirectiveInterfaces.py:1.1.2.4 HookRegistry.py:1.1.2.3 configuration-meta.zcml:1.1.2.2 meta.py:1.1.2.10 metaConfigure.py:1.1.2.2 name.py:1.1.2.13 xmlconfig.py:1.1.2.16

Jim Fulton jim@zope.com
Fri, 7 Jun 2002 10:41:54 -0400


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

Modified Files:
      Tag: Zope-3x-branch
	Action.py ConfigurationDirectiveInterfaces.py HookRegistry.py 
	configuration-meta.zcml meta.py metaConfigure.py name.py 
	xmlconfig.py 
Added Files:
      Tag: Zope-3x-branch
	Exceptions.py 
Log Message:
Merging in Zope3InWonderland-branch, which implemented the following
proposals (see
http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/OldProposals): 
- RenameAllowToRequire

- GroupClassRelatedDirectivesInClassDirective

- ViewInterfaceAndSimplification

- ConsistentUseOfSpacesAsDelimitersInZCMLAttributes

- TwoArgumentViewConstructors

- ImplementsInZCML

- SimpleViewCreationInZCML

- RemoveGetView

- ReplaceProtectWithAllow

- ViewMethodsAsViews

- MergeProtectionAndComponentDefinitions

There were also various security fixes resulting of better integration
of security with components.


=== Added File Zope3/lib/python/Zope/Configuration/Exceptions.py ===
##############################################################################
#
# 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/Action.py 1.1.2.3 => 1.1.2.4 ===
 """
 
-def Action( discriminator, callable, args=(), kw={}):
+def Action(discriminator, callable, args=(), kw={}):
     return discriminator, callable, args, kw
 


=== Zope3/lib/python/Zope/Configuration/ConfigurationDirectiveInterfaces.py 1.1.2.3 => 1.1.2.4 ===
 
         - A discriminator, value used to identify conflicting
-          actions. Actions cnflict of they have the same value values
+          actions. Actions conflict if they have the same values
           for their discriminators.
 
         - callable object


=== Zope3/lib/python/Zope/Configuration/HookRegistry.py 1.1.2.2 => 1.1.2.3 ===
             raise DuplicationError(hname)
         try:
-            defaultimpl=name.resolve(hname)
+            defaultimpl = name.resolve(hname)
         except ImportError:
-            raise BadHookableError(
-                "hookable %s cannot be found" % hname)
+            raise BadHookableError("hookable %s cannot be found" % hname)
         
         parent, last=self._getParentAndLast(hname)
         implfunc="%s_hook" % last
@@ -53,7 +52,7 @@
                 note it must be in the same module as the hookable""" %
                 implfunc)
         
-        self._reg[hname]=0
+        self._reg[hname] = 0
     
     def addHook(self, hookablename, hookname):
         
@@ -62,14 +61,15 @@
         if self._reg[hookablename]:
             raise DuplicateHookError(hookablename, hookname)
         try:
-            implementation=name.resolve(hookname)
+            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',
+                '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
@@ -88,12 +88,12 @@
             
         # find and import immediate parent
         
-        parent, last=self._getParentAndLast(hookablename)
+        parent,last = self._getParentAndLast(hookablename)
         
         # set parent.last to implementation
         setattr(parent, "%s_hook" % last, implementation)
         
-        self._reg[hookablename]=hookname
+        self._reg[hookablename] = hookname
     
     def _getParentAndLast(self, hookablename):
         if hookablename.endswith('.') or hookablename.endswith('+'):
@@ -101,25 +101,27 @@
             repeat = 1
         else:
             repeat = 0
-        names=hookablename.split(".")
-        last=names.pop()
-        importname=".".join(names)
+        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)
+                    '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)
+                'hookable cannot be on top level of Python namespace',
+                hookablename)
         while repeat:
-            grand=getattr(child, last, self)
+            grand = getattr(child, last, self)
             if grand is self:
                 break
-            parent=child
-            child=grand
+            parent = child
+            child = grand
         
         if type(parent) is not ModuleType:
             raise BadHookableError("parent of hookable must be a module")
@@ -127,10 +129,15 @@
         return parent, last
     
     def getHooked(self):
-        return [(key, self._reg[key]) for key in self._reg if self._reg[key]]
+        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]]
+        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]
\ No newline at end of file
+        return [(key, self._reg[key])
+                for key in self._reg]


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


=== Zope3/lib/python/Zope/Configuration/meta.py 1.1.2.9 => 1.1.2.10 ===
 """
 
+# 
+
 from ConfigurationDirectiveInterfaces import INonEmptyDirective
 from ConfigurationDirectiveInterfaces import ISubdirectiveHandler
 
@@ -30,13 +32,51 @@
     "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):
+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
+    directives[name] = subdirs, handler_method
     return subdirs
 
 def _exe(callable, subs, context, kw):
@@ -45,9 +85,41 @@
     if subs or INonEmptyDirective.isImplementedBy(callable):
         return r, subs
     else:
-        return lambda: r, subs
+        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:
@@ -59,20 +131,62 @@
     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)
-
-    callable = getattr(base, _name[1])
+        
+    # 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:
@@ -92,14 +206,14 @@
                   namespace=None):
         namespace = namespace or self._namespace
         subs = register((namespace, name), _context.resolve(handler))
-        return Subdirective(subs, namespace)
+        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)
+    return Subdirective(subs, namespace=namespace)
 
 Directive.__implements__ = INonEmptyDirective
 
@@ -120,12 +234,14 @@
         self._subs = subs
         self._namespace = namespace
 
-    def subdirective(self, _context, name, attributes='', namespace=None):
+    def subdirective(self, _context, name, attributes='',
+                     namespace=None, handler_method=None):
         namespace = namespace or self._namespace
         if not namespace:
             raise InvaliDirectiveDefinition(name)
-            
-        subs = registersub(self._subs, (namespace, name))
+        #if not handler_method:
+        #    handler_method = name
+        subs = registersub(self._subs, (namespace, name), handler_method)
         return Subdirective(subs)
 
     def __call__(self):


=== Zope3/lib/python/Zope/Configuration/metaConfigure.py 1.1.2.1 => 1.1.2.2 ===
 $Id$
 """
-
 from Action import Action
 from HookRegistry import HookRegistry
 
-hookRegistry=HookRegistry() # one could make this a service and
+# 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
+addHookable = hookRegistry.addHookable
+addHook = hookRegistry.addHook
 
 def provideHookable(_context, name, module=None):
     if module:
-        name="%s.%s" % (module, name)
-    name=_context.getNormalizedName(name)
+        name = "%s.%s" % (module, name)
+    name = _context.getNormalizedName(name)
     return [
         Action(
-            discriminator = ('addHookable', name),
-            callable = addHookable,
-            args = (name,)
+            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)
+        name = "%s.%s" % (module, name)
+    name = _context.getNormalizedName(name)
+    implementation = _context.getNormalizedName(implementation)
     return [
         Action(
-            discriminator = ('addHook', name),
-            callable = addHook,
-            args = (name, implementation)
+            discriminator=('addHook', name),
+            callable=addHook,
+            args=(name, implementation)
             )
         ]
 


=== Zope3/lib/python/Zope/Configuration/name.py 1.1.2.12 => 1.1.2.13 ===
 """
 
+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
 
@@ -67,4 +70,17 @@
     name=".".join(name)
     if repeat:
         name+="+"
-    return name
\ No newline at end of file
+    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.2.15 => 1.1.2.16 ===
 """
 
+import os
 import name
 from xml.sax import make_parser
 from xml.sax.xmlreader import InputSource
@@ -24,77 +25,95 @@
 from keyword import iskeyword
 import sys, os
 from types import StringType
+from Exceptions import ConfigurationError
 
-class ZopeXMLConfigurationError(Exception):
+class ZopeXMLConfigurationError(ConfigurationError):
     "Zope XML Configuration error"
 
-    def __init__(self, locator, mess):
-        if not isinstance(mess, StringType):
-            try:
-                mess = "\n%s: %s" % (mess.__class__.__name__, mess)
-            except AttributeError:
-                mess=str(mess)
-        self.lno=locator.getLineNumber()
-        self.cno=locator.getColumnNumber()
-        self.sid=locator.getSystemId()
-        self.mess=mess
+    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 "%s\nat line %s column %s of %s" % (
-            self.mess, self.lno, self.cno, self.sid)
+        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):
-        if isinstance(mess, StringType):
-            try:
-                mess = "%s: %s" % (mess.__class__.__name__, mess)
-            except AttributeError:
-                mess=str(mess)
+    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
+        self.mess = mess
 
 class ConfigurationHandler(ContentHandler):
 
     __top_name = 'http://namespaces.zope.org/zope', 'zopeConfigure' 
 
-    def __init__(self, actions, context, directives=None):
+    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
+        self.__locator = locator
 
     def startElementNS(self, name, qname, attrs):
-        stack=self.__stack
+        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={}
+        kw = {}
         for (ns, aname), value in attrs.items():
             if ns is None:
-                aname=str(aname)
+                aname = str(aname)
                 if iskeyword(aname): aname += '_'
-                kw[aname]=value
-                
+                kw[aname] = value
+
         if len(stack) == 1:
             try:
-                stack.append(begin(self.__directives, name, self.__context,
-                                   **kw))
-            except Exception, v:
-                __traceback_supplement__ = (
-                    ConfigurationTracebackSupplement, self.__locator, v
+                stack.append(
+                    begin(self.__directives, name, self.__context, **kw)
                     )
-                raise
+            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:
@@ -103,11 +122,11 @@
             try:
                 stack.append(sub(subs, name, self.__context, **kw))
             except Exception, v:
-                __traceback_supplement__ = (
-                    ConfigurationTracebackSupplement, self.__locator, v
-                    )
-                raise
-                
+                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:
@@ -117,10 +136,10 @@
             try:
                 actions = end(subs)
             except Exception, v:
-                __traceback_supplement__ = (
-                    ConfigurationTracebackSupplement, self.__locator, v
-                    )
-                raise
+                if self.__testing:
+                    raise
+                raise ZopeXMLConfigurationError, (
+                    self.__locator, str(v)), sys.exc_info()[2] 
 
         append = self.__actions.append
 
@@ -139,9 +158,9 @@
     "Zope XML Configuration error"
 
     def __init__(self, l1, l2, des):
-        self.l1=l1
-        self.l2=l2
-        self.des=des
+        self.l1 = l1
+        self.l2 = l2
+        self.des = des
 
     def __str__(self):
         return """Conflicting configuration action:
@@ -167,8 +186,18 @@
     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):
+
+def xmlconfig(file, actions=None, context=None, directives=None,
+              testing=0):
     if context is None:
         context = name
 
@@ -180,19 +209,28 @@
     src = InputSource(getattr(file, 'name', '<string>'))
     src.setByteStream(file)
     parser = make_parser()
-    parser.setContentHandler(ConfigurationHandler(actions, context,
-                                                  directives))
+    parser.setContentHandler(
+        ConfigurationHandler(actions, context,directives,
+                             testing=testing)
+        )
     parser.setFeature(feature_namespaces, 1)
-    parser.parse(src) 
+    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, 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):
 
@@ -216,11 +254,11 @@
                             (self.include, {})}
 
         f = open(file_name)
-        self._stack=[file_name]
+        self._stack = [file_name]
         xmlconfig(f, self._actions, Context(self._stack), self._directives)
         f.close()
 
-    def include(self, _context, file, package=None):
+    def include(self, _context, file='config.zcml', package=None):
         if package is not None:
             try:
                 package = _context.resolve(package)
@@ -294,23 +332,5 @@
                 callable, args, kw = f
                 callable(*args, **kw)
             except Exception, v:
-                raise ConfigurationExecutionError(loc, v)
-                
-                
-class ConfigurationTracebackSupplement:
-    """Implementation of Zope.Exceptions.ITracebackSupplement"""
-    def __init__(self, locator, message):
-        self.message = message
-        self.line = locator.getLineNumber()
-        self.column = locator.getColumnNumber()
-        self.source_url  = locator.getSystemId()
-        
-    def getInfo(self, as_html=0):
-        
-        if not as_html:
-            return '   - Message: %s' % self.message
-        else:
-            from cgi import escape
-            return '<b>Message:</b><pre>%s</pre> in %s' % escape(self.message)
-        return None
-
+                raise ConfigurationExecutionError, (
+                    loc, v, sys.exc_info()[0]), sys.exc_info()[2]