RevBank::Plugins
Plugin mechanism for RevBank
Description
RevBank itself consists of a simple command line interface and a really brain dead shopping cart. All transactions, even deposits and withdrawals, are handled by plugins.
Plugins are defined in the plugins file in the REVBANK_DATADIR. Each
plugin is a Perl source file.
In the plugins file, paths can either:
-
not contain
/for files in
REVBANK_PLUGINDIR(which by defaults toplugins/in the directory that has therevbankexecutable).This is typically used for the "core" plugins that ship with RevBank.
-
begin with
~/for paths relative to the
HOMEdirectory. -
begin with
/for absolute paths.
-
contain but not begin with
/for paths relative to
REVBANK_DATADIR(which defaults to~/.revbank).
Plugins are always iterated over in the order they were defined in. The filename, regardless of which directory it's in, has to be unique.
The Perl namespace for each plugin is RevBank::Plugin::x, where x is the
filename without the directory name.
Methods
RevBank::Plugins::load
Reads the plugins file and load the plugins.
RevBank::Plugins->new
Returns a list of fresh plugin instances.
RevBank::Plugins::register($package)
Registers a plugin.
RevBank::Plugins::call_hooks($hook, @arguments)
Calls the given hook in each of the plugins. Non-standard hooks, called only
by plugins, SHOULD be prefixed with the name of the plugin, and an underscore.
For example, a plugin called cow can call a hook called cow_moo (which
calls the hook_cow_moo methods).
There is no protection against infinite loops. Be careful!
Writing plugins
*** CAUTION ***
It is the responsibility of the PLUGINS to verify and normalize all
input. Behaviour for bad input is UNDEFINED. Weird things could
happen. Always use parse_user() and parse_amount() and test the
outcome for defined()ness. Use the result of the parse_*() functions
because that's canonicalised.
Don't do this:
$entry->add_contra($u, $a, "Bad example");
But do this:
$u = parse_user($u) or return REJECT, "$u: No such user.";
$a = parse_amount($a) or return REJECT, "$a: Invalid amount.";
$entry->add_contra($u, $a, 'Good, except that $a is special in Perl :)');
There are two kinds of plugin methods: input methods and hooks. A plugin may
define one command input method, and can have any number of hooks.
Input methods
Whenever a command is given in the 'outer' loop of revbank, the command
method of the plugins is called until one of the plugins does not return
NEXT. An input method receives three arguments: the plugin
object, the shopping cart, and the given input string. The plugin object
(please call it $self) is temporary but persists as long as your plugin
keeps control. It can be used as a scratchpad for carrying over values from
one method call to the next.
A command method MUST return with one of the following statements:
-
return NEXT;
The plugin declines handling of the given command, and revbank should proceed with the next one.
Input methods other than
commandMUST NOT returnNEXT. -
return REJECT, "Reason";
The plugin decides that the input should be rejected for the given reason. RevBank will either query the user again, or (if there is any remaining input in the buffer) abort the transaction to avoid confusion.
-
return ABORT, "Reason";
-
return ABORT;
The plugin decides that the transaction should be aborted.
-
return ACCEPT;
The plugin has finished processing the command. No other plugins will be called.
-
return "Prompt", $method;
The plugin requires arguments for the command, which will be taken from the input buffer if extra input was given, or else, requested interactively.
The given method, which can be a reference or the name of the method, will be called with the given input.
The literal input string
abortis a hard coded special case, and will never reach the plugin's input methods.
Hooks
Hooks are called at specific points in the processing flow, and MAY introspect the shopping cart. They SHOULD NOT manipulate the shopping cart, but this option is provided anyway, to allow for interesting hacks. If you do manipulate the cart, re-evaluate your assumptions when upgrading!
Hooks SHOULD NOT prompt for input or execute programs that do so.
Hooks are called as class methods. The return value MUST be either ABORT,
which causes the ongoing transaction to be aborted, or a non-reference, which
will be ignored.
Hooks SHOULD have a dummy @ parameter at the end of their signatures,
so they don't break when more information is added
The following hooks are available, with their respective arguments:
-
hook_register($class, $plugin, @)
Called when a new plugin is registered.
-
hook_abort($class, $cart, @)
Called when a transaction is being aborted, right before the shopping cart is emptied.
-
hook_prompt($class, $cart, $prompt, @)
Called just before the user is prompted for input interactively. The prompt MAY be altered by the plugin.
-
hook_input($class, $cart, $input, $split_input, @)
Called when user input was given.
$split_inputis a boolean that is true if the input will be split on whitespace, rather than treated as a whole. The input MAY be altered by the plugin. -
hook_add($class, $cart, $account, $item, @)
Called when something is added to the cart. Of course, like in
$cart->add,$accountwill be undef if the product is added for the current user.$itemis a reference to a hash with the keysamount,descriptionand the metadata given in theaddcall. Changing the values changes the actual item going into the cart!Be careful to avoid infinite loops if you add new stuff.
-
hook_checkout_prepare($class, $cart, $account, $transaction_id, @)
Called when the transaction is about to be processed. In this phase, the cart and its entries can still be manipulated. If the hook throws an exception, the transaction is aborted.
-
hook_checkout($class, $cart, $account, $transaction_id, @)
Called when the transaction is finalized, before accounts are updated. The cart and cart entries must not be changed.
-
hook_checkout_done($class, $cart, $account, $transaction_id, @)
Called when the transaction is finalized, after accounts were updated.
-
hook_reject($class, $plugin, $reason, $abort, @)
Called when input is rejected by a plugin.
$abortis true when the transaction will be aborted because of the rejection. -
hook_invalid_input($class, $cart, $word, @)
Called when input was not recognised by any of the plugins.
-
hook_plugin_fail($class, $plugin, $error, @)
Called when a plugin fails.
-
hook_account_created($class, $account, @)
Called when a new account was created.
-
hook_account_balance($class, $account, $old, $delta, $new, $transaction_id, @)
Called when an account is updated.
-
hook_products_changed($class, $changes, $mtime, @)
Called after reading a changed products file.
$changesis a reference to an array of[old, new]pairs. For new products,oldwill be undef. For deleted products,newwill be undef.The mtime is the mtime of the products file, not necessarily when the product was changed.
Caveats: Only things that change during runtime cause this hook to be called. When multiple revbank instances are running, each process gets this hook. When the products file is modified externally, the new file is loaded only after user interaction. When a product's primary id changes, it is registered as a deletion and addition, not a change.
Default messages can be silenced by overriding the hooks in
RevBank::Messages. Such a hack might look like:
undef &RevBank::Messages::hook_abort;
sub hook_abort($class, $cart, @) {
print "This message is much better!\n"
}
Utility functions
Several global utility functions are available. See RevBank::Global
Author
Juerd Waalboer #####@juerd.nl
License
Pick your favorite OSI license.