ikiwiki/plugins/externaldemo

134 lines
3.6 KiB
Perl
Executable File

#!/usr/bin/perl
# Demo external plugin. Kinda pointless, since it's a perl script, but
# useful for testing or as an hint of how to write an external plugin in
# other languages.
use warnings;
use strict;
print STDERR "externaldemo plugin running as pid $$\n";
use RPC::XML;
use RPC::XML::Parser;
use IO::Handle;
# autoflush stdout
$|=1;
# Used to build up RPC calls as they're read from stdin.
my $accum="";
sub rpc_read {
# Read stdin, a line at a time, until a whole RPC call is accumulated.
# Parse to XML::RPC object and return.
while (<>) {
$accum.=$_;
# Kinda hackish approach to parse a single XML RPC out of the
# accumulated input. Perl's RPC::XML library doesn't
# provide a better way to do it. Relies on calls always ending
# with a newline, which ikiwiki's protocol requires be true.
if ($accum =~ /^\s*(<\?xml\s.*?<\/(?:methodCall|methodResponse)>)\n(.*)/s) {
$accum=$2; # the rest
# Now parse the XML RPC.
my $r = RPC::XML::Parser->new->parse($1);
if (! ref $r) {
die "error: XML RPC parse failure $r";
}
return $r;
}
}
return undef;
}
sub rpc_handle {
# Handle an incoming XML RPC command.
my $r=rpc_read();
if (! defined $r) {
return 0;
}
if ($r->isa("RPC::XML::request")) {
my $name=$r->name;
my @args=map { $_->value } @{$r->args};
# Dispatch the requested function. This could be
# done with a switch statement on the name, or
# whatever. I'll use eval to call the function with
# the name.
my $ret = eval $name.'(@args)';
die $@ if $@;
# Now send the repsonse from the function back,
# followed by a newline.
my $resp=RPC::XML::response->new($ret);
$resp->serialize(\*STDOUT);
print "\n";
# stdout needs to be flushed here. If it isn't,
# things will deadlock. Perl flushes it
# automatically when $| is set.
return 1;
}
elsif ($r->isa("RPC::XML::response")) {
die "protocol error; got a response when expecting a request";
}
}
sub rpc_call {
# Make an XML RPC call and return the result.
my $command=shift;
my @params=@_;
my $req=RPC::XML::request->new($command, @params);
$req->serialize(\*STDOUT);
print "\n";
# stdout needs to be flushed here to prevent deadlock. Perl does it
# automatically when $| is set.
my $r=rpc_read();
if ($r->isa("RPC::XML::response")) {
return $r->value->value;
}
else {
die "protocol error; got a request when expecting a response";
}
}
# Now on with the actual plugin. Let's do a simple preprocessor plugin.
sub import {
# The import function will be called by ikiwiki when the plugin is
# loaded. When it's imported, it needs to hook into the preprocessor
# stage of ikiwiki.
rpc_call("hook", type => "preprocess", id => "externaldemo", call => "preprocess");
# Here's an exmaple of how to access values in %IkiWiki::config.
print STDERR "url is set to: ".
rpc_call("getvar", "config", "url")."\n";
# Here's an example of how to inject an arbitrary function into
# ikiwiki. Note use of automatic memoization.
rpc_call("inject", name => "IkiWiki::bob",
call => "formattime", memoize => 1);
print STDERR "externaldemo plugin successfully imported\n";
}
sub preprocess {
# This function will be called when ikiwiki wants to preprocess
# something.
my %params=@_;
# Let's use IkiWiki's pagetitle function to turn the page name into
# a title.
my $title=rpc_call("pagetitle", $params{page});
return "externaldemo plugin preprocessing on $title!";
}
sub bob {
print STDERR "externaldemo plugin's bob called via RPC";
}
# Now all that's left to do is loop and handle each incoming RPC request.
while (rpc_handle()) { print STDERR "externaldemo plugin handled RPC request\n" }