aboutsummaryrefslogtreecommitdiff
path: root/plugins/externaldemo
blob: fa93e672d86f24c1e253d20717e4c09b19109be1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/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 example of how to inject an arbitrary function into
	# ikiwiki. Ikiwiki will be able to call bob() just like any other
	# function. Note use of automatic memoization.
	rpc_call("inject", name => "IkiWiki::bob", call => "bob",
		memoize => 1);

	# Here's an exmaple of how to access values in %IkiWiki::config.
	print STDERR "url is set to: ".
		rpc_call("getvar", "config", "url")."\n";

	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" }