[Zope3-checkins] CVS: Zope3/src/zope/app/component - directiveswithperms.py:1.1.2.1 decoratorservice.py:1.1.2.3 hooks.py:1.5.8.7 meta.zcml:1.3.2.1 metaconfigure.py:1.10.8.1 contentdirective.py:NONE

Steve Alexander steve@cat-box.net
Wed, 14 May 2003 13:44:40 -0400


Update of /cvs-repository/Zope3/src/zope/app/component
In directory cvs.zope.org:/tmp/cvs-serv32636/src/zope/app/component

Modified Files:
      Tag: stevea-decorators-branch
	decoratorservice.py hooks.py meta.zcml metaconfigure.py 
Added Files:
      Tag: stevea-decorators-branch
	directiveswithperms.py 
Removed Files:
      Tag: stevea-decorators-branch
	contentdirective.py 
Log Message:
Decorators are now really roughly integrated into zope on this branch.
Still to do:

  * The decorator registry needs an interface and some tests.

  * The decorator registry should be using a type registry rather than
    a simple dict, so that you don't need to keep declaring decorators
    for all the different subtypes of a container that doesn't manage
    its own context.

  * Fix various tests that explicitly use ZopeContainerAdapter.

  * Document use of decorators in Zope 3.

  * Get this work reviewed and discussed.

The first three points should be complete by the end of tomorrow.



=== Added File Zope3/src/zope/app/component/directiveswithperms.py ===
##############################################################################
#
# 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.
#
##############################################################################
""" Register class directive.

$Id: directiveswithperms.py,v 1.1.2.1 2003/05/14 17:44:09 stevea Exp $
"""
from zope.interface import classProvides, implements, classImplements
from zope.interface import implementedBy
from types import ModuleType
from zope.configuration.interfaces import INonEmptyDirective
from zope.configuration.interfaces import ISubdirectiveHandler
from zope.component import getService
from zope.app.services.servicenames import Interfaces, Factories
from zope.configuration.exceptions import ConfigurationError
from zope.configuration.action import Action
from zope.app.component.classfactory import ClassFactory
from zope.app.component.metaconfigure import resolveInterface
from zope.app.security.protectclass \
    import protectLikeUnto, protectName, protectSetAttribute
from zope.app.security.registries.permissionregistry import permissionRegistry
from zope.security.proxy import ProxyFactory
from zope.security.checker import NamesChecker, CheckerPublic
from zope.schema.interfaces import IField
from zope.app.component.decoratorservice import registerDecorator
from zope.app.component.decoratorservice import registerDecoratorForClass
from zope.app.interfaces.component import IDecoratorSpec

__metaclass__ = type

PublicPermission = 'zope.Public'

class ProtectionDeclarationException(Exception):
    """Security-protection-specific exceptions."""
    pass

def handler(serviceName, methodName, *args, **kwargs):
    method=getattr(getService(None, serviceName), methodName)
    method(*args, **kwargs)

def assertPermission(permission=None, *args, **kw):
    """Check if permission is defined"""
    if permission is not None:
        permissionRegistry.ensurePermissionDefined(permission)

