[Zope3-checkins] SVN: Zope3/trunk/ zcml:condition lives again, albeit in a less powerful form.

Marius Gedminas marius at pov.lt
Sun Feb 20 12:00:37 EST 2005


Log message for revision 29227:
  zcml:condition lives again, albeit in a less powerful form.
  
  

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/src/zope/configuration/config.py
  U   Zope3/trunk/src/zope/configuration/interfaces.py
  A   Zope3/trunk/src/zope/configuration/tests/conditions.zcml
  A   Zope3/trunk/src/zope/configuration/tests/test_conditions.py
  U   Zope3/trunk/src/zope/configuration/xmlconfig.py

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2005-02-20 15:36:05 UTC (rev 29226)
+++ Zope3/trunk/doc/CHANGES.txt	2005-02-20 17:00:37 UTC (rev 29227)
@@ -10,6 +10,19 @@
 
     New features
 
+      - You can declare "features" provided by your package with the new
+        <meta:provides feature="name" /> ZCML directive.  The presence of these
+        features can be tested with the new zcml:condition attribute.
+
+      - ZCML supports conditional directives using the zcml:condition
+        attribute.  The value of the attribute is an expression of the
+        form "verb arguments".  Currently the only recognized verb is "have",
+        and it takes a single name of a feature as an argument.
+
+        If the expression is true, the element the attribute is attached to
+        will be used, otherwise the element and its descendents will be
+        ignored.
+
       - API doctool has received some upgrades:
 
         * A new `bookmodule` compiles all our README.txt and other text

Modified: Zope3/trunk/src/zope/configuration/config.py
===================================================================
--- Zope3/trunk/src/zope/configuration/config.py	2005-02-20 15:36:05 UTC (rev 29226)
+++ Zope3/trunk/src/zope/configuration/config.py	2005-02-20 17:00:37 UTC (rev 29227)
@@ -93,6 +93,7 @@
     def __init__(self):
         super(ConfigurationContext, self).__init__()
         self._seen_files = sets.Set()
+        self._features = sets.Set()
 
     def resolve(self, dottedname):
         """Resolve a dotted name to an object
@@ -328,8 +329,6 @@
         self._seen_files.add(path)
         return True
 
-
-
     def action(self, discriminator, callable=None, args=(), kw={}, order=0):
         """Add an action with the given discriminator, callable and arguments
 
@@ -388,6 +387,35 @@
 
         self.actions.append(action)
 
+    def hasFeature(self, feature):
+        """Check whether a named feature has been provided.
+
+        Initially no features are provided
+
+        >>> c = ConfigurationContext()
+        >>> c.hasFeature('onlinehelp')
+        False
+
+        You can declare that a feature is provided
+
+        >>> c.provideFeature('onlinehelp')
+
+        and it becomes available
+
+        >>> c.hasFeature('onlinehelp')
+        True
+
+        """
+        return feature in self._features
+
+    def provideFeature(self, feature):
+        """Declare thata named feature has been provided.
+
+        See `hasFeature` for examples.
+        """
+        self._features.add(feature)
+
+
 class ConfigurationAdapterRegistry(object):
     """Simple adapter registry that manages directives as adapters
 
@@ -1195,7 +1223,45 @@
                      context.info, context.context)
     context.context[name] = schema, context.info
 
+##############################################################################
+# Features
 
+class IProvidesDirectiveInfo(Interface):
+    """Information for a <meta:provides> directive"""
+
+    feature = zope.schema.TextLine(
+        title = u"Feature name",
+        description = u"""The name of the feature being provided
+
+        You can test available features with zcml:condition="have featurename".
+        """,
+        )
+
+def provides(context, feature):
+    """Declare that a feature is provided in context.
+
+    >>> c = ConfigurationContext()
+    >>> provides(c, 'apidoc')
+    >>> c.hasFeature('apidoc')
+    True
+
+    Spaces are not allowed in feature names (this is reserved for providing
+    many features with a single directive in the futute).
+
+    >>> provides(c, 'apidoc onlinehelp')
+    Traceback (most recent call last):
+      ...
+    ValueError: Only one feature name allowed
+
+    >>> c.hasFeature('apidoc onlinehelp')
+    False
+
+    """
+    if len(feature.split()) > 1:
+        raise ValueError("Only one feature name allowed")
+    context.provideFeature(feature)
+
+
 ##############################################################################
 # Argument conversion
 
@@ -1566,3 +1632,13 @@
             handler="zope.configuration.config.subdirective",
             schema="zope.configuration.config.IDirectiveInfo"
             )
+
+    # meta:provides
+    context((metans, 'directive'),
+            info,
+            name='provides',
+            namespace=metans,
+            handler="zope.configuration.config.provides",
+            schema="zope.configuration.config.IProvidesDirectiveInfo"
+            )
+

Modified: Zope3/trunk/src/zope/configuration/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/configuration/interfaces.py	2005-02-20 15:36:05 UTC (rev 29226)
+++ Zope3/trunk/src/zope/configuration/interfaces.py	2005-02-20 17:00:37 UTC (rev 29227)
@@ -86,6 +86,13 @@
         other actions.
         """
 
