From: joey Date: Mon, 13 Aug 2007 17:22:15 +0000 (+0000) Subject: improved docs, add a tutorial for writing plugins X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/commitdiff_plain/75a11e6a8d92f7ff221eb4b1270526d57ef537bc improved docs, add a tutorial for writing plugins --- diff --git a/doc/plugins.mdwn b/doc/plugins.mdwn index 3f154f1f0..318411133 100644 --- a/doc/plugins.mdwn +++ b/doc/plugins.mdwn @@ -4,9 +4,9 @@ plugins are included with ikiwiki. [[pagestats pages="plugins/type/* and !plugins/type/slow"]] There's documentation if you want to [[write]] your own plugins, or you can -install and use plugins [[contributed|contrib]] by others. +[[install]] plugins [[contributed|contrib]] by others. -To enable plugins, use the `--plugin` switch described in +To enable a plugin, use the `--plugin` switch described in [[usage]], or the equivalent `add_plugins` line in [[ikiwiki.setup]]. Enable the [[goodstuff]] plugin to get a nice selection of plugins that will fit most uses of ikiwiki. @@ -14,6 +14,6 @@ will fit most uses of ikiwiki. ## Plugin directory [[inline pages="plugins/* and !plugins/type/* and !plugins/write and -!plugins/contrib and !*/Discussion" +!plugins/contrib and !plugins/install and !*/Discussion" feedpages="created_after(plugins/graphviz)" archive="yes" rootpage="plugins/contrib" postformtext="Add a new plugin named:" show=0]] diff --git a/doc/plugins/contrib.mdwn b/doc/plugins/contrib.mdwn index abdf1bd4e..d0b3fa750 100644 --- a/doc/plugins/contrib.mdwn +++ b/doc/plugins/contrib.mdwn @@ -1,24 +1,7 @@ Contributed [[plugins]]: +(See [[install]] for installation help.) + [[inline pages="plugins/contrib/* !*/Discussion" feedpages="created_after(plugins/contrib/navbar)" archive="yes" rootpage="plugins/contrib" postformtext="Add a new plugin named:" show=0]] - -# Installing third party plugins - -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` - -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/install.mdwn b/doc/plugins/install.mdwn new file mode 100644 index 000000000..aa1c932bc --- /dev/null +++ b/doc/plugins/install.mdwn @@ -0,0 +1,19 @@ +[[meta title="Installing third party plugins"]] + +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` + +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 under that directory, for example in `/home/you/.ikiwiki/plugins`. +Note that this type of plugin has to be executable for ikiwiki to use it. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 016746abb..3030d6558 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -1,3 +1,12 @@ +Ikiwiki's plugin interface allows all kinds of useful [[plugins]] to be +written to extend ikiwiki in many ways. Despite the length of this page, +it's not really hard. This page is a complete reference to everything a +plugin might want to do. There is also a quick [[tutorial]]. + +[[toc levels=2]] + +## Types of plugins + 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 @@ -22,8 +31,6 @@ 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]] - ## Considerations One thing to keep in mind when writing a plugin is that ikiwiki is a wiki diff --git a/doc/plugins/write/tutorial.mdwn b/doc/plugins/write/tutorial.mdwn new file mode 100644 index 000000000..d4b4959fa --- /dev/null +++ b/doc/plugins/write/tutorial.mdwn @@ -0,0 +1,189 @@ +This tutorial will walk you through [[writing|write]] your first ikiwiki +plugin. + +What should the plugin do? Let's make it calculate and output the Fibonachi +sequense. To output the next number in the sequence, all a user has to do +is write this on a wiki page: + + \[[fib ]] + +When the page is built, that'll be replaced by the next number in the +sequence. + +Most of ikiwiki's plugins are written in perl, and it's currently easiest +to write them in perl. So, open your favorite text editor, and start +editing a file named "fib.pm". + + #!/usr/bin/perl + +This isn't really necessary, since fib.pm will be a perl module, but it's +nice to have. Since it's a module, the next bit is this. Notice the "fib" +at the end, matching the "fib" in the filename. + + package IkiWiki::Plugin::fib; + +Now let's import a few modules. Warnings and strict are good ideas, but the +important one is the IkiWiki module. + + use warnings; + use strict; + use IkiWiki 2.00; + +Ok, boilerplate is out of the way. Now to add the one function that ikiwiki +expects to find in any module: `import`. The import function is called when +the module is first loaded, and what modules typically do with it is +register hooks that ikiwiki will call later. + + sub import { + hook(type => "preprocess", id => "fib", call => \&preprocess); + } + +This has hooked our plugin into the preprocess hook, which ikiwiki uses to +expand [[PreprocessorDirectives|preprocessordirectives]]. Notice that "fib" +has shown up again. It doesn't actually have to match the module name this +time, but it generally will. This "fib" is telling ikiwiki what kind of +PreprocessorDirective to handle, namely one that looks like this: + + \[[fib ]] + +Notice the `\&preprocess`? This is how you pass a reference to a function, +and the `preprocess` function is the one that ikiwiki will call to expand +the PreprocessorDirective. So, time to write that function: + + sub preprocess { + my %params=@_; + return 1; + } + +Whatever this function returns is what will show up on the wiki page. +Since this is the Fibonachi sequence, returning 1 will be right for the +first two calls anways, so our plugin isn't _too_ buggy. ;-) Before we fix +the bug, let's finish up the plugin. + + 1 + +Always put this as the last line in your perl modules. Perl likes it. + +Ok, done! If you save the plugin, you can copy it to a place your ikiwiki +looks for plugins (`/usr/share/perl5/IkiWiki/Plugins/` is a good bet; see +[[plugin/install]] for the details of how to figure out where to +install it). Then configure ikiwiki to use the plugin, and you're ready to +insert at least the first two numbers of the Fibonachi sequence on web +pages. Behold, the power of ikiwiki! ... + +---- + +You could stop here, if you like, and go write your own plugin that does +something more useful. Rather than leave you with a broken fib plugin +though, this tutorial will go ahead and complete it. Let's add a simple +Fibonachi generating function to the plugin. This is right out of a +textbook. + + sub fib { + my $num=shift; + return 0 if $num == 1; + return 1 if $num == 2; + return fib($num - 1) + fib($num - 2); + } + +And let's change the `preprocess` sub to use it: + + my $last=0; + + sub preprocess { + my %params=@_; + my $num=$last++; + return fib($num); + } + +Feel free to try it out with a simple page like this: + + \[[fib ]], \[[fib ]], \[[fib ]], \[[fib ]], \[[fib ]] + +Looks like it works ok, doesn't it? That creates a page that lists: + + 1, 1, 3, 5, 8 + +But what happens if there are two pages that both use fib? Try it out. +If ikiwiki builds both pages in one pass, the sequence will continue +counting up from one page to the next. But if that second page is modified +later and needs to be rebuilt, the sequence will start over from 1. This is +because `$last` only remembers what was output during the current +ikiwiki run. + +But that's not the end of the strange behavior. Create a page that inlines +a page that uses fib. Now the inlined page will have one set of numbers, +and the standalone page another. The numbers might even skip over part of +the sequence in some cases. + +Obviously, using a global `$last` veriable was a bad idea. It would +work ok in a more regular cgi-based wiki, which only outputs one page per +run. But since ikiwiki is a wiki *compiler*, things are a bit more +complicated. It't not very hard to fix, though, if we do want the seqense +to start from 1 in every page that uses it. + + my %last; + + sub preprocess { + my %params=@_; + my $page=$params{destpage}; + my $num=$last{$page}++; + return fib($num); + } + +All this is doing is using a hash to store the last number on a per-page +basis. To get the name of the page that's being built, it looks in the +`%params` hash. + +Ok, one more enhancement. Just incrementing the numbers is pretty boring. +It would be nice to be able to jump directly to a given point in the +sequence: + + \[[fib seed=20]], [[fib ]], [[fib ]] + +Just insert these lines of code inside `preprocess`, in the appropriate +spot: + + if (exists $params{seed}) { + $last{$page}=$params{seed}-1; + } + +But this highlights another issue with the plugin. The `fib()` function is +highly recursive and gets quite slow for large numbers. If a user enters +seed=1000, it will run for a very long time, blocking ikiwiki from +finishing. This denial of service attack also uses an ever-increasing +amount of memory due to all the recursion. + +Now, we could try to fix `fib()` to run in constant time for any number, +but that's not the focus of this tutorial. Instead, let's concentrate on +making the plugin use the existing function safely. A good first step would +be a guard on how high it will go. + + my %last; + + sub preprocess { + my %params=@_; + my $page=$params{destpage}; + if (exists $params{seed}) { + $last{$page}=$params{seed}-1; + } + my $num=$last{$page}++; + if ($num > 25) { + return "[[fib will only calculate the first 25 numbers in the sequence]]"; + } + return fib($num); + } + +Returning an error message like this is standard for preprocessor plugins, +so that the user can look at the built page and see what went wrong. + +Are we done? Nope, there's still a security hole. Consider what `fib()` +does for numbers less than 1. Or for any number that's not an integer. In +either case, it will run forever. Here's one way to fix that: + + if (int($num) != $num || $num < 1) { + return "[[fib positive integers only, please]]"; + } + +As these security problems have demonstrated, even a simple input from the +user needs to be checked thuroughly before being used by an ikiwiki plugin.