* Fix --libdir to work at the command line.
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Mon, 13 Aug 2007 03:07:31 +0000 (03:07 +0000)
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Mon, 13 Aug 2007 03:07:31 +0000 (03:07 +0000)
* Plugins can now be written as standalone external programs, which can
  be written in any language that can do XML::RPC.

14 files changed:
IkiWiki.pm
IkiWiki/Plugin/external.pm [new file with mode: 0644]
Makefile.PL
debian/changelog
doc/plugins/contrib.mdwn
doc/plugins/write.mdwn
doc/plugins/write/external.mdwn [new file with mode: 0644]
doc/todo/support_for_plugins_written_in_other_languages.mdwn
doc/usage.mdwn
ikiwiki.in
plugins/externaldemo [new file with mode: 0755]
po/es.po
po/fr.po
po/ikiwiki.pot

index d5a641526b91ad3aff6efb48ae832c151fc1fb9d..7c35ce2b5598a6ad40f8e290337309c9782e87fb 100644 (file)
@@ -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 (file)
index 0000000..e3504a3
--- /dev/null
@@ -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
index 985c944ad4c97a265ffd6b4b4c5783edb884dfd0..0e306ac08ce9bb42fe9a873c742b0de238adc819 100755 (executable)
@@ -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
index 54ef280e3bd66469330d2958ccd2024655365b4d..2b1fbae278b32e4dd5a911677ee94560e68dd151 100644 (file)
@@ -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
 
index dc8b9077139e65f933a2f9f6be40eafd44877071..abdf1bd4e84482ebb6b57ebf32e62ac3ca1cec38 100644 (file)
@@ -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`.
index 416f1b86e63cd8d68ddb0fa9eb34f5de9f5cf6d6..016746abb41ce5befff20b79433dc1bee45344ea 100644 (file)
@@ -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 (file)
index 0000000..735f7a2
--- /dev/null
@@ -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).
+
index 33378a4fe89be83a2d41ff3c21a8991991538491..8476d1b44f8abdb5b9c38e7de7947acdd9de9511 100644 (file)
@@ -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
index 7f556cc95c6a3a0d5911a3a084829da805212ac4..aba213f21f5e7dc52ff13e81246be69f074dd3d5 100644 (file)
@@ -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
 
index c37085bbc1a3884aeda085a7f138f2f8bf11ce6c..6242865ee4206cb7b4a760ed7dc705a5b4c5a07a 100755 (executable)
@@ -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 (executable)
index 0000000..6bbced3
--- /dev/null
@@ -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" }
index da78a0d8aa07bf25bc1bcd1ec1fb5c2da5e7fbd8..3b7c6cd34971296fd8706bf950777cc1758b101a 100644 (file)
--- 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 ""
index 2705758db9735bb2245a28108aa1f02e62e7c970..8525577e4d9c701136c4214b9b2403ad60569ea3 100644 (file)
--- 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 ""
index 31a3430c4b93ebf2543e991f866a665934216de3..b4a3b70ec46ffc681a60aaf2c43a45f9169b2aa5 100644 (file)
@@ -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 ""