[Zope-Checkins] CVS: Zope3/lib/python/Zope/Configuration - HookRegistry.py:1.1.2.1 configuration-meta.zcml:1.1.2.1 metaConfigure.py:1.1.2.1 name.py:1.1.2.12 xmlconfig.py:1.1.2.14

Gary Poster garyposter@earthlink.net
Sun, 14 Apr 2002 17:21:03 -0400


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

Modified Files:
      Tag: Zope-3x-branch
	name.py xmlconfig.py 
Added Files:
      Tag: Zope-3x-branch
	HookRegistry.py configuration-meta.zcml metaConfigure.py 
Log Message:
added support for <hook /> and <hookable /> tags; won't be actually usable until the next commit, in which I point zope.zcml at configuration-meta.zcml.  Also added a name normalization routine in names.py.

hookable tag sets up a possible object that can be overwritten:
<hookable module="Zope.ComponentArchitecture" name="getServiceManager" />

module is optional so you can also write
<hookable name=".getServiceManager" />

These are assuming you are setting up a hookable that you loaded in the package's __init__.py.  Here's one more example, this time completely hypothetical (the other two will not be hypothetical after the next commit).
<hookable module="Zope.Event.hooks" name="publishEvent" />

You use a hook with the "hook" tag:

<hook module="Zope.ComponentArchitecture" name="getServiceManager" 
implementation="Zope.App.OFS.ServiceManager.hooks._getServiceManager" />

or if you are in the right place in the file system, shorten the implementation value to ".hooks._getServiceManager".

Again, it won't be in use until later this evening, after I get back from doing other things.  But you can glance at it now, if so desired.




=== Added File Zope3/lib/python/Zope/Configuration/HookRegistry.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.
# 
##############################################################################
"""
$Id: HookRegistry.py,v 1.1.2.1 2002/04/14 21:21:02 poster Exp $
"""

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 self._reg.has_key(hname):
            raise DuplicationError(hname)
        try:
            name.resolve(hname)
        except ImportError:
            raise BadHookableError(
                "default hookable implementation cannot be found", hname)
        
        parent, last=self._getParentAndLast(hname)
        
        self._reg[hname]=0
    
    def addHook(self, hookablename, hookname):
        
        if not self._reg.has_key(hookablename):
            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, 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]

=== Added File Zope3/lib/python/Zope/Configuration/configuration-meta.zcml ===
<zopeConfigure xmlns='http://namespaces.zope.org/zope'>

  <!-- 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>


=== Added File Zope3/lib/python/Zope/Configuration/metaConfigure.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.
# 
##############################################################################
"""
$Id: metaConfigure.py,v 1.1.2.1 2002/04/14 21:21:02 poster Exp $
"""

from Action import Action
from HookRegistry import HookRegistry

hookRegistry=HookRegistry() # one could make this a service and
# theoretically use it TTW, but that doesn't immediately seem like a
# great idea

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.2.11 => 1.1.2.12 ===
 """Provide configuration object name resolution
 
+$Id$
 """
 
 import sys
@@ -47,3 +48,23 @@
             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
\ No newline at end of file


=== Zope3/lib/python/Zope/Configuration/xmlconfig.py 1.1.2.13 => 1.1.2.14 ===
     def resolve(self, dottedname):
         return name.resolve(dottedname, self.__package)
+    
+    def getNormalizedName(self, dottedname):
+        return name.getNormalizedName(dottedname, self.__package)
 
 
 def xmlconfig(file, actions=None, context=None, directives=None):