[Zope-Checkins] CVS: Zope3/lib/python/Zope/Configuration - meta.py:1.15.4.1

R. David Murray bitz@bitdance.com
Mon, 14 Oct 2002 18:34:33 -0400


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

Modified Files:
      Tag: rdmurray-metameta-branch
	meta.py 
Log Message:
Allow for configuration of the meta-configuration directives (meta meta
configuration).

Previous to this change, if you put in a zcml file a metaconfiguration
directive for an already defined configuration directive, you would
replace the definition of the old configuration directive.  You could
even do this with the metaconfiguration directives themselves,
although this wouldn't be too useful (as soon as you replaced one of them,
you'd lose the definintions of 'subdirective', and so would be unable
to declare any subdirective defintions).

Note that unlike configuration directives, metaconfiguration "overrides"
take place as soon as encountered, regardless of include file
structure.

This override capability was probably not designed into the system.
Yet it is potentially useful:  suppose you are using someone else's
package, but you'd like to augment the behavior of the package's
configuration directives when you use them in your own code, especially
if your code is an *extension* of the existing package.

However, in this scenario you almost always want to augment rather
than completely replace the base directive.  We can allow this by
having register and registersub return an existing subdirectory
registry if there is one.  Thus you can define a new handler for a
directive, which would presumably subclass or call the original
handler, without having to redeclare all the subdirectives.

This also allows us to redefine the metaconfiguration directives
themselves, since now if we declare a new handler for say
directive, the old subdirective is still active and we can
go on to declare a new subdirective handler too, if needed.

So, rather than wreak extensive changes on meta.py to implement
augmented metaconfiguration directive behavior, I've modified
meta.py only slightly to facilitate subclassing and extending the
handlers for the metaconfiguration directives.

One thing I did make a major change to, though, was the setup
of the bootstrap directives.  This is now a recursive and
cross-linked data structure, as you'll read in the comments.
I'm fairly comfortable with the recursiveness, but less
comfortable with the cross-link.  Removing the crosslink
would only entail a little extra verbiage in the metameta
files, so I'm certainly will to do that if others object
to the crosslink.


=== Zope3/lib/python/Zope/Configuration/meta.py 1.15 => 1.15.4.1 ===
--- Zope3/lib/python/Zope/Configuration/meta.py:1.15	Thu Oct  3 15:44:26 2002
+++ Zope3/lib/python/Zope/Configuration/meta.py	Mon Oct 14 18:34:32 2002
@@ -85,9 +85,12 @@
     the directive. The sub-directive registry is returned so that
     it can be used for subsequent sub-directive registration.
 
+    If the same name is registered a second time, the existing
+    subdirective registry will be returned.
+
     """
     
-    subdirs = {}
+    subdirs = _directives.get(name,(None,{}))[1]
     _directives[name] = callable, subdirs
     return subdirs
 
@@ -115,10 +118,13 @@
     subdirective. The sub-directive registry is returned so that it
     can be used for subsequent sub-directive registration.
 
+    If the same name is registered a second time, the existing
+    subdirective registry will be returned.
+
     """
     if not handler_method:
         handler_method = name[1]
-    subdirs = {}
+    subdirs = directives.get(name,({},))[0]
     directives[name] = subdirs, handler_method
     return subdirs
 
@@ -155,7 +161,15 @@
 
     r = callable(context, **kw)
 
