#!/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" }