class DirectiveWithPermissions:
    """Base class for a directive with permissions"""

    def require(self, _context,
                permission=None, attributes=None, interface=None,
                like_class=None, set_attributes=None, set_schema=None):
        """Require a the permission to access a specific aspect"""

        if like_class:
            r = self._mimic(_context, like_class)
        else:
            r = []

        if not (interface or attributes or set_attributes or set_schema):
            if r:
                return r
            raise ConfigurationError("Nothing required")

        if not permission:
            raise ConfigurationError("No permission specified")

        if interface:
            for i in interface.strip().split():
                self._protectByInterface(i, permission, r)
        if attributes:
            self._protectNames(attributes, permission, r)
        if set_attributes:
            self._protectSetAttributes(set_attributes, permission, r)
        if set_schema:
            for s in set_schema.strip().split():
                self._protectSetSchema(s, permission, r)

        return r

    def _mimic(self, _context, class_):
        """Base security requirements on those of the given class"""
        class_to_mimic = _context.resolve(class_)
        return [
            Action(discriminator=('mimic', self._class, object()),
                   callable=protectLikeUnto,
                   args=(self._class, class_to_mimic),
                   )
            ]

    def allow(self, _context, attributes=None, interface=None):
        """Like require, but with permission_id zope.Public"""
        return self.require(_context, PublicPermission, attributes, interface)

    def _protectByInterface(self, interface, permission_id, r):
        "Set a permission on names in an interface."
        interface = resolveInterface(self._context, interface)
        for n, d in interface.namesAndDescriptions(1):
            self._protectName(n, permission_id, r)
        r.append(
            Action(
               discriminator = None,
               callable = handler,
               args = (Interfaces, 'provideInterface',
                       interface.__module__+ '.'+ interface.__name__,
                       interface)
               )
            )

    def _protectName(self, name, permission_id, r):
        "Set a permission on a particular name."
        r.append((
            ('protectName', self._class, name),
            protectName, (self._class, name, permission_id)))

    def _protectNames(self, names, permission_id, r):
        "Set a permission on a bunch of names."
        for name in names.split():
            self._protectName(name, permission_id, r)

    def _protectSetAttribute(self, name, permission_id, r):
        "Set a set-permission on a particular name."
        r.append((
            ('protectSetAttribute', self._class, name),
            protectSetAttribute, (self._class, name, permission_id)))

    def _protectSetAttributes(self, names, permission_id, r):
        "Set a set-permission on a bunch of names."
        for name in names.split():
            self._protectSetAttribute(name, permission_id, r)

    def _protectSetSchema(self, schema, permission_id, r):
        "Set a set-permission on a bunch of names."
        schema = resolveInterface(self._context, schema)
        for name in schema:
            field = schema[name]
            if IField.isImplementedBy(field) and not field.readonly:
                r.append((
                    ('protectSetAttribute', self._class, name),
                    protectSetAttribute, (self._class, name, permission_id)))

        r.append(
            Action(
               discriminator = None,
               callable = handler,
               args = (Interfaces, 'provideInterface',
                       schema.__module__+ '.'+ schema.__name__,
                       schema)
               )
            )


class ContentDirective(DirectiveWithPermissions):
    classProvides(INonEmptyDirective)
    implements(ISubdirectiveHandler)

    def __init__(self, _context, class_):
        self._id = class_
        self._class = _context.resolve(class_)
        if isinstance(self._class, ModuleType):
            raise ConfigurationError('Content class attribute must be a class')
        # not used yet
        #self._name = class_
        #self._normalized_name = _context.getNormalizedName(class_)
        self._context = _context

    def implements(self, _context, interface):
        r = []
        for interface in interface.strip().split():

            resolved_interface = resolveInterface(_context, interface)
            r += [
                Action(
                    discriminator = ('ContentDirective',
                                     self._class,
                                     object()),
                    callable = classImplements,
                    args = (self._class, resolved_interface),
                    ),
                Action(
                   discriminator = None,
                   callable = handler,
                   args = (Interfaces, 'provideInterface',
                           resolved_interface.__module__+
                           '.'+
                           resolved_interface.__name__,
                           resolved_interface)
                   )
                ]
        return r

    def __call__(self):
        "Handle empty/simple declaration."
        return ()

    def factory(self, _context,
                permission=None, title="", id=None, description=''):
        """Register a zmi factory for this class"""

        id = id or self._id

        # note factories are all in one pile, services and content,
        # so addable names must also act as if they were all in the
        # same namespace, despite the service/content division
        return [
            Action(
                discriminator = ('FactoryFromClass', id),
                callable = provideClass,
                args = (id, self._class,
                        permission, title, description)
                )
            ]

    def decorate(self, _context,
                 decorator, type):
        """Register a decorator for this class"""
        return [
            Action(
                discriminator = ('DecoratorForClass', self._class, type),
                callable = registerDecoratorForClass,
                args = (self._class, decorator, type)
                )
            ]

def provideClass(id, _class, permission=None,
                 title='', description=''):
    """Provide simple class setup

    - create a component

    - set component permission
    """

    assertPermission(permission)
    factory = ClassFactory(_class)

    if permission == PublicPermission:
        permission = CheckerPublic

    if permission:
        # XXX should getInterfaces be public, as below?
        factory = ProxyFactory(factory,
                               NamesChecker(('getInterfaces',),
                                            __call__=permission))

    getService(None, Factories).provideFactory(id, factory)

class MixinBase:
    def __init__(self, inner, outer):
        self.inner = inner
        self.outer = outer

