Pyrus plugins: custom commands

Introduction

Custom commands add new functionality to pyrus.phar. An example of a plugin that implements custom commands is the PEAR2_Pyrus_Developer package, which implements the make, package, pickle, and run-phpt commands.

Custom command plugins can implement multiple commands, and are defined by an xml file that is noted in package.xml with the customcommand file role. The XML format is defined and validated by pyrus with the customcommand.xsd XSchema file.

Command Arguments and Options: a brief primer

Commands can have arguments and options. Here is an example of a command with a single argument:

php pyrus.phar install PackageName

The command is install, and the argument is PackageName.

Here is an example of a command with multiple arguments:

php pyrus.phar install PackageName package.xml http://example.com/Foo.tgz

The command is install, and the arguments are PackageName, package.xml and http://example.com/Foo.tgz.

An option is a special argument that is preceded by 1-2 dashes (- or --). Short arguments are single letters preceded by a dash, and long arguments are words preceded by two dashes. Here is an example of a command with both a short and a long option

php pyrus.phar package -p --tar

The command is package, the short option is -p and the long option is --tar. Short options are aliases for long options. For the package command, the -p short option is an alias to the --phar long option.

Options can also accept arguments in 2 formats. If an option accepts an argument, there are two ways of passing that argument. Short options consider the next argument to be their argument:

php pyrus.phar install -r /path/to/packagingroot PackageName

/path/to/packagingroot is the argument to the short option -r, PackageName is the argument to the install command.

Long options require an = sign in between the option and the argument as in:

php pyrus.phar install --packagingroot=/path/to/packagingroot PackageName

/path/to/packagingroot is the argument to the long option --packagingroot, PackageName is the argument to the install command.

Overview of the Custom Command XML Format

Here is a human-readable overview of a custom command's XML definition file. Optional tags are enclosed in [brackets]. If there is a choice of tags, they are separated by a vertical line | and enclosed in parentheses like (<this>|<example>).

<?xml version="1.0" encoding="UTF-8"?>
<commands version="2.0" xmlns="http://pear2.php.net/dtd/customcommand-2.0">
 <command>
  <name>commandname</name>
  <class>CommandClass\Name</class>
  <function>makePackageXml</function>
[ <webfunction>webCommand</webfunction>]
[ <gtkfunction>gtkCommand</gtkfunction>]
[ <autoloadpath>CommandClass</autoloadpath>]
  <summary>Short description of command purpose</summary>
  <shortcut>CN</shortcut>
  <options>
  [<option>
    <name>optionname</name>
    <shortopt>O</shortopt>
    <type>(<bool/>|<string/>|<int/>|<float/>|<counter/>|
           <callback>optionProcessorCallback</callback>|
           <set><value>value1</value><value>value2</value>...</set>)</type>
   [<default>defaultvalue</default>]
    <doc>Short description of option purpose</doc>
   </option>]
  </options>
  <arguments>
  [<argument>
    <name>argname</name>
    <multiple>0</multiple>
    <optional>1</optional>
    <doc>Short argument description</doc>
   </argument>]
  </arguments>
  <doc>
Long description of command usage for help output
  </doc>
 </command>
</commands>

A command need not require or accept any arguments or options, and can accept multiple arguments and multiple options. In addition, a custom command definition XML file may declare multiple commands.

Commands can also implement a shortcut, which is a shorter alias. The upgrade command, for instance, has a shortcut of up.

By convention, all command names of external projects should be prefixed with the vendor name. For instance, if the vendor is Zend Framework, commands should be prefixed with something like zf- or with zend-. An install command would be zf-install.

In addition, all shortcuts from external vendors should be upper-cased and consist of the first letter of the vendor, and the first two letters of the command. zf-install would have a shortcut of Zin. This helps to avoid name collisions between vendors.

Telling Pyrus how to load your command: <class> and <autoloadpath>

This is the same method used for all plugins, and is documented here.

The three frontend command handlers are discussed in the CLI section, Web section, and the Gtk section.

Defining arguments

Pyrus recognizes both optional and required arguments, as well as the ability to specify repeating arguments. The logic is very similar to function signatures in PHP. Each argument is placed by name into an associative array and passed to the function. Note that invalid input is not passed to custom commands, so a command can assume valid input if it is called.

