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