[commits] [Wiki] created: Doc/Dev/HordeArgvExtend

Jan Schneider jan at horde.org
Mon Feb 18 14:57:27 UTC 2013


jan  Mon, 18 Feb 2013 15:57:27 +0100

Created page: http://wiki.horde.org/Doc/Dev/HordeArgvExtend

+ Horde_Argv

++ Extending Horde_Argv

Since the two major controlling factors in how //Horde_Argv//  
interprets command-line options are the action and type of each  
option, the most likely direction of extension is to add new actions  
and new types.

+++ Adding new types

To add new types, you need to define your own subclass of the  
{{Horde_Argv_Option class}}. This class has a couple of properties  
that define //Horde_Argv//'s types: {{$TYPES}} and {{$TYPE_CHECKER}}.

{{$TYPES}} is a tuple of type names; in your subclass, simply define a  
new tuple {{$TYPES}} that builds on the standard one.

{{$TYPE_CHECKER}} is a dictionary mapping type names to type-checking  
functions. A type-checking function has the following signature:

<code type="php">
foo check_foo(Horde_Argv_Option $option, string $opt, string $value)
</code>

You can name it whatever you like, and make it return any type you  
like (e.g. the hypothetical type foo). The value returned by a  
type-checking function will wind up in the {{Horde_Argv_Values}}  
instance returned by {{Horde_Argv_Parser->parseArgs()}}, or be passed  
to callbacks as the {{$value}} parameter.

Your type-checking function should throw  
{{Horde_Argv_OptionValueException}} if it encounters any problems.  
{{Horde_Argv_OptionValueException}} takes a single string argument,  
which is passed as-is to {{Horde_Argv_Parser}}'s {{parserError()}}  
method, which in turn prepends the program name and the string  
{{"error:"}} and prints everything to stderr before terminating the  
process.

Here's a silly example that demonstrates adding an imaginary  
{{!MyComplex}} option type to parse complex numbers on the command line.

You need to define your type-checker, since it's referred to in the  
{{$TYPE_CHECKER}} class attribute of your {{Horde_Argv_Option}}  
subclass:

<code type="php">
class MyOption extends Horde_Argv_Option
{
     public function __construct()
     {
         $this->TYPES[] = 'complex';
         $this->TYPE_CHECKER['complex'] = 'checkComplex';
     }

     public function checkComplex($option, $opt, $value)
     {
         try {
             return new MyComplex(value);
         } catch (Exception $e) {
             throw new Horde_Argv_OptionValueException(
                 sprintf('option %s: invalid complex value: %s', (opt, value))
             );
         }
     }
}
</code>

That's it! Now you can write a script that uses the new option type  
just like any other //Horde_Argv//-based script, except you have to  
instruct your {{Horde_Argv_Parser}} to use {{!MyOption}} instead of  
{{Horde_Argv_Option}}:

<code type="php">
$parser = new Horde_Argv_Parser(array('optionClass' => 'MyOption'));
$parser->addOption('-c', array('type' => 'complex'));
</code>

Alternately, you can build your own option list and pass it to  
{{Horde_Argv_Parser}}; if you don't use {{addOption()}} in the above  
way, you don't need to tell {{Horde_Argv_Parser}} which option class  
to use:

<code type="php">
$option_list = array(
     new MyOption(
         '-c',
         array('action' => 'store', 'type' => 'complex', 'dest' => 'c')
     )
);
parser = new Horde_Argv_Parser(array('optionList' => $option_list));
</code>

+++ Adding new actions

Adding new actions is a bit trickier, because you have to understand  
that //Horde_Argv// has a couple of classifications for actions:

: "store" actions : actions that result in //Horde_Argv// storing a  
value to a property of the current {{Horde_Argv_Values}} instance;  
these options require a {{dest}} attribute to be supplied to the  
{{Horde_Argv_Option}} constructor
: "typed" actions : actions that take a value from the command line  
and expect it to be of a certain type; or rather, a string that can be  
converted to a certain type. These options require a type attribute to  
the {{Horde_Argv_Option}} constructor.

These are overlapping sets: some default "store" actions are  
{{store}}, {{store_const}}, {{append}}, and {{count}}, while the  
default "typed" actions are {{store}}, {{append}}, and {{callback}}.

When you add an action, you need to decide if it's a "store" action, a  
"typed" action, neither, or both. Three class properties of  
{{Horde_Argv_Option}} (or your {{Horde_Argv_Option}} subclass) control  
this:

