Horde_Argv Option Callbacks When Horde_Argv's built-in actions and types aren't quite enough for your needs, you have two choices: extend Horde_Argv or define a callback option. Extending Horde_Argv is more general, but overkill for a lot of simple cases. Quite often a simple callback is all you need. You define a callback in two steps: - define the option itself using the callback action - write the callback; this is a function (or method) that takes at least four arguments, as described below Defining a callback option As always, the easiest way to define a callback option is by using the addOption() method of your Horde_Argv_Parser object. The only option attribute you must specify is callback, the function to call:
$parser->addOption('-c', array('action' => 'callback', 'callback' => 'my_callback'));
Note that you supply a callable here -- so you must have already defined a function my_callback() when you define the callback option. In this simple case, Horde_Argv knows nothing about the arguments the "-c" option expects to take. Usually, this means that the option doesn't take any arguments -- the mere presence of "-c" on the command-line is all it needs to know. In some circumstances, though, you might want your callback to consume an arbitrary number of command-line arguments. This is where writing callbacks gets tricky; it's covered later in this document.
Horde_Argv always passes four particular arguments to your callback, and it will only pass additional arguments if you specify them via callback_args and callback_kwargs. Thus, the minimal callback function signature is:
function my_callback($option, $opt, $value, $parser)
The four arguments to a callback are described below.
There are several other option attributes that you can supply when you define an option attribute:
type: has its usual meaning: as with the store or append actions, it instructs Horde_Argv to consume one argument and convert it to type. Rather than storing the converted value(s) anywhere, though, Horde_Argv passes it to your callback function.
func(Horde_Argv_Option $option,
string $opt,
mixed $value,
Horde_Argv_Parser $parser,
array $args,
array $kwargs)
where
$option: is the Horde_Argv_Option instance that's calling the callback
function record_foo_seen($option, $opt, $value, $parser)
{
$parser->saw_foo = true;
}
$parser->addOption(
'--foo',
arry('action' => 'callback', 'callback' => 'record_foo_seen')
);
Of course, you could do that with the store_true action. Here's a slightly more interesting example: record the fact that "-a" is seen, but blow up if it comes after "-b" in the command-line.
$check_order = function($option, $opt, $value, $parser)
{
if ($parser->values->b) {
throw new Horde_Argv_OptionValueException("can't use -a after -b");
}
$parser->values->a = 1;
}
[...]
$parser->addOption(
'-a',
array('action' => 'callback', 'callback' => $check_order)
);
$parser->addOption('-b', array('action' => 'store_true', 'dest' => 'b'));
If you want to re-use this callback for several similar options (set a flag, but blow up if "-b" has already been seen), it needs a bit of work: the error message and the flag that it sets must be generalized.
function check_order($option, $opt, $value, $parser)
{
if ($parser->values->b) {
throw new Horde_Argv_OptionValueException(sprintf("can't use %s after -b", $opt));
}
$parser->values->{$option->dest} = 1;
}
[...]
$parser->addOption(
'-a',
array('action' => 'callback', 'callback' => 'check_order', 'dest' => 'a')
);
$parser->addOption(
'-b',
array('action' => 'store_true', 'dest' => 'b')
);
$parser->addOption(
'-c',
array('action' => 'callback', 'callback' => 'check_order', 'dest' => 'c')
);
Of course, you could put any condition in there -- you're not limited to checking the values of already-defined options. For example, if you have options that should not be called when the moon is full, all you have to do is this:
function check_moon($option, $opt, $value, $parser)
{
if (is_moon_full()) {
throw new Horde_Argv_OptionValueException(sprintf('%s option invalid when moon is full', $opt));
}
$parser->values->{$option->dest} = 1;
}
[...]
$parser->addOption(
'--foo',
array('action' => 'callback', 'callback' => 'check_moon', 'dest' => 'foo')
);
(The definition of is_moon_full() is left as an exercise for the reader.)
Examples part 2: fixed arguments
Things get slightly more interesting when you define callback options that take a fixed number of arguments. Specifying that a callback option takes arguments is similar to defining a store or append option: if you define type, then the option takes one argument that must be convertible to that type; if you further define nargs, then the option takes nargs arguments.
Here's an example that just emulates the standard store action:
function store_value($option, $opt, $value, $parser)
{
$parser->values->{$option->dest} = $value;
}
[...]
$parser->addOption(
'--foo',
array('action' => 'callback', 'callback' => 'store_value',
'type' => 'int', 'nargs' => 3, 'dest' => 'foo')
);
Note that Horde_Argv takes care of consuming 3 arguments and converting them to integers for you; all you have to do is store them. (Or whatever: obviously you don't need a callback for this example. Use your imagination!)
Examples part 3: variable arguments
Things get hairy when you want an option to take a variable number of arguments. For this case, you must write a callback, as Horde_Argv doesn't provide any built-in capabilities for it. And you have to deal with certain intricacies of conventional Unix command-line parsing that Horde_Argv normally handles for you. In particular, callbacks have to worry about bare "--" and "-" arguments; the convention is:
- bare "--", if not the argument to some option, causes command-line processing to halt and the "--" itself is lost
- bare "-" similarly causes command-line processing to halt, but the "-" itself is kept
- either "--" or "-" can be option arguments
If you want an option that takes a variable number of arguments, there are several subtle, tricky issues to worry about. The exact implementation you choose will be based on which trade-offs you're willing to make for your application (which is why Horde_Argv doesn't support this sort of thing directly).
Nevertheless, here's a stab at a callback for an option with variable arguments:
function vararg_callback($option, $opt, $value, $parser)
{
$done = 0;
$value = array();
$rargs = $parser->rargs;
while ($rargs) {
$arg = $rargs[0];
// Stop if we hit an $arg like '--foo', '-a', '-fx', '--file=f',
// etc. Note that this also stops on '-3' or '-3.0', so if
// your option takes numeric values, you will need to handle
// this.
if ((substr($arg, 0, 2) == '--' && strlen($arg) > 2) ||
($arg[0] == '-' && strlen($arg) > 1 && $arg[1] != '-')) {
break;
} else {
$value[] = $arg;
}
array_shift($rargs);
}
$parser->values->{$option->dest} = $value;
}
[...]
$parser->addOption(
'-c', '--callback',
array('action' => 'callback', 'callback' => 'vararg_callback')
);
The main weakness with this particular implementation is that negative numbers in the arguments following "-c" will be interpreted as further options, rather than as arguments to "-c". Fixing this is left as an exercise for the reader.