+    def provideFeature(name):
+        """Record that a named feature is available in this context."""
+
+    def hasFeature(name):
+        """Check whether a named feature is available in this context."""
+
+
 class IGroupingContext(Interface):
 
     def before():

Copied: Zope3/trunk/src/zope/configuration/tests/conditions.zcml (from rev 28766, Zope3/trunk/src/zope/configuration/tests/conditions.zcml)
===================================================================
--- Zope3/trunk/src/zope/configuration/tests/conditions.zcml	2005-01-07 16:43:09 UTC (rev 28766)
+++ Zope3/trunk/src/zope/configuration/tests/conditions.zcml	2005-02-20 17:00:37 UTC (rev 29227)
@@ -0,0 +1,78 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:meta="http://namespaces.zope.org/meta"
+    xmlns:test="http://sample.namespaces.zope.org/test"
+    xmlns:zcml="http://namespaces.zope.org/zcml"
+    >
+
+  <meta:directive
+      name="register"
+      namespace="http://sample.namespaces.zope.org/test"
+      schema=".test_conditions.IRegister"
+      handler=".test_conditions.register"
+      >
+    This registers a directive which creates registrations we can test.
+  </meta:directive>
+
+  <test:register id="unqualified.registration" />
+
+  <meta:provides feature="testfeature" />
+  <meta:provides feature="anothertestfeature" />
+
+  <configure zcml:condition="have testfeature">
+    ZCML directives inside here should be included.
+
+    <configure>
+      <test:register id="nested.true.condition" />
+    </configure>
+
+    <!-- These registrations stand on the basis of their own
+         conditions: -->
+    <test:register
+        zcml:condition="have anothertestfeature"
+        id="true.condition.nested.in.true"
+        />
+
+    <test:register
+        zcml:condition="have undefinedfeature"
+        id="false.condition.nested.in.true"
+        />
+
+  </configure>
+
+  <test:register
+      zcml:condition="have testfeature"
+      id="direct.true.condition"
+      >
+    This registration should be included.
+  </test:register>
+
+  <configure zcml:condition="have undefinedfeature">
+    ZCML directives inside here should be ignored.
+
+    <configure>
+      <test:register id="nested.false.condition" />
+    </configure>
+
+    <!-- These registrations are ignored, since the container is
+         ignored: -->
+    <test:register
+        zcml:condition="have testfeature"
+        id="true.condition.nested.in.false"
+        />
+
+    <test:register
+        zcml:condition="have undefinedfeature"
+        id="false.condition.nested.in.false"
+        />
+
+  </configure>
+
+  <test:register
+      zcml:condition="have undefinedfeature"
+      id="direct.false.condition"
+      >
+    This registration should be ignored.
+  </test:register>
+
+</configure>


Property changes on: Zope3/trunk/src/zope/configuration/tests/conditions.zcml
___________________________________________________________________
Name: svn:mime-type
   + text/xml
Name: svn:eol-style
   + native

Copied: Zope3/trunk/src/zope/configuration/tests/test_conditions.py (from rev 28766, Zope3/trunk/src/zope/configuration/tests/test_conditions.py)
===================================================================
--- Zope3/trunk/src/zope/configuration/tests/test_conditions.py	2005-01-07 16:43:09 UTC (rev 28766)
+++ Zope3/trunk/src/zope/configuration/tests/test_conditions.py	2005-02-20 17:00:37 UTC (rev 29227)
@@ -0,0 +1,109 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+r'''How to conditionalize specific directives
+
+There is a "condition" attribute in the
+"http://namespaces.zope.org/zcml" namespace which is honored on all
+elements in ZCML.  The value of the attribute is an expression
+which is used to determine if that element and its descendents are
+used.  If the condition is true, processing continues normally,
+otherwise that element and its descendents are ignored.
+
+Currently the expression is always of the form "have featurename", and it
+checks for the presence of a <meta:provides feature="featurename" />.
+
+Our demonstration uses a trivial registry; each registration consists
+of a simple id inserted in the global `registry` in this module.  We
+can checked that a registration was made by checking whether the id is
+present in `registry`.
+
+We start by loading the example ZCML file, *conditions.zcml*::
+
+  >>> import zope.configuration.tests
+  >>> import zope.configuration.xmlconfig
+
+  >>> context = zope.configuration.xmlconfig.file("conditions.zcml",
+  ...                                             zope.configuration.tests)
+
+To show that our sample directive works, we see that the unqualified
+registration was successful::
+
+  >>> "unqualified.registration" in registry
+  True
+
+When the expression specified with ``zcml:condition`` evaluates to
+true, the element it is attached to and all contained elements (not
+otherwise conditioned) should be processed normally::
+
+  >>> "direct.true.condition" in registry
+  True
+  >>> "nested.true.condition" in registry
+  True
+
+However, when the expression evaluates to false, the conditioned
+element and all contained elements should be ignored::
+
+  >>> "direct.false.condition" in registry
+  False
+  >>> "nested.false.condition" in registry
+  False
+
+Conditions on container elements affect the conditions in nested
+elements in a reasonable way.  If an "outer" condition is true, nested
+conditions are processed normally::
+
+  >>> "true.condition.nested.in.true" in registry
+  True
+  >>> "false.condition.nested.in.true" in registry
+  False
+
+If the outer condition is false, inner conditions are not even
+evaluated, and the nested elements are ignored::
+
+  >>> "true.condition.nested.in.false" in registry
+  False
+  >>> "false.condition.nested.in.false" in registry
+  False
+
+Now we need to clean up after ourselves::
+
+  >>> del registry[:]
+
+'''
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+import zope.testing.doctest
+
+
+class IRegister(zope.interface.Interface):
+    """Trivial sample registry."""
+
+    id = zope.schema.Id(
+        title=u"Identifier",
+        description=u"Some identifier that can be checked.",
+        required=True,
+        )
+
+registry = []
+
+def register(context, id):
+    context.action(discriminator=('Register', id),
+                   callable=registry.append,
+                   args=(id,)
+                   )
+
+def test_suite():
+    return zope.testing.doctest.DocTestSuite()