: {{$ACTIONS}} : all actions must be listed in {{$ACTIONS}}
: {{$STORE_ACTIONS}} : "store" actions are additionally listed here
: {{$TYPED_ACTIONS}} : "typed" actions are additionally listed here

In order to actually implement your new action, you must override  
{{Horde_Argv_Option}}'s {{takeAction()}} method and add a case that  
recognizes your action.

For example, let's add an {{extend}} action. This is similar to the  
standard {{append}} action, but instead of taking a single value from  
the command-line and appending it to an existing list, extend will  
take multiple values in a single comma-delimited string, and extend an  
existing list with them. That is, if {{"--names"}} is an {{extend}}  
option of type {{string}}, the command line

<code>
--names=foo,bar --names blah --names ding,dong
</code>

would result in a list

<code type="php">
array('foo', 'bar', 'blah', 'ding', 'dong')
</code>

Again we define a subclass of {{Horde_Argv_Option}}:

<code type="php">
class MyOption extends Horde_Argv_Option
{
     public function __construct()
     {
         $this->ACTIONS[] = 'extend';
         $this->STORE_ACTIONS[] = 'extend';
         $this->TYPED_ACTIONS[] = 'extend';
     }

     public function takeAction($action, $dest, $opt, $value, $values, $parser)
     {
         if ($action == 'extend') {
             $lvalue = explode(',', $value);
             $values->dest = array_merge($values->ensureValue('dest', array()),
                                         $lvalue);
         } else {
             parent::takeAction($action, $dest, $opt, $value, $values,  
$parser);
         }
     }
}
</code>

Features of note:

* {{extend}} both expects a value on the command-line and stores that  
value somewhere, so it goes in both {{$STORE_ACTIONS}} and  
{{$TYPED_ACTIONS}}
* {{!MyOption::takeAction()}} implements just this one new action, and  
passes control back to {{Horde_Argv_Option::takeAction()}} for the  
standard //Horde_Argv// actions
* {{$values}} is an instance of the {{Horde_Argv_Values}} class, which  
provides the very useful {{ensureValue()}} method. {{ensureValue()}}  
is essentially a getter with a safety valve; it is called as
> {{$values->ensureValue($attr, $value)}}
> If the {{$attr}} property of {{$values}} doesn't exist or is  
> {{null}}, then {{ensureValue()}} first sets it to {{$value}}, and  
> then returns {{$value}}. This is very handy for actions like  
> {{extend}}, {{append}}, and {{count}}, all of which accumulate data  
> in a variable and expect that variable to be of a certain type (an  
> array for the first two, an integer for the latter). Using  
> {{ensureValue()}} means that scripts using your action don't have to  
> worry about setting a default value for the option destinations in  
> question; they can just leave the default as {{null}} and  
> {{ensureValue()}} will take care of getting it right when it's needed.

+++ Other reasons to extend Horde_Argv

Adding new types and new actions are the big, obvious reasons why you  
might want to extend //Horde_Argv//. I can think of at least two other  
areas to play with.

First, the simple one: {{Horde_Argv_Parser}} tries to be helpful by  
calling {{exit()}} when appropriate, i.e. when there's an error on the  
command line or when the user requests help. In the former case, the  
traditional course of letting the script crash with a traceback is  
unacceptable; it will make users think there's a bug in your script  
when they make a command-line error. In the latter case, there's  
generally not much point in carrying on after printing a help message.

If this behaviour bothers you, it shouldn't be too hard to "fix" it.  
You'll have to

# subclass {{Horde_Argv_Parser}} and override {{parserError()}}
# subclass {{Horde_Argv_Option}} and override {{takeAction()}} --  
you'll need to provide your own handling of the {{help}} action that  
doesn't call {{exit()}}

The second, much more complex, possibility is to override the  
command-line syntax implemented by //Horde_Argv//. In this case, you'd  
leave the whole machinery of option actions and types alone, but  
rewrite the code that processes {{argv}}. You'll need to subclass  
{{Horde_Argv_Parser}} in any case; depending on how radical a rewrite  
you want, you'll probably need to override one or all of  
{{parseArgs()}}, {{_processLongOpt()}}, and {{_processShortOpts()}}.

Both of these are left as an exercise for the reader. I have not tried  
to implement either myself, since I'm quite happy with  
//Horde_Argv//'s default behaviour (naturally).

Happy hacking, and don't forget: Use the Source, Luke.



More information about the commits mailing list