aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>2007-08-13 03:07:31 +0000
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>2007-08-13 03:07:31 +0000
commit506bcbac0420a46997ad531961d543e62c011513 (patch)
tree8eaa71367c58158924077e61a3f6b2d1383cbb96
parentb2101850e2098684a864e6143725247606da96bb (diff)
downloadikiwiki-506bcbac0420a46997ad531961d543e62c011513.tar
ikiwiki-506bcbac0420a46997ad531961d543e62c011513.tar.gz
* Fix --libdir to work at the command line.
* Plugins can now be written as standalone external programs, which can be written in any language that can do XML::RPC.
-rw-r--r--IkiWiki.pm9
-rw-r--r--IkiWiki/Plugin/external.pm169
-rwxr-xr-xMakefile.PL5
-rw-r--r--debian/changelog5
-rw-r--r--doc/plugins/contrib.mdwn13
-rw-r--r--doc/plugins/write.mdwn39
-rw-r--r--doc/plugins/write/external.mdwn88
-rw-r--r--doc/todo/support_for_plugins_written_in_other_languages.mdwn43
-rw-r--r--doc/usage.mdwn7
-rwxr-xr-xikiwiki.in2
-rwxr-xr-xplugins/externaldemo132
-rw-r--r--po/es.po29
-rw-r--r--po/fr.po78
-rw-r--r--po/ikiwiki.pot24
14 files changed, 536 insertions, 107 deletions
diff --git a/IkiWiki.pm b/IkiWiki.pm
index d5a641526..7c35ce2b5 100644
--- a/IkiWiki.pm
+++ b/IkiWiki.pm
@@ -160,11 +160,20 @@ sub loadplugin ($) { #{{{
return if grep { $_ eq $plugin} @{$config{disable_plugins}};
+ foreach my $dir ($config{libdir}, "$installdir/lib/ikiwiki") {
+ if (defined $dir && -x "$dir/plugins/$plugin") {
+ require IkiWiki::Plugin::external;
+ import IkiWiki::Plugin::external "$dir/plugins/$plugin";
+ return 1;
+ }
+ }
+
my $mod="IkiWiki::Plugin::".possibly_foolish_untaint($plugin);
eval qq{use $mod};
if ($@) {
error("Failed to load plugin $mod: $@");
}
+ return 1;
} #}}}
sub error ($;$) { #{{{
diff --git a/IkiWiki/Plugin/external.pm b/IkiWiki/Plugin/external.pm
new file mode 100644
index 000000000..e3504a30d
--- /dev/null
+++ b/IkiWiki/Plugin/external.pm
@@ -0,0 +1,169 @@
+#!/usr/bin/perl
+# Support for external plugins written in other languages.
+# Communication via XML RPC a pipe.
+# See externaldemo for an example of a plugin that uses this.
+package IkiWiki::Plugin::external;
+
+use warnings;
+use strict;
+use IkiWiki 2.00;
+use RPC::XML;
+use RPC::XML::Parser;
+use IPC::Open2;
+use IO::Handle;
+
+my %plugins;
+
+sub import { #{{{
+ my $self=shift;
+ my $plugin=shift;
+ return unless defined $plugin;
+
+ my ($plugin_read, $plugin_write);
+ my $pid = open2($plugin_read, $plugin_write, $plugin);
+
+ # open2 doesn't respect "use open ':utf8'"
+ binmode($plugin_read, ':utf8');
+ binmode($plugin_write, ':utf8');
+
+ $plugins{$plugin}={in => $plugin_read, out => $plugin_write, pid => $pid,
+ accum => ""};
+
+ rpc_call($plugins{$plugin}, "import");
+} #}}}
+
+sub rpc_write ($$) { #{{{
+ my $fh=shift;
+ my $string=shift;
+
+ $fh->print($string."\n");
+ $fh->flush;
+} #}}}
+
+sub rpc_call ($$;@) { #{{{
+ my $plugin=shift;
+ my $command=shift;
+
+ # send the command
+ my $req=RPC::XML::request->new($command, @_);
+ rpc_write($plugin->{out}, $req->as_string);
+
+ # process incoming rpc until a result is available
+ while ($_ = $plugin->{in}->getline) {
+ $plugin->{accum}.=$_;
+ while ($plugin->{accum} =~ /^\s*(<\?xml\s.*?<\/(?:methodCall|methodResponse)>)\n(.*)/s) {
+ $plugin->{accum}=$2;
+ my $r = RPC::XML::Parser->new->parse($1);
+ error("XML RPC parser failure: $r") unless ref $r;
+ if ($r->isa('RPC::XML::response')) {
+ my $value=$r->value;
+ if ($value->isa('RPC::XML::array')) {
+ return @{$value->value};
+ }
+ elsif ($value->isa('RPC::XML::struct')) {
+ return %{$value->value};
+ }
+ elsif ($value->isa('RPC::XML::fault')) {
+ die $value->string;
+ }
+ else {
+ return $value->value;
+ }
+ }
+
+ my $name=$r->name;
+ my @args=map { $_->value } @{$r->args};
+
+ # When dispatching a function, first look in
+ # IkiWiki::RPC::XML. This allows overriding
+ # IkiWiki functions with RPC friendly versions.
+ my $ret;
+ if (exists $IkiWiki::RPC::XML::{$name}) {
+ $ret=$IkiWiki::RPC::XML::{$name}($plugin, @args);
+ }
+ elsif (exists $IkiWiki::{$name}) {
+ $ret=$IkiWiki::{$name}(@args);
+ }
+ else {
+ error("XML RPC call error, unknown function: $name");
+ }
+
+ my $string=eval { RPC::XML::response->new($ret)->as_string };
+ if ($@ && ref $ret) {
+ # One common reason for serialisation to
+ # fail is a complex return type that cannot
+ # be represented as an XML RPC response.
+ # Handle this case by just returning 1.
+ $string=eval { RPC::XML::response->new(1)->as_string };
+ }
+ if ($@) {
+ error("XML response serialisation failed: $@");
+ }
+ rpc_write($plugin->{out}, $string);
+ }
+ }
+
+ return undef;
+} #}}}
+
+package IkiWiki::RPC::XML;
+
+sub getvar ($$$) { #{{{
+ my $plugin=shift;
+ my $varname="IkiWiki::".shift;
+ my $key=shift;
+
+ no strict 'refs';
+ my $ret=$varname->{$key};
+ use strict 'refs';
+ return $ret;
+} #}}}
+
+sub setvar ($$$;@) { #{{{
+ my $plugin=shift;
+ my $varname="IkiWiki::".shift;
+ my $key=shift;
+
+ no strict 'refs';
+ my $ret=$varname->{$key}=@_;
+ use strict 'refs';
+ return $ret;
+} #}}}
+
+sub inject ($@) { #{{{
+ # Bind a given perl function name to a particular RPC request.
+ my $plugin=shift;
+ my %params=@_;
+
+ if (! exists $params{name} || ! exists $params{call}) {
+ die "inject needs name and call parameters";
+ }
+ my $sub = sub {
+ IkiWiki::Plugin::external::rpc_call($plugin, $params{call}, @_)
+ };
+ eval qq{*$params{name}=\$sub};
+ return 1;
+} #}}}
+
+sub hook ($@) { #{{{
+ # the call parameter is a function name to call, since XML RPC
+ # cannot pass a function reference
+ my $plugin=shift;
+ my %params=@_;
+
+ my $callback=$params{call};
+ delete $params{call};
+
+ IkiWiki::hook(%params, call => sub {
+ IkiWiki::Plugin::external::rpc_call($plugin, $callback, @_)
+ });
+} #}}}
+
+sub pagespec_match ($@) { #{{{
+ # convert pagespec_match's return object into a XML RPC boolean
+ my $plugin=shift;
+
+ return RPC::XML::boolean->new(0 + IkiWiki::pagespec_march(@_));
+} #}}}
+
+1
diff --git a/Makefile.PL b/Makefile.PL
index 985c944ad..0e306ac08 100755
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -55,6 +55,11 @@ extra_install:
install -m 644 $$file $(DESTDIR)$(PREFIX)/share/ikiwiki/$$dir; \
done; \
done
+
+ install -d $(DESTDIR)$(PREFIX)/lib/ikiwiki/plugins
+ for file in `find plugins -maxdepth 1 -type f`; do \
+ install -m 755 $$file $(DESTDIR)$(PREFIX)/lib/ikiwiki/plugins; \
+ done; \
install -d $(DESTDIR)$(PREFIX)/share/man/man1
install -m 644 ikiwiki.man $(DESTDIR)$(PREFIX)/share/man/man1/ikiwiki.1
diff --git a/debian/changelog b/debian/changelog
index 54ef280e3..2b1fbae27 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -22,8 +22,11 @@ ikiwiki (2.6) UNRELEASED; urgency=low
* Support pagespec "functions" with no parameters, like included() in the
conditional plugin.
* Add time=mtime option to postsparkline.
+ * Fix --libdir to work at the command line.
+ * Plugins can now be written as standalone external programs, which can
+ be written in any language that can do XML::RPC.
- -- Joey Hess <joeyh@debian.org> Sun, 12 Aug 2007 05:17:23 -0400
+ -- Joey Hess <joeyh@debian.org> Sun, 12 Aug 2007 20:49:23 -0400
ikiwiki (2.5) unstable; urgency=low
diff --git a/doc/plugins/contrib.mdwn b/doc/plugins/contrib.mdwn
index dc8b90771..abdf1bd4e 100644
--- a/doc/plugins/contrib.mdwn
+++ b/doc/plugins/contrib.mdwn
@@ -6,10 +6,10 @@ rootpage="plugins/contrib" postformtext="Add a new plugin named:" show=0]]
# Installing third party plugins
-Plugins are perl modules and should be installed somewhere in the perl
-module search path. See the @INC list at the end of the output of `perl -V`
-for a list of the directories in that path. All plugins are in the
-IkiWiki::Plugin namespace, so they go in a IkiWiki/Plugin subdirectory
+Most ikiwiki plugins are perl modules and should be installed somewhere in
+the perl module search path. See the @INC list at the end of the output of
+`perl -V` for a list of the directories in that path. All plugins are in
+the IkiWiki::Plugin namespace, so they go in a IkiWiki/Plugin subdirectory
inside the perl search path. For example, if your perl looks in
`/usr/local/lib/site_perl` for modules, you can locally install ikiwiki
plugins to `/usr/local/lib/site_perl/IkiWiki/Plugin`
@@ -17,3 +17,8 @@ plugins to `/usr/local/lib/site_perl/IkiWiki/Plugin`
You can use the `libdir` configuration option to add a directory to the
search path. For example, if you set `libdir` to `/home/you/.ikiwiki/`,
then ikiwiki will look for plugins in `/home/you/.ikiwiki/IkiWiki/Plugins`.
+
+Ikiwiki also supports plugins that are external programs. These are
+typically written in some other language than perl. Ikiwiki searches for
+these in /usr/lib/ikiwiki/plugins by default. If `libdir` is set, it will
+also look in that directory, for example in `/home/you/.ikiwiki/plugins`.
diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn
index 416f1b86e..016746abb 100644
--- a/doc/plugins/write.mdwn
+++ b/doc/plugins/write.mdwn
@@ -1,8 +1,26 @@
-ikiwiki [[plugins]] are written in perl. Each plugin is a perl module, in
-the `IkiWiki::Plugin` namespace. The name of the plugin is typically in
-lowercase, such as `IkiWiki::Plugin::inline`. Ikiwiki includes a
-`IkiWiki::Plugin::skeleton` that can be fleshed out to make a useful
-plugin. `IkiWiki::Plugin::pagecount` is another simple example.
+Most ikiwiki [[plugins]] are written in perl, like ikiwiki. This gives the
+plugin full access to ikiwiki's internals, and is the most efficient.
+However, plugins can actually be written in any language that supports XML
+RPC. These are called [[external]] plugins.
+
+A plugin written in perl is a perl module, in the `IkiWiki::Plugin`
+namespace. The name of the plugin is typically in lowercase, such as
+`IkiWiki::Plugin::inline`. Ikiwiki includes a `IkiWiki::Plugin::skeleton`
+that can be fleshed out to make a useful plugin.
+`IkiWiki::Plugin::pagecount` is another simple example. All perl plugins
+should `use IkiWiki` to import the ikiwiki plugin interface. It's a good
+idea to include the version number of the plugin interface that your plugin
+expects: `use IkiWiki 2.00`.
+
+An external plugin is an executable program. It can be written in any
+language. Its interface to ikiwiki is via XML RPC, which it reads from
+ikiwiki on its standard input, and writes to ikiwiki on its standard
+output. For more details on writing external plugins, see [[external]].
+
+Despite these two types of plugins having such different interfaces,
+they're the same as far as how they hook into ikiwiki. This document will
+explain how to write both sorts of plugins, albeit with an emphasis on perl
+plugins.
[[toc levels=2]]
@@ -19,18 +37,13 @@ being edited.
## Registering plugins
-All plugins should `use IkiWiki` to import the ikiwiki plugin interface.
-It's a good idea to include the version number of the plugin interface that
-your plugin expects: `use IkiWiki 2.00`
-
Plugins should, when imported, call `hook()` to hook into ikiwiki's
processing. The function uses named parameters, and use varies depending on
the type of hook being registered -- see below. Note that a plugin can call
the function more than once to register multiple hooks. All calls to
`hook()` should be passed a "type" parameter, which gives the type of
hook, a "id" paramter, which should be a unique string for this plugin, and
-a "call" parameter, which is a reference to a function to call for the
-hook.
+a "call" parameter, which tells what function to call for the hook.
An optional "last" parameter, if set to a true value, makes the hook run
after all other hooks of its type. Useful if the hook depends on some other
@@ -272,7 +285,7 @@ namespace. These variables and functions are the ones most plugins need,
and a special effort will be made to avoid changing them in incompatible
ways, and to document any changes that have to be made in the future.
-Note that IkiWiki also provides other variables functions that are not
+Note that IkiWiki also provides other variables and functions that are not
exported by default. No guarantee is made about these in the future, so if
it's not exported, the wise choice is to not use it.
@@ -287,7 +300,7 @@ hash. The best way to understand the contents of the hash is to look at
If your plugin needs to access data about other pages in the wiki. It can
use the following hashes, using a page name as the key:
-* `links` lists the names of each page that a page links to, in an array
+* `%links` lists the names of each page that a page links to, in an array
reference.
* `%destsources` contains the name of the source file used to create each
destination file.
diff --git a/doc/plugins/write/external.mdwn b/doc/plugins/write/external.mdwn
new file mode 100644
index 000000000..735f7a20e
--- /dev/null
+++ b/doc/plugins/write/external.mdwn
@@ -0,0 +1,88 @@
+External plugins are standalone, executable programs, that can be written
+in any language. When ikiwiki starts up, it runs the program, and
+communicates with it using XML RPC. If you want to [[write]] an external
+plugin, read on..
+
+ikiwiki contains one sample external plugin, named `externaldemo`. This is
+written in perl, but is intended to be an example of how to write an
+external plugin in your favorite programming language. Wow us at how much
+easier you can do the same thing in your favorite language. ;-)
+
+## How external plugins use XML RPC
+
+While XML RPC is typically used over http, ikiwiki doesn't do that.
+Instead, the external plugin reads XML RPC data from stdin, and writes it
+to stdout. To ease parsing, each separate XML RPC request or response must
+start at the beginning of a line, and end with a newline. When outputting
+XML RPC to stdout, be _sure_ to flush stdout. Failure to do so will result
+in deadlock!
+
+An external plugin should operate in a loop. First, read a command from
+stdin, using XML RPC. Dispatch the command, and return its result to
+stdout, also using XML RPC. After reading a command, and before returning
+the result, the plugin can output XML RPC requests of its own, calling
+functions in ikiwiki. Note: *Never* make an XML RPC request at any other
+time. Ikiwiki won't be listening for it, and you will deadlock.
+
+When ikiwiki starts up an external plugin, the first RPC it will make
+is to call the plugin's `import()` function. That function typically makes
+an RPC to ikiwiki's `hook()` function, registering a callback.
+
+An external plugin can use XML RPC to call any of the exported functions
+documented in the [[plugin_interface_documentation|write]]. It can also
+actually call any non-exported IkiWiki function, but doing so is a good way
+to break your plugin when ikiwiki changes. There is currently no versioned
+interface like there is for perl plugins, but external plugins were first
+supported in ikiwiki version 2.6.
+
+## Accessing data structures
+
+Ikiwiki has a few global data structures such as `%config`, which holds
+its configuration. External plugins can use the `getvar` and `setvar` RPCs
+to access any such global hash. To get the "url" configuration value,
+call `getvar("config", "url")`. To set it, call
+`setvar("config", "url", "http://example.com/)`.
+
+## Notes on function parameters
+
+The [[plugin_interface_documentation|write]] talks about functions that take
+"named parameters". When such a function is called over XML RPC, such named
+parameters look like a list of keys and values:
+
+ page, foo, destpage, bar, magnify, 1
+
+If a name is repeated in the list, the later value overrides the earlier
+one:
+
+ name, Bob, age, 20, name, Sally, gender, female
+
+In perl, boiling this down to an associative array of named parameters is
+very easy:
+
+ sub foo {
+ my %params=@list;
+
+Other languages might not find it so easy. If not, it might be a good idea
+to convert these named parameters into something more natural for the
+language as part of their XML RPC interface.
+
+## Function injection
+
+Some parts of ikiwiki are extensible by adding functions. For example, the
+RCS interface relies on plugins providing several IkiWiki::rcs_* functions.
+It's actually possible to do this from an external plugin too.
+
+To make your external plugin provide an `IkiWiki::rcs_update` function, for
+example, make an RPC call to `inject`. Pass it named parameters "name" and
+"call", where "name" is the name of the function to inject into perl (here
+"Ikiwiki::rcs_update" and "call" is the RPC call ikiwiki will make whenever
+that function is run.
+
+## Limitations of XML RPC
+
+Since XML RPC can't pass around references to objects, it can't be used
+with functions that take or return such references. That means you can't
+use XML RPC for `cgi` or `formbuilder` hooks (which are passed CGI and
+FormBuilder perl objects), or use it to call `template()` (which returns a
+perl HTML::Template object).
+
diff --git a/doc/todo/support_for_plugins_written_in_other_languages.mdwn b/doc/todo/support_for_plugins_written_in_other_languages.mdwn
index 33378a4fe..8476d1b44 100644
--- a/doc/todo/support_for_plugins_written_in_other_languages.mdwn
+++ b/doc/todo/support_for_plugins_written_in_other_languages.mdwn
@@ -1,5 +1,7 @@
ikiwiki should support writing plugins in other languages
+> [[done]] !!
+
While it should be possible to call ikiwiki from C, doing the callbacks in C is
probably hard. And accessing perl at all from C is ugly. It also doesn't
make it very easy to write plugins in an interpreted language, since that
@@ -11,46 +13,39 @@ child process that it can spawn. The child could then be any program,
written in any language. It could talk XML RPC via stdio. (This assumes
that most languages allow easily serialising XML::RPC calls and responses
to a file descriptor. Some XML RPC implementations may be hardcoded to use
-http..)
+http..) For ease of implementation, each rpc request sent via stio should
+end with a newline, and begin with "<?xml ..>".
Here's how it would basically look, not showing the actual XML RPC used to
pass values.
- -> import
- <- 1
- <- hook type => preprocess, id => foo
- -> 1
- <- done 1
- -> 1
-
- -> callback type => preprocess, id => foo, page => bar
- <- 1
- <- getconfig url
- -> "http://example.com", ...
- <- debug "foo"
- -> 1
- <- done "my return value"
- -> 1
+ -> call import
+ <- call hook type => preprocess, id => foo, call => plugin_preprocess
+ -> result 1
+ <- result 1
+
+ -> call plugin_preprocess page => bar
+ <- call getconfig url
+ -> result "http://example.com", ...
+ <- call debug "foo"
+ -> result 1
+ <- result done "my return value"
From ikiwiki's POV:
* ikiwiki always initiates each conversation with a command
* After sending a command, ikiwiki reads commands, dispatches them, and
- returns the results, in a loop.
-* The loop continues until the plugin calls the "done" command, with a value
- that is the return value for the command that initiated the conversation.
+ returns the results, in a loop, until it gets a result for the command it
+ called.
From the plugin's POV:
* It's probably sitting in an XML::RPC loop.
* Get a command from ikiwiki.
-* Disaptch the command to the appropriate function. The main commands seem
- to be "import" and "callback"; callback can call any function that the
- plugin has registered a hook for. Others commands might include
- "rcs_*" for RCS plugins..
+* Dispatch the command to the appropriate function.
* The function can use XML::RPC to communicate with ikiwiki to get things
like config values; and to call ikiwiki functions.
-* When the callback returns, use XML::RPC to send a "done" command to ikiwiki.
+* Send the function's return value back to ikiwiki.
Simple enough, really. ikiwiki would need to add accessor functions for
all important variables, such as "getconfig" and "setconfig". It would
diff --git a/doc/usage.mdwn b/doc/usage.mdwn
index 7f556cc95..aba213f21 100644
--- a/doc/usage.mdwn
+++ b/doc/usage.mdwn
@@ -231,9 +231,10 @@ configuration options of their own.
* --libdir directory
- Makes ikiwiki look in the specified directory first, before the regular perl
- library directories. For example, if you set libdir to "/home/you/.ikiwiki/",
- you can install plugins in "/home/you/.ikiwiki/IkiWiki/Plugin/".
+ Makes ikiwiki look in the specified directory first, before the regular
+ locations when loading library files and plugins. For example, if you set
+ libdir to "/home/you/.ikiwiki/", you can install a Foo.pm plugin as
+ "/home/you/.ikiwiki/IkiWiki/Plugin/Foo.pm".
* --discussion, --no-discussion
diff --git a/ikiwiki.in b/ikiwiki.in
index c37085bbc..6242865ee 100755
--- a/ikiwiki.in
+++ b/ikiwiki.in
@@ -50,7 +50,7 @@ sub getconfig () { #{{{
"httpauth!" => \$config{httpauth},
"userdir=s" => \$config{userdir},
"htmlext=s" => \$config{htmlext},
- "libdir" => \$config{libdir},
+ "libdir=s" => \$config{libdir},
"exclude=s@" => sub {
push @{$config{wiki_file_prune_regexps}}, $_[1];
},
diff --git a/plugins/externaldemo b/plugins/externaldemo
new file mode 100755
index 000000000..6bbced30e
--- /dev/null
+++ b/plugins/externaldemo
@@ -0,0 +1,132 @@
+#!/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 approch to parse a single XML RPC out of the
+ # accumulated input. 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.
+ rpc_call("inject", name => "IkiWiki::bob", call => "bob");
+
+ # 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" }
diff --git a/po/es.po b/po/es.po
index da78a0d8a..3b7c6cd34 100644
--- a/po/es.po
+++ b/po/es.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: es\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-08-05 13:46-0700\n"
+"POT-Creation-Date: 2007-08-12 22:57-0400\n"
"PO-Revision-Date: 2007-04-28 22:01+0200\n"
"Last-Translator: Víctor Moral <victor@taquiones.net>\n"
"Language-Team: Spanish <es@li.org>\n"
@@ -41,7 +41,7 @@ msgid "%s is not an editable page"
msgstr "la página %s no es modificable"
#: ../IkiWiki/CGI.pm:429 ../IkiWiki/Plugin/brokenlinks.pm:24
-#: ../IkiWiki/Plugin/inline.pm:184 ../IkiWiki/Plugin/opendiscussion.pm:17
+#: ../IkiWiki/Plugin/inline.pm:208 ../IkiWiki/Plugin/opendiscussion.pm:17
#: ../IkiWiki/Plugin/orphans.pm:28 ../IkiWiki/Render.pm:99
#: ../IkiWiki/Render.pm:179
msgid "discussion"
@@ -58,11 +58,11 @@ msgstr "creando página %s"
msgid "editing %s"
msgstr "modificando página %s"
-#: ../IkiWiki/CGI.pm:691
+#: ../IkiWiki/CGI.pm:688
msgid "You are banned."
msgstr "Ha sido expulsado."
-#: ../IkiWiki/CGI.pm:723
+#: ../IkiWiki/CGI.pm:708
msgid "login failed, perhaps you need to turn on cookies?"
msgstr ""
"registro fallido, ¿ tal vez necesita activar las cookies en el navegador ?"
@@ -170,34 +170,35 @@ msgid "failed to resize: %s"
msgstr "redimensionado fallido: %s"
#: ../IkiWiki/Plugin/img.pm:96
+#, perl-format
msgid "failed to determine size of image %s"
msgstr "no he podido determinar el tamaño de la imagen %s"
-#: ../IkiWiki/Plugin/inline.pm:36
+#: ../IkiWiki/Plugin/inline.pm:39
msgid "Must specify url to wiki with --url when using --rss or --atom"
msgstr ""
"Es obligatorio indicar un url al wiki cuando se usan los parámetros --rss ó "
"--atom"
-#: ../IkiWiki/Plugin/inline.pm:106
+#: ../IkiWiki/Plugin/inline.pm:130
#, perl-format
msgid "unknown sort type %s"
msgstr "no conozco este tipo de ordenación %s"
-#: ../IkiWiki/Plugin/inline.pm:146
+#: ../IkiWiki/Plugin/inline.pm:170
msgid "Add a new post titled:"
msgstr "Añadir una entrada nueva titulada:"
-#: ../IkiWiki/Plugin/inline.pm:161
+#: ../IkiWiki/Plugin/inline.pm:185
#, perl-format
msgid "nonexistant template %s"
msgstr "la plantilla %s no existe "
-#: ../IkiWiki/Plugin/inline.pm:192 ../IkiWiki/Render.pm:103
+#: ../IkiWiki/Plugin/inline.pm:216 ../IkiWiki/Render.pm:103
msgid "Discussion"
msgstr "Comentarios"
-#: ../IkiWiki/Plugin/inline.pm:403
+#: ../IkiWiki/Plugin/inline.pm:429
msgid "RPC::XML::Client not found, not pinging"
msgstr "No he encontrado el componente RPC::XML::Client, no envío señal alguna"
@@ -281,11 +282,11 @@ msgstr "El complemento polygen no ha sido instalado"
msgid "polygen failed"
msgstr "El programa polygen ha fallado"
-#: ../IkiWiki/Plugin/postsparkline.pm:25
+#: ../IkiWiki/Plugin/postsparkline.pm:32
msgid "missing formula"
msgstr "falta la fórmula"
-#: ../IkiWiki/Plugin/postsparkline.pm:32
+#: ../IkiWiki/Plugin/postsparkline.pm:39
msgid "unknown formula"
msgstr "fórmula desconocida "
@@ -586,7 +587,7 @@ msgstr ""
"Es obligatorio especificar un url al wiki con el parámetro --url si se "
"utiliza el parámetro --cgi"
-#: ../IkiWiki.pm:175 ../IkiWiki.pm:176
+#: ../IkiWiki.pm:184 ../IkiWiki.pm:185
msgid "Error"
msgstr "Error"
@@ -594,7 +595,7 @@ msgstr "Error"
#. translators: preprocessor directive name,
#. translators: the second a page name, the
#. translators: third a number.
-#: ../IkiWiki.pm:667
+#: ../IkiWiki.pm:676
#, perl-format
msgid "%s preprocessing loop detected on %s at depth %i"
msgstr ""
diff --git a/po/fr.po b/po/fr.po
index 2705758db..8525577e4 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: ikiwiki\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-06-29 20:49-0400\n"
+"POT-Creation-Date: 2007-08-12 22:57-0400\n"
"PO-Revision-Date: 2007-08-05 23:38+0200\n"
"Last-Translator: Cyril Brulebois <cyril.brulebois@enst-bretagne.fr>\n"
"Language-Team: French <debian-l10n-french@lists.debian.org>\n"
@@ -37,34 +37,34 @@ msgstr "Administrateur"
msgid "Preferences saved."
msgstr "Les préférences ont été enregistrées."
-#: ../IkiWiki/CGI.pm:349
+#: ../IkiWiki/CGI.pm:350
#, perl-format
msgid "%s is not an editable page"
msgstr "%s n'est pas une page éditable"
-#: ../IkiWiki/CGI.pm:428 ../IkiWiki/Plugin/brokenlinks.pm:24
-#: ../IkiWiki/Plugin/inline.pm:184 ../IkiWiki/Plugin/opendiscussion.pm:17
-#: ../IkiWiki/Plugin/orphans.pm:28 ../IkiWiki/Render.pm:91
-#: ../IkiWiki/Render.pm:171
+#: ../IkiWiki/CGI.pm:429 ../IkiWiki/Plugin/brokenlinks.pm:24
+#: ../IkiWiki/Plugin/inline.pm:208 ../IkiWiki/Plugin/opendiscussion.pm:17
+#: ../IkiWiki/Plugin/orphans.pm:28 ../IkiWiki/Render.pm:99
+#: ../IkiWiki/Render.pm:179
msgid "discussion"
msgstr "Discussion"
-#: ../IkiWiki/CGI.pm:474
+#: ../IkiWiki/CGI.pm:475
#, perl-format
msgid "creating %s"
msgstr "Création de %s"
-#: ../IkiWiki/CGI.pm:491 ../IkiWiki/CGI.pm:506 ../IkiWiki/CGI.pm:517
-#: ../IkiWiki/CGI.pm:543 ../IkiWiki/CGI.pm:587
+#: ../IkiWiki/CGI.pm:493 ../IkiWiki/CGI.pm:509 ../IkiWiki/CGI.pm:521
+#: ../IkiWiki/CGI.pm:548 ../IkiWiki/CGI.pm:593
#, perl-format
msgid "editing %s"
msgstr "Édition de %s"
-#: ../IkiWiki/CGI.pm:684
+#: ../IkiWiki/CGI.pm:688
msgid "You are banned."
msgstr "Vous avez été banni."
-#: ../IkiWiki/CGI.pm:716
+#: ../IkiWiki/CGI.pm:708
msgid "login failed, perhaps you need to turn on cookies?"
msgstr ""
"Échec de l'identification, vous devriez peut-être autoriser les cookies."
@@ -146,11 +146,11 @@ msgstr "Échec du lancement de « fortune »"
msgid "failed to find url in html"
msgstr "Échec dans la recherche de l'URL dans le code HTML"
-#: ../IkiWiki/Plugin/graphviz.pm:59
+#: ../IkiWiki/Plugin/graphviz.pm:58
msgid "failed to run graphviz"
msgstr "Échec du lancement de graphviz"
-#: ../IkiWiki/Plugin/graphviz.pm:81
+#: ../IkiWiki/Plugin/graphviz.pm:85
msgid "prog not a valid graphviz program"
msgstr "Le programme n'est pas un programme graphviz valable"
@@ -175,31 +175,31 @@ msgstr "Échec du redimensionnement : %s"
msgid "failed to determine size of image %s"
msgstr "Échec du redimensionnement : %s"
-#: ../IkiWiki/Plugin/inline.pm:36
+#: ../IkiWiki/Plugin/inline.pm:39
msgid "Must specify url to wiki with --url when using --rss or --atom"
msgstr ""
"Vous devez indiquer l'URL du wiki par --url lors de l'utilisation de --rss "
"ou --atom"
-#: ../IkiWiki/Plugin/inline.pm:106
+#: ../IkiWiki/Plugin/inline.pm:130
#, perl-format
msgid "unknown sort type %s"
msgstr "Type de tri %s inconnu"
-#: ../IkiWiki/Plugin/inline.pm:146
+#: ../IkiWiki/Plugin/inline.pm:170
msgid "Add a new post titled:"
msgstr "Ajouter un nouveau article dont le titre est :"
-#: ../IkiWiki/Plugin/inline.pm:161
+#: ../IkiWiki/Plugin/inline.pm:185
#, perl-format
msgid "nonexistant template %s"
msgstr "Le modèle (« template ») %s n'existe pas"
-#: ../IkiWiki/Plugin/inline.pm:192 ../IkiWiki/Render.pm:95
+#: ../IkiWiki/Plugin/inline.pm:216 ../IkiWiki/Render.pm:103
msgid "Discussion"
msgstr "Discussion"
-#: ../IkiWiki/Plugin/inline.pm:403
+#: ../IkiWiki/Plugin/inline.pm:429
msgid "RPC::XML::Client not found, not pinging"
msgstr "RPC::XML::Client introuvable, pas de réponse au ping"
@@ -247,6 +247,10 @@ msgstr "Obtenir un compte OpenID"
msgid "All pages are linked to by other pages."
msgstr "Toutes les pages sont liées à d'autres pages."
+#: ../IkiWiki/Plugin/pagetemplate.pm:21
+msgid "bad or missing template"
+msgstr ""
+
#: ../IkiWiki/Plugin/passwordauth.pm:162
msgid "Account creation successful. Now you can Login."
msgstr "Le compte a été créé. Vous pouvez maintenant vous identifier."
@@ -279,11 +283,11 @@ msgstr "polygen n'est pas installé"
msgid "polygen failed"
msgstr "Échec du lancement de polygen"
-#: ../IkiWiki/Plugin/postsparkline.pm:25
+#: ../IkiWiki/Plugin/postsparkline.pm:32
msgid "missing formula"
msgstr "formule manquante"
-#: ../IkiWiki/Plugin/postsparkline.pm:32
+#: ../IkiWiki/Plugin/postsparkline.pm:39
msgid "unknown formula"
msgstr "formule inconnue"
@@ -459,47 +463,47 @@ msgstr ""
"après un commit sur le SVN (« hook post-commit »), impossible d'envoyer des "
"notifications"
-#: ../IkiWiki/Render.pm:255 ../IkiWiki/Render.pm:275
+#: ../IkiWiki/Render.pm:263 ../IkiWiki/Render.pm:283
#, perl-format
msgid "skipping bad filename %s"
msgstr "Saut du fichier incorrect %s"
-#: ../IkiWiki/Render.pm:315
+#: ../IkiWiki/Render.pm:323
#, perl-format
msgid "removing old page %s"
msgstr "Suppression de l'ancienne page %s"
-#: ../IkiWiki/Render.pm:343
+#: ../IkiWiki/Render.pm:356
#, perl-format
msgid "scanning %s"
msgstr "Parcours de %s"
-#: ../IkiWiki/Render.pm:348
+#: ../IkiWiki/Render.pm:361
#, perl-format
msgid "rendering %s"
msgstr "Rendu de %s"
-#: ../IkiWiki/Render.pm:360
+#: ../IkiWiki/Render.pm:373
#, perl-format
msgid "rendering %s, which links to %s"
msgstr "Rendu de %s, qui est lié à %s"
-#: ../IkiWiki/Render.pm:377
+#: ../IkiWiki/Render.pm:390
#, perl-format
msgid "rendering %s, which depends on %s"
msgstr "Rendu de %s, qui dépend de %s"
-#: ../IkiWiki/Render.pm:415
+#: ../IkiWiki/Render.pm:428
#, perl-format
msgid "rendering %s, to update its backlinks"
msgstr "Rendu de %s, afin de mettre à jour ses rétroliens"
-#: ../IkiWiki/Render.pm:427
+#: ../IkiWiki/Render.pm:440
#, perl-format
msgid "removing %s, no longer rendered by %s"
msgstr "Suppression de %s, qui n'est plus rendu par %s"
-#: ../IkiWiki/Render.pm:453
+#: ../IkiWiki/Render.pm:466
#, perl-format
msgid "ikiwiki: cannot render %s"
msgstr "ikiwiki : impossible d'effectuer le rendu de %s"
@@ -558,13 +562,13 @@ msgid "failed to write %s: %s"
msgstr "Échec de l'écriture de %s : %s"
#. translators: The parameter is a C filename.
-#: ../IkiWiki/Wrapper.pm:100
+#: ../IkiWiki/Wrapper.pm:107
#, perl-format
msgid "failed to compile %s"
msgstr "Échec de la compilation de %s"
#. translators: The parameter is a filename.
-#: ../IkiWiki/Wrapper.pm:108
+#: ../IkiWiki/Wrapper.pm:115
#, perl-format
msgid "successfully generated %s"
msgstr "%s a été créé avec succès"
@@ -573,13 +577,17 @@ msgstr "%s a été créé avec succès"
msgid "usage: ikiwiki [options] source dest"
msgstr "Syntaxe : ikiwiki [options] source destination"
-#: ../IkiWiki.pm:122
+#: ../ikiwiki.in:81
+msgid "usage: --set var=value"
+msgstr ""
+
+#: ../IkiWiki.pm:124
msgid "Must specify url to wiki with --url when using --cgi"
msgstr ""
"Vous devez indiquer une URL vers le wiki par --url lors de l'utilisation de "
"--cgi"
-#: ../IkiWiki.pm:169 ../IkiWiki.pm:170
+#: ../IkiWiki.pm:184 ../IkiWiki.pm:185
msgid "Error"
msgstr "Erreur"
@@ -587,7 +595,7 @@ msgstr "Erreur"
#. translators: preprocessor directive name,
#. translators: the second a page name, the
#. translators: third a number.
-#: ../IkiWiki.pm:656
+#: ../IkiWiki.pm:676
#, perl-format
msgid "%s preprocessing loop detected on %s at depth %i"
msgstr ""
diff --git a/po/ikiwiki.pot b/po/ikiwiki.pot
index 31a3430c4..b4a3b70ec 100644
--- a/po/ikiwiki.pot
+++ b/po/ikiwiki.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-08-05 14:56-0700\n"
+"POT-Creation-Date: 2007-08-12 22:57-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -42,7 +42,7 @@ msgid "%s is not an editable page"
msgstr ""
#: ../IkiWiki/CGI.pm:429 ../IkiWiki/Plugin/brokenlinks.pm:24
-#: ../IkiWiki/Plugin/inline.pm:206 ../IkiWiki/Plugin/opendiscussion.pm:17
+#: ../IkiWiki/Plugin/inline.pm:208 ../IkiWiki/Plugin/opendiscussion.pm:17
#: ../IkiWiki/Plugin/orphans.pm:28 ../IkiWiki/Render.pm:99
#: ../IkiWiki/Render.pm:179
msgid "discussion"
@@ -173,29 +173,29 @@ msgstr ""
msgid "failed to determine size of image %s"
msgstr ""
-#: ../IkiWiki/Plugin/inline.pm:37
+#: ../IkiWiki/Plugin/inline.pm:39
msgid "Must specify url to wiki with --url when using --rss or --atom"
msgstr ""
-#: ../IkiWiki/Plugin/inline.pm:128
+#: ../IkiWiki/Plugin/inline.pm:130
#, perl-format
msgid "unknown sort type %s"
msgstr ""
-#: ../IkiWiki/Plugin/inline.pm:168
+#: ../IkiWiki/Plugin/inline.pm:170
msgid "Add a new post titled:"
msgstr ""
-#: ../IkiWiki/Plugin/inline.pm:183
+#: ../IkiWiki/Plugin/inline.pm:185
#, perl-format
msgid "nonexistant template %s"
msgstr ""
-#: ../IkiWiki/Plugin/inline.pm:214 ../IkiWiki/Render.pm:103
+#: ../IkiWiki/Plugin/inline.pm:216 ../IkiWiki/Render.pm:103
msgid "Discussion"
msgstr ""
-#: ../IkiWiki/Plugin/inline.pm:425
+#: ../IkiWiki/Plugin/inline.pm:429
msgid "RPC::XML::Client not found, not pinging"
msgstr ""
@@ -277,11 +277,11 @@ msgstr ""
msgid "polygen failed"
msgstr ""
-#: ../IkiWiki/Plugin/postsparkline.pm:25
+#: ../IkiWiki/Plugin/postsparkline.pm:32
msgid "missing formula"
msgstr ""
-#: ../IkiWiki/Plugin/postsparkline.pm:32
+#: ../IkiWiki/Plugin/postsparkline.pm:39
msgid "unknown formula"
msgstr ""
@@ -575,7 +575,7 @@ msgstr ""
msgid "Must specify url to wiki with --url when using --cgi"
msgstr ""
-#: ../IkiWiki.pm:175 ../IkiWiki.pm:176
+#: ../IkiWiki.pm:184 ../IkiWiki.pm:185
msgid "Error"
msgstr ""
@@ -583,7 +583,7 @@ msgstr ""
#. translators: preprocessor directive name,
#. translators: the second a page name, the
#. translators: third a number.
-#: ../IkiWiki.pm:667
+#: ../IkiWiki.pm:676
#, perl-format
msgid "%s preprocessing loop detected on %s at depth %i"
msgstr ""