Allow external plugins to return no value
Instead of using the XML-RPC v2 extension <nil/>, which Perl's XML::RPC::Parser does not (yet) support (Joey's patch is pending), we agreed on a sentinel: {'null':''}, that is, a hash with a single key "null" pointing to the empty string. The Python proxy automatically converts None appropriately and raises an exception if a hook function should, by weird coincidence, attempt to return {'null':''}. Signed-off-by: martin f. krafft <madduck@madduck.net>master
parent
99fce0af0d
commit
e3624de63c
|
@ -68,7 +68,22 @@ sub rpc_call ($$;@) { #{{{
|
||||||
return @{$value->value};
|
return @{$value->value};
|
||||||
}
|
}
|
||||||
elsif ($value->isa('RPC::XML::struct')) {
|
elsif ($value->isa('RPC::XML::struct')) {
|
||||||
return %{$value->value};
|
my %hash=%{$value->value};
|
||||||
|
|
||||||
|
# XML-RPC v1 does not allow for
|
||||||
|
# nil/null/None/undef values to be
|
||||||
|
# transmitted, so until
|
||||||
|
# XML::RPC::Parser honours v2
|
||||||
|
# (<nil/>), external plugins send
|
||||||
|
# a hash with one key "null" pointing
|
||||||
|
# to an empty string.
|
||||||
|
if (exists $hash{null} &&
|
||||||
|
$hash{null} eq "" &&
|
||||||
|
int(keys(%hash)) == 1) {
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
return %hash;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return $value->value;
|
return $value->value;
|
||||||
|
@ -92,6 +107,14 @@ sub rpc_call ($$;@) { #{{{
|
||||||
error("XML RPC call error, unknown function: $name");
|
error("XML RPC call error, unknown function: $name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# XML-RPC v1 does not allow for nil/null/None/undef
|
||||||
|
# values to be transmitted, so until XML::RPC::Parser
|
||||||
|
# honours v2 (<nil/>), send a hash with one key "null"
|
||||||
|
# pointing to an empty string.
|
||||||
|
if (! defined $ret) {
|
||||||
|
$ret={"null" => ""};
|
||||||
|
}
|
||||||
|
|
||||||
my $string=eval { RPC::XML::response->new($ret)->as_string };
|
my $string=eval { RPC::XML::response->new($ret)->as_string };
|
||||||
if ($@ && ref $ret) {
|
if ($@ && ref $ret) {
|
||||||
# One common reason for serialisation to
|
# One common reason for serialisation to
|
||||||
|
|
|
@ -13,8 +13,6 @@ __author__ = 'martin f. krafft <madduck@madduck.net>'
|
||||||
__copyright__ = 'Copyright © ' + __author__
|
__copyright__ = 'Copyright © ' + __author__
|
||||||
__licence__ = 'GPLv2'
|
__licence__ = 'GPLv2'
|
||||||
|
|
||||||
LOOP_DELAY = 0.1
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import xmlrpclib
|
import xmlrpclib
|
||||||
|
@ -31,6 +29,9 @@ class _IkiWikiExtPluginXMLRPCDispatcher(SimpleXMLRPCDispatcher):
|
||||||
# python2.4 and before only took one argument
|
# python2.4 and before only took one argument
|
||||||
SimpleXMLRPCDispatcher.__init__(self)
|
SimpleXMLRPCDispatcher.__init__(self)
|
||||||
|
|
||||||
|
def dispatch(self, method, params):
|
||||||
|
return self._dispatch(method, params)
|
||||||
|
|
||||||
class _XMLStreamParser(object):
|
class _XMLStreamParser(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -77,8 +78,8 @@ class _XMLStreamParser(object):
|
||||||
|
|
||||||
class _IkiWikiExtPluginXMLRPCHandler(object):
|
class _IkiWikiExtPluginXMLRPCHandler(object):
|
||||||
|
|
||||||
def __init__(self, debug_fn, allow_none=False, encoding=None):
|
def __init__(self, debug_fn):
|
||||||
self._dispatcher = _IkiWikiExtPluginXMLRPCDispatcher(allow_none, encoding)
|
self._dispatcher = _IkiWikiExtPluginXMLRPCDispatcher()
|
||||||
self.register_function = self._dispatcher.register_function
|
self.register_function = self._dispatcher.register_function
|
||||||
self._debug_fn = debug_fn
|
self._debug_fn = debug_fn
|
||||||
|
|
||||||
|
@ -125,20 +126,28 @@ class _IkiWikiExtPluginXMLRPCHandler(object):
|
||||||
|
|
||||||
def handle_rpc(self, in_fd, out_fd):
|
def handle_rpc(self, in_fd, out_fd):
|
||||||
self._debug_fn('waiting for procedure calls from ikiwiki...')
|
self._debug_fn('waiting for procedure calls from ikiwiki...')
|
||||||
ret = _IkiWikiExtPluginXMLRPCHandler._read(in_fd)
|
xml = _IkiWikiExtPluginXMLRPCHandler._read(in_fd)
|
||||||
if ret is None:
|
if xml is None:
|
||||||
# ikiwiki is going down
|
# ikiwiki is going down
|
||||||
self._debug_fn('ikiwiki is going down, and so are we...')
|
self._debug_fn('ikiwiki is going down, and so are we...')
|
||||||
return
|
return
|
||||||
|
|
||||||
self._debug_fn('received procedure call from ikiwiki: [%s]' % ret)
|
self._debug_fn('received procedure call from ikiwiki: [%s]' % xml)
|
||||||
ret = self._dispatcher._marshaled_dispatch(ret)
|
params, method = xmlrpclib.loads(xml)
|
||||||
self._debug_fn('sending procedure response to ikiwiki: [%s]' % ret)
|
ret = self._dispatcher.dispatch(method, params)
|
||||||
_IkiWikiExtPluginXMLRPCHandler._write(out_fd, ret)
|
xml = xmlrpclib.dumps((ret,), methodresponse=True)
|
||||||
|
self._debug_fn('sending procedure response to ikiwiki: [%s]' % xml)
|
||||||
|
_IkiWikiExtPluginXMLRPCHandler._write(out_fd, xml)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class IkiWikiProcedureProxy(object):
|
class IkiWikiProcedureProxy(object):
|
||||||
|
|
||||||
|
# how to communicate None to ikiwiki
|
||||||
|
_IKIWIKI_NIL_SENTINEL = {'null':''}
|
||||||
|
|
||||||
|
# sleep during each iteration
|
||||||
|
_LOOP_DELAY = 0.1
|
||||||
|
|
||||||
def __init__(self, id, in_fd=sys.stdin, out_fd=sys.stdout, debug_fn=None):
|
def __init__(self, id, in_fd=sys.stdin, out_fd=sys.stdout, debug_fn=None):
|
||||||
self._id = id
|
self._id = id
|
||||||
self._in_fd = in_fd
|
self._in_fd = in_fd
|
||||||
|
@ -151,9 +160,25 @@ class IkiWikiProcedureProxy(object):
|
||||||
self._xmlrpc_handler = _IkiWikiExtPluginXMLRPCHandler(self._debug_fn)
|
self._xmlrpc_handler = _IkiWikiExtPluginXMLRPCHandler(self._debug_fn)
|
||||||
self._xmlrpc_handler.register_function(self._importme, name='import')
|
self._xmlrpc_handler.register_function(self._importme, name='import')
|
||||||
|
|
||||||
def hook(self, type, function):
|
def hook(self, type, function, name=None):
|
||||||
self._hooks.append((type, function.__name__))
|
if name is None:
|
||||||
self._xmlrpc_handler.register_function(function)
|
name = function.__name__
|
||||||
|
self._hooks.append((type, name))
|
||||||
|
|
||||||
|
def hook_proxy(*args):
|
||||||
|
# curpage = args[0]
|
||||||
|
# kwargs = dict([args[i:i+2] for i in xrange(1, len(args), 2)])
|
||||||
|
ret = function(*args)
|
||||||
|
self._debug_fn("%s hook `%s' returned: [%s]" % (type, name, ret))
|
||||||
|
if ret == IkiWikiProcedureProxy._IKIWIKI_NIL_SENTINEL:
|
||||||
|
raise IkiWikiProcedureProxy.InvalidReturnValue, \
|
||||||
|
'hook functions are not allowed to return %s' \
|
||||||
|
% IkiWikiProcedureProxy._IKIWIKI_NIL_SENTINEL
|
||||||
|
if ret is None:
|
||||||
|
ret = IkiWikiProcedureProxy._IKIWIKI_NIL_SENTINEL
|
||||||
|
return ret
|
||||||
|
|
||||||
|
self._xmlrpc_handler.register_function(hook_proxy, name=name)
|
||||||
|
|
||||||
def _importme(self):
|
def _importme(self):
|
||||||
self._debug_fn('importing...')
|
self._debug_fn('importing...')
|
||||||
|
@ -161,7 +186,7 @@ class IkiWikiProcedureProxy(object):
|
||||||
self._debug_fn('hooking %s into %s chain...' % (function, type))
|
self._debug_fn('hooking %s into %s chain...' % (function, type))
|
||||||
self._xmlrpc_handler.send_rpc('hook', self._in_fd, self._out_fd,
|
self._xmlrpc_handler.send_rpc('hook', self._in_fd, self._out_fd,
|
||||||
id=self._id, type=type, call=function)
|
id=self._id, type=type, call=function)
|
||||||
return 0
|
return IkiWikiProcedureProxy._IKIWIKI_NIL_SENTINEL
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
|
@ -169,10 +194,13 @@ class IkiWikiProcedureProxy(object):
|
||||||
ret = self._xmlrpc_handler.handle_rpc(self._in_fd, self._out_fd)
|
ret = self._xmlrpc_handler.handle_rpc(self._in_fd, self._out_fd)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return
|
return
|
||||||
time.sleep(LOOP_DELAY)
|
time.sleep(IkiWikiProcedureProxy._LOOP_DELAY)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print >>sys.stderr, 'uncaught exception: %s' % e
|
print >>sys.stderr, 'uncaught exception: %s' % e
|
||||||
import traceback
|
import traceback
|
||||||
print >>sys.stderr, traceback.format_exc(sys.exc_info()[2])
|
print >>sys.stderr, traceback.format_exc(sys.exc_info()[2])
|
||||||
import posix
|
import posix
|
||||||
sys.exit(posix.EX_SOFTWARE)
|
sys.exit(posix.EX_SOFTWARE)
|
||||||
|
|
||||||
|
class InvalidReturnValue(Exception):
|
||||||
|
pass
|
||||||
|
|
|
@ -151,7 +151,7 @@ def htmlize_demo(*args):
|
||||||
kwargs = _arglist_to_dict(args)
|
kwargs = _arglist_to_dict(args)
|
||||||
debug("hook `htmlize' called with arguments %s" % kwargs)
|
debug("hook `htmlize' called with arguments %s" % kwargs)
|
||||||
return kwargs['content']
|
return kwargs['content']
|
||||||
proxy.hook('htmlize', htmlize_demo)
|
#proxy.hook('htmlize', htmlize_demo)
|
||||||
|
|
||||||
def pagetemplate_demo(*args):
|
def pagetemplate_demo(*args):
|
||||||
# Templates are filled out for many different things in ikiwiki, like
|
# Templates are filled out for many different things in ikiwiki, like
|
||||||
|
@ -178,12 +178,10 @@ def templatefile_demo(*args):
|
||||||
# change the default ("page.tmpl"). Template files are looked for in
|
# change the default ("page.tmpl"). Template files are looked for in
|
||||||
# /usr/share/ikiwiki/templates by default.
|
# /usr/share/ikiwiki/templates by default.
|
||||||
#
|
#
|
||||||
# TODO: we cannot really pass undef/None via xml-rpc, so what to do?
|
|
||||||
kwargs = _arglist_to_dict(args)
|
kwargs = _arglist_to_dict(args)
|
||||||
debug("hook `templatefile' called with arguments %s" % kwargs)
|
debug("hook `templatefile' called with arguments %s" % kwargs)
|
||||||
raise NotImplementedError
|
|
||||||
return None
|
return None
|
||||||
#proxy.hook('templatefile', templatefile_demo)
|
proxy.hook('templatefile', templatefile_demo)
|
||||||
|
|
||||||
def sanitize_demo(*args):
|
def sanitize_demo(*args):
|
||||||
# Use this to implement html sanitization or anything else that needs to
|
# Use this to implement html sanitization or anything else that needs to
|
||||||
|
|
Loading…
Reference in New Issue