-    if subs or INonEmptyDirective.isImplementedBy(callable):
+    #XXX This _metadataKey check is a temporary hack.  I really want this
+    # line to just be "if INonEmptyDirective.isImplementedBy(callable):",
+    # but to achieve that I have to go through and put the correct
+    # declarations on all the current directive classes and functions,
+    # and I don't want to do that until I know this branch is going to
+    # be accepted.
+    _metadataKey = "__Zope.Configuration.metadataKey__"
+    if (len(subs)>1 or (len(subs)==1 and not subs.has_key(_metadataKey))
+            or INonEmptyDirective.isImplementedBy(callable)):
         return r, subs
     else:
         return (
@@ -276,22 +290,32 @@
     def __init__(self, _context, namespace):
         self._namespace = namespace
 
-    def directive(self, _context, name, handler, attributes='',
-                  namespace=None):
+    def _register(self, _context, name, handler, namespace=None,
+                  attributes=''):
         namespace = namespace or self._namespace
         subs = register((namespace, name), _context.resolve(handler))
+        return subs, namespace
+        
+    def directive(self, *args, **kw):
+        subs, namespace = self._register(*args, **kw)
         return Subdirective(subs, namespace=namespace)
     directive.__implements__ = INonEmptyDirective
 
     def __call__(self):
         return ()
 
-def Directive(_context, namespace, name, handler, attributes=''):
+
+def _registerDirective(_context, namespace, name, handler, attributes=''):
     subs = register((namespace, name), _context.resolve(handler))
+    return subs, namespace
+
+def Directive(*args, **kw):
+    subs, namespace = _registerDirective(*args, **kw)
     return Subdirective(subs, namespace=namespace)
 
 Directive.__implements__ = INonEmptyDirective
 
+
 class Subdirective:
     """This is the meta-meta-directive"""
     # 
@@ -307,13 +331,17 @@
         self._subs = subs
         self._namespace = namespace
 
-    def subdirective(self, _context, name, attributes='',
-                     namespace=None, handler_method=None):
+    def _register(self, _context, name, namespace=None, handler_method=None,
+                  attributes=''):
         namespace = namespace or self._namespace
         if not namespace:
             raise InvalidDirectiveDefinition(name)
         #If handler_method is None, registersub will use name.
         subs = registersub(self._subs, (namespace, name), handler_method)
+        return subs, namespace
+
+    def subdirective(self, *args, **kw):
+        subs, namespace = self._register(*args, **kw)
         return Subdirective(subs,namespace=namespace)
     subdirective.__implements__ = INonEmptyDirective
 
@@ -323,24 +351,30 @@
 def _clear():
     """Initialize _directives data structure with bootstrap directives."""
 
-    #We initialize _directives with handlers for three (sub)directives:
-    #directives, directive, and subdirective.  Given these three
-    #(whose implementation is contained in this module) we can use
-    #zcml to define any other directives needed for a given system.
+    # We initialize _directives with handlers for three (sub)directives:
+    # directives, directive, and subdirective.  Given these three
+    # (whose implementation is contained in this module) we can use
+    # zcml to define any other directives needed for a given system.
+    #
+    # The data structure created here is both recursive and incestuous.
+    # The recursive part allows for an unlimited number of levels
+    # of subdirective definition nesting.  The incestuous part is
+    # perhaps less legitimate, but seems logical since I can't think of
+    # a reason you would want the subdirective subdirective of the
+    # directive directive to do anything different from the subdirective
+    # subdirective of the directive subdirective of the directives
+    # directive.
     #
-    #This initialziation is done in a function to facilitate support
-    #the unittest CleanUp class.
+    # This initialziation is done in a function to facilitate support
+    # the unittest CleanUp class.
 
+    _directives.clear()
     zopens = 'http://namespaces.zope.org/zope'
     subdirkey = (zopens, 'subdirective')
-    subdir1 = {subdirkey: ({}, 'subdirective')}
-    subdir2 = {subdirkey: (subdir1, 'subdirective')}
-    subdir3 = {subdirkey: (subdir2, 'subdirective')}
-    _directives.clear()
-    _directives[(zopens, 'directive')] = (Directive, subdir3)
-    #This is incecstuous, but since there's no way to modify a
-    #directive or subdirective other than replacement, it should be OK.
-    directive = {(zopens, 'directive'): (subdir3, 'directive')}
+    subs = {}
+    subs[subdirkey] = (subs, 'subdirective')
+    _directives[(zopens, 'directive')] = (Directive, subs)
+    directive = {(zopens, 'directive'): (subs, 'directive')}
     _directives[(zopens, 'directives')] = (DirectiveNamespace, directive)
 
 _clear()