Here is a sample argument definition and the way that Pyrus will handle different valid inputs for the hypothetical foo command:

 <arguments>
  <argument>
   <name>argname</name>
   <multiple>0</multiple>
   <optional>0</optional>
   <doc>required arg</doc>
  </argument>
  <argument>
   <name>argname2</name>
   <multiple>0</multiple>
   <optional>1</optional>
   <doc>optional arg</doc>
  </argument>
  <argument>
   <name>argname3</name>
   <multiple>1</multiple>
   <optional>1</optional>
   <doc>optional arg, multiple</doc>
  </argument>
 </arguments>
php pyrus.phar foo arg1
<?php
function foo($frontend$args$options)
    {
        
// $args = array('argname' => 'arg1')
    
}
?>
php pyrus.phar foo arg1 arg2
<?php
function foo($frontend$args$options)
    {
        
// $args = array('argname' => 'arg1', 'argname2' => 'arg2')
    
}
?>
php pyrus.phar foo arg1 arg2 arg3
<?php
function foo($frontend$args$options)
    {
        
// $args = array('argname' => 'arg1', 'argname2' => 'arg2', 'argname3' => array('arg3'))
    
}
?>
php pyrus.phar foo arg1 arg2 arg3 arg4
<?php
function foo($frontend$args$options)
    {
        
// $args = array('argname' => 'arg1', 'argname2' => 'arg2', 'argname3' => array('arg3', 'arg4'))
    
}
?>

Defining options

Options must define a short and a long option, a type for the option, and a short description of the option for help text.

Options fall into two categories, those that accept arguments, and those that don't. Options that don't accept arguments are of type <bool/> and <counter/>.

The types recognized are:

  • <bool/>
  • <counter/>
  • <string/>
  • <int/>
  • <float/>
  • <callback></callback>
  • <set></set>

<bool/>

A <bool/> option sets its value to TRUE if present, and FALSE if not.

  <options>
   <option>
    <name>nocompatible</name>
    <shortopt>n</shortopt>
    <type><bool/></type>
    <doc>Do not generate package_compatible.xml</doc>
   </option>
  </options>
php pyrus.phar foo
<?php
function foo($frontend$args$options)
    {
        
// $options = array('nocompatible' => false)
    
}
?>
php pyrus.phar foo -n
<?php
function foo($frontend$args$options)
    {
        
// $options = array('nocompatible' => true)
    
}
?>
php pyrus.phar foo --nocompatible
<?php
function foo($frontend$args$options)
    {
        
// $options = array('nocompatible' => true)
    
}
?>

<counter/>

A <counter/> option sets its value to 0 if not present, and to the number of times the option is present if it is. This is the only option type for which multiple occurrences of the option are allowed.

  <options>
   <option>
    <name>verbose</name>
    <shortopt>v</shortopt>
    <type><counter/></type>
    <doc>How verbose to be with output</doc>
   </option>
  </options>
php pyrus.phar foo
<?php
function foo($frontend$args$options)
    {
        
// $options = array('verbose' => 0)
    
}
?>
php pyrus.phar foo -v
<?php
function foo($frontend$args$options)
    {
        
// $options = array('verbose' => 1)
    
}
?>
php pyrus.phar foo --verbose -vv --verbose
<?php
function foo($frontend$args$options)
    {
        
// $options = array('verbose' => 4)
    
}
?>

<string/>

A <string/> option sets its value to the argument passed in if present, or to NULL if not present.

  <options>
   <option>
    <name>name</name>
    <shortopt>n</shortopt>
    <type><string/></type>
    <doc>Name of foo</doc>
   </option>
  </options>
php pyrus.phar foo
<?php
function foo($frontend$args$options)
    {
        
// $options = array('name' => null)
    
}
?>
php pyrus.phar foo -n myname
<?php
function foo($frontend$args$options)
    {
        
// $options = array('name' => 'myname')
    
}
?>
php pyrus.phar foo --name=myname
<?php
function foo($frontend$args$options)
    {
        
// $options = array('name' => 'myname')
    
}
?>

<int/>

An <int/> option sets its value to the integer argument passed in if present, or to NULL if not present.

  <options>
   <option>
    <name>namecount</name>
    <shortopt>n</shortopt>
    <type><int/></type>
    <doc>Number of foo names</doc>
   </option>
  </options>