Property changes on: Zope3/trunk/src/zope/configuration/tests/test_conditions.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/configuration/xmlconfig.py
===================================================================
--- Zope3/trunk/src/zope/configuration/xmlconfig.py	2005-02-20 15:36:05 UTC (rev 29226)
+++ Zope3/trunk/src/zope/configuration/xmlconfig.py	2005-02-20 17:00:37 UTC (rev 29227)
@@ -38,6 +38,10 @@
 
 logger = logging.getLogger("config")
 
+ZCML_NAMESPACE = "http://namespaces.zope.org/zcml"
+ZCML_CONDITION = (ZCML_NAMESPACE, u"condition")
+
+
 class ZopeXMLConfigurationError(ConfigurationError):
     """Zope XML Configuration error
 
@@ -181,6 +185,7 @@
     def __init__(self, context, testing=0):
         self.context = context
         self.testing = testing
+        self.ignore_depth = 0
 
     def setDocumentLocator(self, locator):
         self.locator = locator
@@ -189,12 +194,22 @@
         self.context.getInfo().characters(text)
 
     def startElementNS(self, name, qname, attrs):
+        if self.ignore_depth:
+            self.ignore_depth += 1
+            return
 
         data = {}
         for (ns, aname), value in attrs.items():
             if ns is None:
                 aname = str(aname)
                 data[aname] = value
+            if (ns, aname) == ZCML_CONDITION:
+                # need to process the expression to determine if we
+                # use this element and it's descendents
+                use = self.evaluateCondition(value)
+                if not use:
+                    self.ignore_depth = 1
+                    return
 
         info = ParserInfo(
             self.locator.getSystemId(),
@@ -213,8 +228,58 @@
 
         self.context.setInfo(info)
 
+    def evaluateCondition(self, expression):
+        """Evaluate a ZCML condition.
 
+        `expression` is a string of the form "verb arguments".
+
+        Currently the only supported verb is 'have'.  It takes one argument:
+        the name of a feature.
+
+        >>> from zope.configuration.config import ConfigurationContext
+        >>> context = ConfigurationContext()
+        >>> context.provideFeature('apidoc')
+        >>> c = ConfigurationHandler(context, testing=True)
+        >>> c.evaluateCondition("have apidoc")
+        True
+        >>> c.evaluateCondition("have onlinehelp")
+        False
+
+        Ill-formed expressions raise an error
+
+        >>> c.evaluateCondition("want apidoc")
+        Traceback (most recent call last):
+          ...
+        ValueError: Invalid ZCML condition: 'want apidoc'
+
+        >>> c.evaluateCondition("have x y")
+        Traceback (most recent call last):
+          ...
+        ValueError: Only one feature allowed: 'have x y'
+
+        >>> c.evaluateCondition("have")
+        Traceback (most recent call last):
+          ...
+        ValueError: Feature name missing: 'have'
+        """
+        arguments = expression.split(None)
+        verb = arguments.pop(0)
+        if verb == 'have':
+            if not arguments:
+                raise ValueError("Feature name missing: %r" % expression)
+            if len(arguments) > 1:
+                raise ValueError("Only one feature allowed: %r" % expression)
+            return self.context.hasFeature(arguments[0])
+        else:
+            raise ValueError("Invalid ZCML condition: %r" % expression)
+
     def endElementNS(self, name, qname):
+        # If ignore_depth is set, this element will be ignored, even
+        # if this this decrements ignore_depth to 0.
+        if self.ignore_depth:
+            self.ignore_depth -= 1
+            return
+
         info = self.context.getInfo()
         info.end(
             self.locator.getLineNumber(),



More information about the Zope3-Checkins mailing list