diff options
author | joey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071> | 2007-08-13 17:22:15 +0000 |
---|---|---|
committer | joey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071> | 2007-08-13 17:22:15 +0000 |
commit | 75a11e6a8d92f7ff221eb4b1270526d57ef537bc (patch) | |
tree | 2018639f693f1424750790f038bf2363e7292eab /doc/plugins/write | |
parent | 15f393584a799473fa17dccd53df5fc2dcf694a8 (diff) |
improved docs, add a tutorial for writing plugins
Diffstat (limited to 'doc/plugins/write')
-rw-r--r-- | doc/plugins/write/tutorial.mdwn | 189 |
1 files changed, 189 insertions, 0 deletions
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. |