[Zope-Perl] Perl External Methods

Gisle Aas gisle@ActiveState.com
13 Jun 2000 12:13:28 +0200


The Perl External Method implementation now looks like this:

# Copyright 2000 Digital Creations
# Copyright 2000 ActiveState Tool Corp.
#
# This module can be redistributed and/or modified
# under the terms of the Zope Public Licence v1.0.
#
# This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment (http://www.zope.org/).

from Globals import Persistent, HTMLFile, MessageDialog
import OFS.SimpleItem
import AccessControl.Role
import Acquisition

from string import split, strip, join, find
import re

ZopeExt_name_re = re.compile(r"^ZopeExt(::[a-zA-Z]\w*)+$")

def get_perl_func(name, module):
    if not ZopeExt_name_re.match(name):
        raise ValueError, "ZopeExt:: prefix required for perl function name"
    if not ZopeExt_name_re.match(module):
        raise ValueError, "ZopeExt:: prefix required for perl module name"

    # By importing here we only load perl on demand, i.e. when the first perl
    # function is called.  Perl easily adds a few megabytes to the Zope image.
    import perl

    # unshift(@INC, "./Extensions");
    inc = perl.get_ref("@INC")
    inc_copy = inc[:]
    inc.insert(0, INSTANCE_HOME + "/Extensions")
    #inc[1:] = perl.get_ref("@")  # only leave "$INSTANCE_HOME/Extensions"
    try:
        perl.require(module)  # XXX do we need some kind of untaint here??
    finally:
        inc[:] = inc_copy   # restore @INC

    f = perl.get_ref(name)
    return f


# A class emulating internal func-code objects enough to fool
# ZPublisher's mapply.  Need to emulate co_varnames and co_argcount.
# For Python External methods you find a similar class in
# App.Extentions.

class FuncCode:
    def __init__(self, args = ""):
        self.co_varnames = filter(None, map(strip, split(args, ",")))
        self.co_argcount = len(self.co_varnames)

    def args(self):
        return join(self.co_varnames, ", ")


# This is the actual class that we will store instances of, in the
# ZODB as this product is instantiated through manage_addPerlExtMethod
# below.  It stores enough information to obtain a reference to a single
# perl function loaded from the file system on demand and the information
# needed to fool mapply into thinking that this object is a function
# by itself.

class PerlExtMethod(OFS.SimpleItem.Item, Persistent, Acquisition.Explicit,
                    AccessControl.Role.RoleManager):
    """Web-callable functions that encapsulate external perl functions."""

    # Emulated attributes of functions.  The func_code class attribute
    # is not really used as instances override it with their own version.
    func_defaults=()
    func_code=FuncCode()

    # Set up various stuff that the Zope frameworks wants
    meta_type='Perl External Method'
    manage_options=(
        (
        {'label':'Properties', 'action':'manage_main',},
        {'label':'Try It', 'action':'manage_try'},
        )
        +OFS.SimpleItem.Item.manage_options
        +AccessControl.Role.RoleManager.manage_options
        )

    __ac_permissions__=(
        ('View management screens', ('manage_main',)),
        ('Change External Methods', ('manage_edit',)),
        ('View', ('__call__','')),
        )

    # I am not really sure why :-(
    ZopeTime=Acquisition.Acquired
    HelpSys=Acquisition.Acquired

    def __init__(self, id, title, module, function, args):
        self.id=id
        self.manage_edit(title, module, function, args)

    manage_main = HTMLFile('methodEdit', globals())
    
    def manage_edit(self, title, module, function, args, REQUEST=None):
        """Change the perl external method"""

        self.title = str(title)
        
        module = strip(str(module))
        if module[-3:] == '.pm': module=module[:-3]
        if module[0:9] != 'ZopeExt::': module = "ZopeExt::" + module
        self.module = module

        function = strip(str(function))
        if find(function, "::") < 0:
            function = module + "::" + function
        self.function = function
        self.func_code = FuncCode(str(args))

        # try to load the function to get early warnings in case
        # there are problems
        get_perl_func(self.function, self.module)
        
        if REQUEST: return MessageDialog(
            title  ='Changed %s' % self.id,
            message='%s has been updated' % self.id,
            action =REQUEST['URL1']+'/manage_main',
            target ='manage_main')

    manage_try = HTMLFile('methodTry', globals())

    def __call__(self, *args, **kw):
        """Invoke a PerlExternalMethod"""

        # The "_v_" prefix tells ZODB not try to store this thing
        if not hasattr(self, "_v_f"):
            self._v_f = get_perl_func(self.function, self.module)
        return apply(self._v_f, args, kw)

    def args(self):
        return self.func_code.args()

    # another interface to calling that dtml-methods are able to invoke
    call = __call__



manage_addPerlExtMethodForm=HTMLFile('methodAdd', globals())

def manage_addPerlExtMethod(self, id, title, module, function, args,
                            REQUEST=None):
    """Add a perl external method to a folder"""
    self._setObject(id, PerlExtMethod(id, title, module, function, args))
    if REQUEST:
        return self.manage_main(self, REQUEST)


def initialize(context):
    context.registerClass(
        PerlExtMethod,
        constructors=(manage_addPerlExtMethodForm,
                      manage_addPerlExtMethod),
        icon='extmethod.gif',
    )