222 lines
7.4 KiB
Text
222 lines
7.4 KiB
Text
=head1 NAME
|
|
|
|
RevBank::Plugins - Plugin mechanism for RevBank
|
|
|
|
=head1 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 C<revbank.plugins> file. Each plugin is a Perl
|
|
source file in the C<plugins> directory. Plugins are always iterated over in
|
|
the order they were defined in.
|
|
|
|
=head2 Methods
|
|
|
|
=head3 RevBank::Plugins::load
|
|
|
|
Reads the C<revbank.plugins> file and load the plugins.
|
|
|
|
=head3 RevBank::Plugins->new
|
|
|
|
Returns a B<list> of fresh plugin instances.
|
|
|
|
=head3 RevBank::Plugins::register($package)
|
|
|
|
Registers a plugin.
|
|
|
|
=head3 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 C<cow> can call a hook called C<cow_moo> (which
|
|
calls the C<hook_cow_moo> methods).
|
|
|
|
There is no protection against infinite loops. Be careful!
|
|
|
|
=head1 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 C<command> input method, and can have any number of hooks.
|
|
|
|
=head2 Input methods
|
|
|
|
Whenever a command is given in the 'outer' loop of revbank, the C<command>
|
|
method of the plugins is called until one of the plugins returns either
|
|
C<ACCEPT> or C<DONE>. An input method receives three arguments: the plugin
|
|
object, the shopping cart, and the given input string. The plugin object
|
|
(please call it C<$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:
|
|
|
|
=over 10
|
|
|
|
=item return NEXT;
|
|
|
|
The plugin declines handling of the given command, and revbank should proceed
|
|
with the next one.
|
|
|
|
Input methods other than C<command> MUST NOT return C<NEXT>.
|
|
|
|
=item 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.
|
|
|
|
=item return ABORT, "Reason";
|
|
|
|
=item return ABORT;
|
|
|
|
The plugin decides that the transaction should be aborted.
|
|
|
|
=item return ACCEPT;
|
|
|
|
The plugin has finished processing the command. No other plugins will be called.
|
|
|
|
=item 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 C<abort> is a hard coded special case, and will
|
|
never reach the plugin's input methods.
|
|
|
|
=back
|
|
|
|
=head2 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 C<ABORT>,
|
|
which causes the ongoing transaction to be aborted, or a non-reference, which
|
|
will be ignored.
|
|
|
|
Hooks SHOULD have a dummy C<@> 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:
|
|
|
|
=over 10
|
|
|
|
=item hook_register($class, $plugin, @)
|
|
|
|
Called when a new plugin is registered.
|
|
|
|
=item hook_abort($class, $cart, @)
|
|
|
|
Called when a transaction is being aborted, right before the shopping cart is
|
|
emptied.
|
|
|
|
=item hook_prompt($class, $cart, $prompt, @)
|
|
|
|
Called just before the user is prompted for input interactively. The prompt
|
|
MAY be altered by the plugin.
|
|
|
|
=item hook_input($class, $cart, $input, $split_input, @)
|
|
|
|
Called when user input was given. C<$split_input> is 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.
|
|
|
|
=item hook_add($class, $cart, $user, $item, @)
|
|
|
|
Called when something is added to the cart. Of course, like in C<< $cart->add
|
|
>>, C<$user> will be undef if the product is added for the current user.
|
|
|
|
C<$item> is a reference to a hash with the keys C<amount>, C<description> and
|
|
the metadata given in the C<add> call. Changing the values changes the actual
|
|
item going into the cart!
|
|
|
|
Be careful to avoid infinite loops if you add new stuff.
|
|
|
|
=item hook_checkout_prepare($class, $cart, $user, $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.
|
|
|
|
=item hook_checkout($class, $cart, $user, $transaction_id, @)
|
|
|
|
Called when the transaction is finalized, before accounts are updated. The cart and cart entries must not be changed.
|
|
|
|
=item hook_checkout_done($class, $cart, $user, $transaction_id, @)
|
|
|
|
Called when the transaction is finalized, after accounts were updated.
|
|
|
|
=item hook_reject($class, $plugin, $reason, $abort, @)
|
|
|
|
Called when input is rejected by a plugin. C<$abort> is true when the
|
|
transaction will be aborted because of the rejection.
|
|
|
|
=item hook_invalid_input($class, $cart, $word, @)
|
|
|
|
Called when input was not recognised by any of the plugins.
|
|
|
|
=item hook_plugin_fail($class, $plugin, $error, @)
|
|
|
|
Called when a plugin fails.
|
|
|
|
=item hook_user_created($class, $username, @)
|
|
|
|
Called when a new user account was created.
|
|
|
|
=item hook_user_balance($class, $username, $old, $delta, $new, $transaction_id, @)
|
|
|
|
Called when a user account is updated.
|
|
|
|
=item hook_products_changed($class, $changes, $mtime, @)
|
|
|
|
Called after reading a changed products file. C<$changes> is a reference to an array of C<[old, new]> pairs. For new products, C<old> will be undef. For deleted products, C<new> will 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.
|
|
|
|
=back
|
|
|
|
Default messages can be silenced by overriding the hooks in
|
|
C<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"
|
|
}
|
|
|
|
=head2 Utility functions
|
|
|
|
Several global utility functions are available. See L<RevBank::Global>
|
|
|
|
=head1 AUTHOR
|
|
|
|
Juerd Waalboer <#####@juerd.nl>
|
|
|
|
=head1 LICENSE
|
|
|
|
Pick your favorite OSI license.
|
|
|