class DecoratorDirective(DirectiveWithPermissions):
    classProvides(INonEmptyDirective)
    implements(ISubdirectiveHandler)

    def __init__(self, _context, id, class_, names=None, trusted='untrusted'):
        self._id = id
        self._class = _context.resolve(class_)
        if not names:
            self._names = None
        else:
            self._names = names.split()
        if isinstance(self._class, ModuleType):
            raise ConfigurationError(
                    'Decorator "class" attribute must be a class')
        self._context = _context
        self._get_permission_map = {}
        self._set_permission_map = {}
        if trusted == 'untrusted':
            self._trusted_mixin = False
        elif trusted == 'trusted':
            self._trusted_mixin = True
        else:
            raise ConfigurationError('Value of "trusted" must be "trusted" or'
                                     ' "untrusted".')

    def __call__(self):
        class_ = self._class

        bases = (class_, MixinBase)
        cname = "GeneratedDecoratorMixinClass"
        cdict = {}
        newclass = type(cname, bases, cdict)

        names = self._names
        if names is None:
            names = ([name for name in self._get_permission_map] +
                     [name for name in self._set_permission_map])

        spec = DecoratorSpec(newclass, implementedBy(class_), names,
                             self._get_permission_map,
                             self._set_permission_map,
                             self._trusted_mixin)

        return [
            Action(
              discriminator = ('decorator', self._id),
              callable = registerDecorator,
              args = (self._id, spec)
              ),
            ]

    def _protectName(self, name, permission_id, r):
        "Set a permission on a particular name."
        r.append((
            ('decorator_protectName', self._id, name),
            _decorateAddPermission,
            (self._get_permission_map, name, permission_id, self._id, 'get')))

    def _protectSetAttribute(self, name, permission_id, r):
        "Set a set-permission on a particular name."
        r.append((
            ('decorator_protectSetAttribute', self._id, name),
            _decorateAddPermission,
            (self._set_permission_map, name, permission_id, self._id, 'set')))

def _decorateAddPermission(map, name, permission_id, decorator_id, operation):
    if name in map:
        raise ConfigurationError(
            'Permission for %s (%s) defined twice for decorator %s'
            % (name, operation, decorator_id))
    if permission_id == PublicPermission:
        permission_id = CheckerPublic
    map[name] = permission_id


class DecoratorSpec:
    implements(IDecoratorSpec)

    def __init__(self, mixinFactory, mixinInterfaceSpec, names, permissionMap,
                 setPermissionMap, mixinIsTrusted):
        self.mixinFactory = mixinFactory
        self.mixinInterfaceSpec = mixinInterfaceSpec
        self.names = names
        self.permissionMap = permissionMap
        self.setPermissionMap = setPermissionMap
        self.mixinIsTrusted = mixinIsTrusted


