|No such thing as a small change|
I used perl's flexible object system in the past to create programs that support plug-ins. This can be a very powerful feature, depending on how your program is meant to be used.
Recently, this subject came to my mind again when I participated in the Movable Type plugin contest. So I thought, why not let everybody know what can be done with perl to write pluggable programs? I wrote this tutorial, which is meant for people with some understanding of perl and packages, and I'd like to know what you think before I suggest it to the gods.
Well, here goes:
Writing Plugable Programs With Perl
A plugin is just a way for people to extend your software without you having to care, or even know about it. It allows you to write an infrastructure over which people will have the freedom to do what they need. A good, well known example of plugins use is in web browsers (such as Mozilla) that just can't display every kind of content that exists in the web, so they allow plugins to do this for them.
The example program: complain.pl
In this tutorial we will attempt to solve a problem that we all faced one time or another, after spending a lot of time reading posts: How can we prove our superiority to the highest extent possible (find problems in the code) while wasting as little time and effort as possible?
The example program, complain.pl, will do this for you. It'll do more than that. It'll handle complain-fasion. You know, today you complain about 'use strict', tommorow it's efficiency, who knows what next? So for this purpose, finding a new reason to complain will only involve adding a new plugin to complain.pl - a plugin that somebody must have written already in a lively comunity such as Perl Monks!
so, let's introduce our program:
What is a perl plug-in?
Essentialy, a plugin is just a perl package that complies to certain rules declared by the author of the pluggable program. These rules could be as simple as: "Sit in the plugins/ directory", "Put a line describing yourself in /etc/myprog.conf" or quite complex.
For our program, we'll decide that plugins reside in the plugins/ directory, and that's it. We'll also take a shortcut and assume that this directory in below the current directory. Doing the right thing here is left as an excersize...
Loading a plugin at runtime
Perl has a way of loading packages selectively on runtime: the 'require' keyword. unlike the familiar 'use', which is unconditional, happens before the program starts, and dies if it can't happen, require allows you much more flexibility. So you could write:
And this is what we'll do in our programs:
So we required the plugins and filtered out those who for some reason misbehaved. Which brings us to an important point: You can't let a plugin fail your entire program. That's why we used 'eval' here.
What require really doesrequire() is very similar to use(). They both let you include a module in this form:
Except for the differences I already mentioned, there's one more important difference, in how the two are called:
As you see, require takes an expression. So we can put in, for example, require "plugins/$_.pl". If you use the form require Some::Module, then Some::Module is a bareword that is translated to a string.
Declaring the plugin interface
A plugin is only usable if the main program knows how to call it. So it is important to declare the way your plugin is used, so the plugin author could implement the required behaveiour.
For example, say we want our program to allow the plugins to affect it's initialization process. We'll declare that the plugins should implement an 'init' subroutine, and do this in our main program:
This is sometimes called 'program events' that the plugin should react to.
For our program, we'll just execute the 'evaluate' method:
You'll notice that there's a problem here: What if a plugin does not implement a certain feature? This will certainly die with "Can't call bal bla bla..." For 'evaluate' this isn't much of a problem, since if a plugin does not implement it, we'll drop him from our command line options. But say we wanted to let plugins declare their version on init time?
'can' to the rescue
You don't want to allow one misbehaving plugin to ruin your entire program, so we'll use a mechanism built into perl that allows us to check if a package can handle a certain method or not:
The UNIVERSAL package is the 'top object' in perl, but it's also useful for dealing with simple packages, like we did here. the 'can' method of UNIVERSAL is what did the checking for us.
Now it keeps quiet.
Now we know what we expect our plugins to do and maybe we've even written some doccumentaion for others. Now let's write one. The 'bench' plugin will tell us exactly how slow a program is.
You can try it now:
Comunication between the plugin and the program
That print statement above must have caused you to scream to high heavens. A plugin should not decide on where and how to output its results!
OK, we need a way for the plugin to communicate the results back to the program. In our program, we'll keep a list of results, and declare the addOpinion subroutine for use by our plugin.
But we need to make our plugin be able to find this method. So what we'll do, we'll create a 'context object' - an object that is passed to the plugin and tells it useful stuff about the using program.
We'll add this simple package to the top of complain.pl:
Now we'll just change the way we issue program events, so that a context object will be passed to the program. This is the new code, that we shall put right after the modules are loaded:
And we'll fix the 'bench' plug-in to take advantage of the new and improved API :)
Wrapping it up
Now all we have to do is use the opinions expressed by our complaining plug-ins to generate a report. We'll add this final part to our program, in which we use the context object to check what happened:
Just for fun: A plugin that checks if you used strictThis is the most fasionable complaint. Let's write a simple checker for this:
Note that this plug-in makes use of declareVersion, which is very considerate.
And that's all there's to it
Of course, this is just the most basic program I could think of. Better implementations will probably do a lot of things different, like creating a plugin object that plugins must inherit from, or do other fancy object-oriented stuff. But this is not the place for it.
You might want to take a look at the plugin interface of MovableType.
If anybody knows other good examples, please share.
My public key