In part 1 I introduced the sbcli pluginset. In order to use it, you need to create a point of entry for your users. Here’s a simple script in a file named calc:
#!/usr/bin/php5
<?php
require_once("stickleback/PluginRegistry.php");
$localdir = dirname(__FILE__) . DIRECTORY_SEPARATOR;
$reg = stickleback_PluginRegistry::getInstance();
$reg->addPluginDir( "{$localdir}bundledplugins" );
$reg->addPluginDir( "{$localdir}userplugins" );
$reg->findPlugins();
$argv[0] = "calc";
$runner = new stickleback_Runner( );
$runner->run( array( 'use' => 'sbcli', 'argv' => $argv ) );
?>
I acquire a reference to the stickleback_PluginRepository object and establish two plugin directories: bundledplugins and userplugins. This is a typical configuration, in that it allows both for core and contributed plugins. Of course, in the real world you would need to decide where these directories should reside. PEAR can help you with your own plugin directory. The contributed plugins directory might be controlled from a configuration file.
Having set up a plugin environment, I need to start my invocation somewhere. An sbcli interface consists of a tree of sbcli_cmd objects. The end user’s command-line input is analysed by the sbcli plugin object which invokes the correct sbcli_cmd objects. I pass the user’s input to the sbcli object via a stickleback_Runner object. This is a simple helper that handles the basics of finding the plugin to run, and passing it the argument list.
Why did I change $argv[0]? sbcli employs the user’s input to locate which sbcli_cmd plugins to run. The first element in a command line call might be calc, but it might equally be ./calc or /my/path/calc.
Now that I’m invoking sbcli, I need to make sure that an sbcli_cmd plugin called calc exists for it to find. There are always two aspects to creating a plugin with stickleback. There’s the class, and there’s the metadata. Let’s begin with the class at ./bundledplugins/calccli/Calc.php.
class calccli_Calc extends sbcli_cmdImpl {
function doHandleEvent( sbcli_Event $event, array $args ) {
$event->addOut("welcome to calc\n");
}
}
Command plugins must implement the sbgui_cmd interface (remember that stickleback uses interfaces to define its extension points). sbcli provides a partial implementation, however, in the sbgui_cmdImpl class which itself implements the interface. Unless you want to do something unusual therefore, the easiest way of creating a command is to extend sbgui_cmdImpl.
As you can see, all you’re required to implement is doHandleEvent(). This is where you do your commanding stuff, whatever that may be. Notice that you’re given an sbcli_Event object. Why call its addOut() method rather than simply printing your output? It’s up to you really - printing works fine, but because there may be a pipeline of commands you may need to support undo functionality, should one of the commands in the pipeline fail (cf the Gang of Four’s discussion of the Command pattern). For the purposes of this keep-it-simple example, though it really doesn’t matter which way you go.
No stickleback pluginset or plugin will be recognised without the metadata to be found in an sbplugin.xml file. This pluginset’s file is to be found at ./bundledplugins/calccli/sbplugin.xml. Here it is:
<?xml version="1.0" standalone="yes"?>
<pluginset
name="calccli"
xmlns='http://developer.yahoo.com/xmlns/stickleback/sbconfig'>
<plugin name="calccli" type="calccli_Calc">
<param name="cmd-aliases" value="calc" />
<param name="describe" value="a simple calculator" />
<offers epoint="sbcli_cmd" />
<offers epoint="sbcli_flag" />
<honors plugin="sbcli" epoint="sbcli_cmd" />
</plugin>
</pluginset>
Let’s break this down. Remember a plugin essentially does two things. It extends (<honors>) host plugins and provides (<offers>) extension points for its own extenders. The eponymous plugin for the calccli pluginset (every pluginset must have an eponymous plugin) extends sbcli. CLI command plugins should either extend one another, or extend sbcli. This one, being the root of our CLI application plugs straight into the source. It offers two extension points: sbcli_cmd and sbcli_flag.
The <param> elements are a way of passing information from the sbplugin.xml file to the instantiated plugin. In this case describe is used in usage messages and cmd-aliases lists the commands that the plugin should recognize from user input. You may want to label your command generate, for example, but also accept gen for short. In this case, the name calc is important, because it’s the hard-coded first argument the executable set at $argv[0].
Let’s run the code:
$ ./calc
welcome to calc
There’s more to see though. What if we cause an error by passing our command unexpected input? Here’s what:
$ ./calc kjlj
ERROR: command 'kjlj' not found
usage:calc
a simple calculator
As you can see sbcli automatically generates usage information for you. As you will see in future articles, such messages can be quite complex.
In part 3, I will extend this example to include a subcommand that can actually do stuff.