=== Zope3/src/zope/app/component/decoratorservice.py 1.1.2.2 => 1.1.2.3 ===
--- Zope3/src/zope/app/component/decoratorservice.py:1.1.2.2	Wed May 14 06:39:04 2003
+++ Zope3/src/zope/app/component/decoratorservice.py	Wed May 14 13:44:09 2003
@@ -16,11 +16,56 @@
 $Id$
 """
 
-# class -> IDecoratorSpec
-_reg = {}
+class DecoratorRegistry:
 
-queryDecoratorSpec = _reg.get
+    def __init__(self):
+        # XXX This class needs an interface. Also, rather than using a dict,
+        #     this should be using a type registry.
+        self._id_to_spec = {}
+        self._class_to_id = {}
+        self._class_to_spec = {}
+
+    def queryContextDecoratorSpec(self, class_):
+        return self._class_to_spec.get(class_)
+
+    def registerDecoratorForClass(self, class_, decorator_id, type):
+        if type != 'context':
+            raise ValueError(
+                'Only decorators of type "context" are implemented.')
+        if class_ in self._class_to_id:
+            raise ValueError('More than one decorator registered for decorator'
+                             ' type "%s", class "%s"' % (type, class_))
+        spec = self._id_to_spec.get(decorator_id)
+        if spec is None:
+            raise ValueError('Decorator id %s not defined' % decorator_id)
+        # NB: only context decorators supported for now!
+        self._class_to_id[class_] = decorator_id
+        self._class_to_spec[class_] = spec
+
+    def queryDecoratorForClass(self, class_, type):
+        if type != 'context':
+            return None
+        return self._class_to_id.get(class_)
+
+    def registerDecorator(self, decorator_id, spec):
+        if decorator_id in self._id_to_spec:
+            raise ValueError('More than one decorator registered with id %s'
+                             % decorator_id)
+        self._id_to_spec[decorator_id] = spec
+
+    def _clear(self):
+        self._id_to_spec.clear()
+        self._class_to_id.clear()
+        self._class_to_spec.clear()
+
+decoratorRegistry = DecoratorRegistry()
+
+queryDecoratorForClass = decoratorRegistry.queryDecoratorForClass
+registerDecoratorForClass = decoratorRegistry.registerDecoratorForClass
+queryDecoratorForClass = decoratorRegistry.queryDecoratorForClass
+registerDecorator = decoratorRegistry.registerDecorator
+queryContextDecoratorSpec = decoratorRegistry.queryContextDecoratorSpec
 
 from zope.testing.cleanup import addCleanUp
-addCleanUp(_reg.clear)
+addCleanUp(decoratorRegistry._clear)
 del addCleanUp


=== Zope3/src/zope/app/component/hooks.py 1.5.8.6 => 1.5.8.7 ===
--- Zope3/src/zope/app/component/hooks.py:1.5.8.6	Wed May 14 10:19:05 2003
+++ Zope3/src/zope/app/component/hooks.py	Wed May 14 13:44:09 2003
@@ -23,10 +23,10 @@
 from zope.security.proxy import trustedRemoveSecurityProxy
 from zope.app.traversing import IContainmentRoot
 from zope.proxy.context.wrapper import Wrapper
-from zope.app.component.decoratorservice import queryDecoratorSpec
+from zope.app.component.decoratorservice import queryContextDecoratorSpec
 from zope.security.proxy import Proxy, getChecker, getObject
 from zope.proxy.context.decorator import Decorator
-from zope.security.checker import DecoratedChecker
+from zope.security.checker import DecoratedChecker, BasicTypes
 from zope.interface import providedBy, InterfaceSpecification
 
 def getServiceManager_hook(context, local=False):
@@ -68,23 +68,24 @@
 def ContextWrapper_hook(_ob, _parent, **kw):
     """
     """
+    # XXX This needs a test
     t = type(_ob)
     if t in BasicTypes:
         # Don't wrap basic objects
         return _ob
 
-    cls = getattr(ob, '__class__', None)
+    cls = getattr(_ob, '__class__', None)
     if cls is not None:
-        decorator_spec = queryDecoratorSpec(cls)
+        decorator_spec = queryContextDecoratorSpec(cls)
         if decorator_spec is not None:
             return decorate(spec, _ob, _parent, kw)
     if t is Proxy:
         # insert into proxies
         checker = getChecker(_ob)
         _ob = getObject(_ob)
-        _ob = Proxy(Wrapper(_ob, _parent, kw), checker)
+        _ob = Proxy(Wrapper(_ob, _parent, **kw), checker)
     else:
-        _ob = Wrapper(_ob, _parent, kw)
+        _ob = Wrapper(_ob, _parent, **kw)
 
     return _ob
 


=== Zope3/src/zope/app/component/meta.zcml 1.3 => 1.3.2.1 ===
--- Zope3/src/zope/app/component/meta.zcml:1.3	Mon May 12 12:32:39 2003
+++ Zope3/src/zope/app/component/meta.zcml	Wed May 14 13:44:09 2003
@@ -2,163 +2,241 @@
 
   <directives namespace="http://namespaces.zope.org/zope">
 
-    <directive name="interface" attributes="interface"
-       handler="zope.app.component.metaconfigure.interface" />
-
-    <directive name="adapter" attributes="factory provides for permission"
-       handler="zope.app.component.metaconfigure.adapter" />
-
-    <directive name="utility" attributes="component provides permission name"
-       handler="zope.app.component.metaconfigure.utility" />
-
-    <directive name="factory" attributes="component id"
-       handler="zope.app.component.metaconfigure.factory" />
-
-    <directive 
-       name="view" 
-       attributes="component type name for layer
-                   permission allowed_interface allowed_attributes"
-       handler="zope.app.component.metaconfigure.view" />
-
-    <directive name="defaultView" 
-               attributes="component type name for layer"
-       handler="zope.app.component.metaconfigure.defaultView" />
-
-    <directive 
-       name="resource" 
-       attributes="component type name layer
-                   permission allowed_interface allowed_attributes"
-       handler="zope.app.component.metaconfigure.resource" />
-
-    <directive name="skin" attributes="name type layers" 
-        handler="zope.app.component.metaconfigure.skin" />
-
-    <directive name="serviceType" attributes="id interface"
-       handler="zope.app.component.metaconfigure.serviceType" />
-
-    <directive name="service" attributes="serviceType component permission"
-       handler="zope.app.component.metaconfigure.service" />
-
-  </directives>
-
-<directives namespace="http://namespaces.zope.org/zope">
-
-  <directive
-      name="content"
-      handler="zope.app.component.contentdirective.ContentDirective"
-      description="Make a component available as a content object type"
-      >
-    <attribute
-        name="class"
-        required="yes"
-        description="resolvable name of a class" 
+    <directive
+        name="interface"
+        attributes="interface"
+        handler="zope.app.component.metaconfigure.interface"
+        />
+    <directive
+        name="adapter"
+        attributes="factory provides for permission"
+        handler="zope.app.component.metaconfigure.adapter"
+        />
+    <directive
+        name="utility"
+        attributes="component provides permission name"
+        handler="zope.app.component.metaconfigure.utility"
+        />
+    <directive
+        name="factory"
+        attributes="component id"
+        handler="zope.app.component.metaconfigure.factory"
+        />
+    <directive
+        name="view"
+        attributes="component type name for layer
+                    permission allowed_interface allowed_attributes"
+        handler="zope.app.component.metaconfigure.view"
+        />
+    <directive
+        name="defaultView" 
+        attributes="component type name for layer"
+        handler="zope.app.component.metaconfigure.defaultView"
+        />
+    <directive
+        name="resource"
+        attributes="component type name layer
+                    permission allowed_interface allowed_attributes"
+        handler="zope.app.component.metaconfigure.resource"
+        />
+    <directive
+        name="skin"
+        attributes="name type layers"
+        handler="zope.app.component.metaconfigure.skin"
+        />
+    <directive
+        name="serviceType"
+        attributes="id interface"
+        handler="zope.app.component.metaconfigure.serviceType"
+        />
+    <directive
+        name="service"
+        attributes="serviceType component permission"
+        handler="zope.app.component.metaconfigure.service"
         />
 
-    <subdirective name="implements">
-
-      <description>
-            Declare that the class given by the content
-            directive's class attribute implements a given interface
-        </description>
-
+    <directive
+        name="decorator"
+        handler="zope.app.component.directiveswithperms.DecoratorDirective"
+        description="Register a decorator">
       <attribute
-          name="interface"
+          name="id"
           required="yes"
-          description="resolvable name of an Interface" 
+          description="unique id of this decorator"
           />
-    </subdirective>
-
-    <subdirective name="require">
-      <description>
+      <attribute name="names" required="no">
+        <description>
+            Space-separated list of names to be dispatched to the decorator.
+            If 'names' is not given, it is infered from the permissions that
+            are defined.
+        </description>
+      </attribute>
+      <attribute name="trusted" required="no">
+        <description>
+            Takes either the value 'trusted' or the value 'untrusted'.
+            A trusted decorator mix-in can access its wrapped object without
+            going through a security proxy. An untrusted decorator mix-in
+            must go through the usual security proxy for that class.
+            The default is untrusted.
+        </description>
+      </attribute>
+      <subdirective name="require">
+        <description>
           Indicate that the a specified list of names or the
           names in a given Interface require a given permission for
           access.
         </description>
-
+        <attribute
+            name="permission"
+            required="yes"
+            description="a permission id"
+            />
+        <attribute
+            name="attributes"
+            description="space-separated list of attribute names"
+            />
+        <attribute
+            name="interface"
+            description="the resolvable name of an interface"
+            />
+        <attribute name="like_class">
+          <description>
+              a class on which the security requirements
+              for this class will be based 
+          </description>
+        </attribute>
+      </subdirective>
+      <subdirective name="allow">
+        <description>
+            Declare a part of the class to be publicly
+            viewable (that is, requires the zope.Public
+            permission).  Only one of the following two
+            attributes may be used.
+         </description>
+        <attribute
+            name="attributes"
+            description="space-separated list of attribute names"
+            />
+        <attribute
+            name="interface"
+            description="the resolvable name of an interface"
+            />
+      </subdirective>
+    </directive>
+
+    <directive
+        name="content"
+        handler="zope.app.component.directiveswithperms.ContentDirective"
+        description="Make a component available as a content object type">
       <attribute
-          name="permission"
+          name="class"
           required="yes"
-          description="a permission id" 
-          />
-
-      <attribute
-          name="attributes"
-          description="space-separated list of attribute names" 
+          description="resolvable name of a class" 
           />
+      <subdirective name="implements">
+        <description>
+            Declare that the class given by the content
+            directive's class attribute implements a given interface
+        </description>
 
-      <attribute
-          name="interface"
-          description="the resolvable name of an interface" 
-          />
+        <attribute
+            name="interface"
+            required="yes"
+            description="resolvable name of an Interface"
+            />
+      </subdirective>
 
-      <attribute name="like_class">
+      <subdirective name="require">
         <description>
+          Indicate that the a specified list of names or the
+          names in a given Interface require a given permission for
+          access.
+        </description>
+        <attribute
+            name="permission"
+            required="yes"
+            description="a permission id"
+            />
+        <attribute
+            name="attributes"
+            description="space-separated list of attribute names"
+            />
+        <attribute
+            name="interface"
+            description="the resolvable name of an interface"
+            />
+        <attribute name="like_class">
+          <description>
               a class on which the security requirements
               for this class will be based 
-            </description>
+          </description>
         </attribute>
-
-    </subdirective>
-
-    <subdirective name="allow">
-      <description>
+      </subdirective>
+      <subdirective name="allow">
+        <description>
             Declare a part of the class to be publicly
             viewable (that is, requires the zope.Public
             permission).  Only one of the following two
             attributes may be used.
          </description>
-
-      <attribute
-          name="attributes"
-          description="space-separated list of attribute names" 
-          />
-
-      <attribute
-          name="interface"
-          description="the resolvable name of an interface" 
-          />
-
-    </subdirective>
-
-    <subdirective name="factory">
-
-      <description>
+        <attribute
+            name="attributes"
+            description="space-separated list of attribute names"
+            />
+        <attribute
+            name="interface"
+            description="the resolvable name of an interface"
+            />
+      </subdirective>
+      <subdirective name="factory">
+        <description>
             Specify the factory used to create this
             content object
         </description>
-
-      <attribute name="permission">
-        <description>
-              permission id required to use this factory.
+        <attribute name="permission">
+          <description>
+              Permission id required to use this factory.
               Although optional, this attribute should normally
-              be specified. 
-              </description>
+              be specified 
+          </description>
         </attribute>
-
-      <attribute name="id">
-        <description>
-              the identifier for this factory in the
+        <attribute name="id">
+          <description>
+              The identifier for this factory in the
               ZMI factory identification scheme.  If not given, defaults
               to the literal string given as the content directive's
               'class' attribute.
-            </description>
+          </description>
         </attribute>
-
-      <attribute name="title">
-        <description>
-              text suitable for use in the 'add content' menu of
+        <attribute name="title">
+          <description>
+              Text suitable for use in the 'add content' menu of
               a management interface
-            </description>
+          </description>
         </attribute>
-
-      <attribute name="description">
-        <description>
+        <attribute name="description">
+          <description>
               longer narrative description of what this
               factory does
           </description>
         </attribute>
-
-    </subdirective>
+      </subdirective>
+      <subdirective name="decorate">
+        <description>
+            Specify the decorator used with this kind of content object.
+        </description>
+        <attribute name="decorator">
+          <description>
+              The id of the decorator to use.
+          </description>
+        </attribute>
+        <attribute name="type">
+          <description>
+              The type of decorator to use. The most common type of decorator
+              in the Zope 3 core is 'context'.
+          </description>
+        </attribute>
+      </subdirective>
   </directive>
 </directives>
 


=== Zope3/src/zope/app/component/metaconfigure.py 1.10 => 1.10.8.1 ===
--- Zope3/src/zope/app/component/metaconfigure.py:1.10	Thu May  1 15:35:07 2003
+++ Zope3/src/zope/app/component/metaconfigure.py	Wed May 14 13:44:09 2003
@@ -68,7 +68,7 @@
     elif not for_:
         raise ValueError(
             "A for interface must be provided. Use * for all objects.")
-        
+
     if for_:
         for_ = resolveInterface(_context, for_)
 

=== Removed File Zope3/src/zope/app/component/contentdirective.py ===