php pyrus.phar foo
<?php
function foo($frontend$args$options)
    {
        
// $options = array('namecount' => null)
    
}
?>
php pyrus.phar foo -n 3
<?php
function foo($frontend$args$options)
    {
        
// $options = array('namecount' => 3)
    
}
?>
php pyrus.phar foo --namecount=3
<?php
function foo($frontend$args$options)
    {
        
// $options = array('namecount' => 3)
    
}
?>

<float/>

A <float/> option sets its value to the float argument passed in if present, or to NULL if not present.

  <options>
   <option>
    <name>foopercent</name>
    <shortopt>f</shortopt>
    <type><float/></type>
    <doc>Foo percent in decimal notation</doc>
   </option>
  </options>
php pyrus.phar foo
<?php
function foo($frontend$args$options)
    {
        
// $options = array('foopercent' => null)
    
}
?>
php pyrus.phar foo -f .2
<?php
function foo($frontend$args$options)
    {
        
// $options = array('foopercent' => 0.2)
    
}
?>
php pyrus.phar foo --foopercent=0.3
<?php
function foo($frontend$args$options)
    {
        
// $options = array('foopercent' => 0.3)
    
}
?>

<callback></callback>

A <callback/> option calls the specified callback with the value passed in by the user as a string. The callback must be a static method in the same class that implements the command.

  <options>
   <option>
    <name>foocallback</name>
    <shortopt>f</shortopt>
    <type><callback>processfoo</callback></type>
    <doc>Foo date</doc>
   </option>
  </options>
php pyrus.phar foo
<?php
function foo($frontend$args$options)
    {
        
// $options = array('foocallback' => null)
    
}

    static function 
processfoo($value)
    {
        
// $value = null
        
return null;
    }
?>
php pyrus.phar foo -f 2009-12-31
<?php
function foo($frontend$args$options)
    {
        
// $options = array('foocallback' => new DateTime('2009-12-31', new DateTimeZone('UTC')))
    
}

    static function 
processfoo($value)
    {
        
// $value = '2009-12-31';
        
$date = new DateTime($value, new DateTimeZone('UTC'));
        return 
$date;
    }
?>
php pyrus.phar foo --foocallback=2009-12-31
<?php
function foo($frontend$args$options)
    {
        
// $options = array('foocallback' => new DateTime('2009-12-31', new DateTimeZone('UTC')))
    
}

    static function 
processfoo($value)
    {
        
// $value = '2009-12-31';
        
$date = new DateTime($value, new DateTimeZone('UTC'));
        return 
$date;
    }
?>

<set></set>

A <set/> option sets its value to NULL if not present. Otherwise it ensures that the value passed in by the user is within a pre-defined acceptable list of values.

  <options>
   <option>
    <name>numbah</name>
    <shortopt>n</shortopt>
    <type>
     <set>
      <value>one</value>
      <value>two</value>
      <value>three</value>
      <value>four</value>
     </set>
    </type>
    <doc>Numbers less than five</doc>
   </option>
  </options>
php pyrus.phar foo
<?php
function foo($frontend$args$options)
    {
        
// $options = array('numbah' => null)
    
}
?>
php pyrus.phar foo -n four
<?php
function foo($frontend$args$options)
    {
        
// $options = array('numbah' => 'four')
    
}
?>
php pyrus.phar foo --numbah=one
<?php
function foo($frontend$args$options)
    {
        
// $options = array('numbah' => 'one')
    
}
?>

Declaring CLI command method

A class must implement a command-line handler, which should have this method signature:

<?php
function commandhandler($frontend$args$options)
    {
        
// perform command here
    
}
?>

The first argument is the CLI frontend, and can be used for asking the user a question with the ask() method, or passing control to a built-in command. $args is an associative array of arguments. Only required arguments are guaranteed to be present, all optional arguments must be verified as present before using. $options is an associative array of options. All options will be present, those not present will be initialized to NULL.

Options should be accessed using their long names, and arguments using their names (see examples in the documentation sections for arguments and options above).

The ask() method accepts 3 arguments:

  1. string $question the question to ask the user. It should end in a question mark

  2. (optional) array $choices an array of possible answers

  3. (optional) string $default the default answer

Declaring Web command method

Although a Web frontend has not yet been written for Pyrus, the XML command format supports the eventual addition of a Web frontend. A separate method should be used for the Web handling, and is named via the <webfunction> tag.

Declaring GTK command method

Although a GTK frontend has not yet been written for Pyrus, the XML command format supports the eventual addition of a GTK frontend. A separate method should be used for the GTK handling, and is named via the <gtkfunction> tag.