(no commit message)
[ikiwiki] / plugins / externaldemo
1 #!/usr/bin/perl
2 # Demo external plugin. Kinda pointless, since it's a perl script, but
3 # useful for testing or as an hint of how to write an external plugin in
4 # other languages.
5 use warnings;
6 use strict;
7
8 print STDERR "externaldemo plugin running as pid $$\n";
9
10 use RPC::XML;
11 use IO::Handle;
12
13 # autoflush stdout
14 $|=1;
15
16 # Used to build up RPC calls as they're read from stdin.
17 my $accum="";
18
19 sub rpc_read {
20         # Read stdin, a line at a time, until a whole RPC call is accumulated.
21         # Parse to XML::RPC object and return.
22         while (<>) {
23                 $accum.=$_;
24
25                 # Kinda hackish approach to parse a single XML RPC out of the
26                 # accumulated input. Perl's RPC::XML library doesn't
27                 # provide a better way to do it. Relies on calls always ending
28                 # with a newline, which ikiwiki's protocol requires be true.
29                 if ($accum =~ /^\s*(<\?xml\s.*?<\/(?:methodCall|methodResponse)>)\n(.*)/s) {
30                         $accum=$2; # the rest
31         
32                         # Now parse the XML RPC.
33                         my $parser;
34                         eval q{
35                                 use RPC::XML::ParserFactory;
36                                 $parser = RPC::XML::ParserFactory->new;
37                         };
38                         if ($@) {
39                                 # old interface
40                                 eval q{
41                                         use RPC::XML::Parser;
42                                         $parser = RPC::XML::Parser->new;
43                                 };
44                         }
45                         my $r=$parser->parse($1);
46                         if (! ref $r) {
47                                 die "error: XML RPC parse failure $r";
48                         }
49                         return $r;
50                 }
51         }
52
53         return undef;
54 }
55
56 sub rpc_handle {
57         # Handle an incoming XML RPC command.
58         my $r=rpc_read();
59         if (! defined $r) {
60                 return 0;
61         }
62         if ($r->isa("RPC::XML::request")) {
63                 my $name=$r->name;
64                 my @args=map { $_->value } @{$r->args};
65                 # Dispatch the requested function. This could be
66                 # done with a switch statement on the name, or
67                 # whatever. I'll use eval to call the function with
68                 # the name.
69                 my $ret = eval $name.'(@args)';
70                 die $@ if $@;
71         
72                 # Now send the repsonse from the function back,
73                 # followed by a newline.
74                 my $resp=RPC::XML::response->new($ret);
75                 $resp->serialize(\*STDOUT);
76                 print "\n";
77                 # stdout needs to be flushed here. If it isn't,
78                 # things will deadlock. Perl flushes it
79                 # automatically when $| is set.
80                 return 1;
81         }
82         elsif ($r->isa("RPC::XML::response")) {
83                 die "protocol error; got a response when expecting a request";
84         }
85 }
86
87 sub rpc_call {
88         # Make an XML RPC call and return the result.
89         my $command=shift;
90         my @params=@_;
91
92         my $req=RPC::XML::request->new($command, @params);
93         $req->serialize(\*STDOUT);
94         print "\n";
95         # stdout needs to be flushed here to prevent deadlock. Perl does it
96         # automatically when $| is set.
97         
98         my $r=rpc_read();
99         if ($r->isa("RPC::XML::response")) {
100                 return $r->value->value;
101         }
102         else {
103                 die "protocol error; got a request when expecting a response";
104         }
105 }
106
107 # Now on with the actual plugin. Let's do a simple preprocessor plugin.
108
109 sub import {
110         # The import function will be called by ikiwiki when the plugin is
111         # loaded. When it's imported, it needs to hook into the preprocessor
112         # stage of ikiwiki.
113         rpc_call("hook", type => "preprocess", id => "externaldemo", call => "preprocess");
114
115         # Here's an exmaple of how to access values in %IkiWiki::config.
116         print STDERR "url is set to: ".
117                 rpc_call("getvar", "config", "url")."\n";
118
119         # Here's an example of how to inject an arbitrary function into
120         # ikiwiki. Note use of automatic memoization.
121         rpc_call("inject", name => "IkiWiki::bob",
122                 call => "formattime", memoize => 1);
123
124         print STDERR "externaldemo plugin successfully imported\n";
125 }
126
127 sub preprocess {
128         # This function will be called when ikiwiki wants to preprocess
129         # something.
130         my %params=@_;
131
132         # Let's use IkiWiki's pagetitle function to turn the page name into
133         # a title.
134         my $title=rpc_call("pagetitle", $params{page});
135
136         return "externaldemo plugin preprocessing on $title!";
137 }
138
139 sub bob {
140         print STDERR "externaldemo plugin's bob called via RPC";
141 }
142
143 # Now all that's left to do is loop and handle each incoming RPC request.
144 while (rpc_handle()) { print STDERR "externaldemo plugin handled RPC request\n" }