* $optionHandler = new ezcConsoleInput();
*
* // Register simple parameter -h/--help
* $optionHandler->registerOption( new ezcConsoleOption( 'h', 'help' ) );
*
* // Register complex parameter -f/--file
* $file = new ezcConsoleOption(
* 'f',
* 'file',
* ezcConsoleInput::TYPE_STRING,
* null,
* false,
* 'Process a file.',
* 'Processes a single file.'
* );
* $optionHandler->registerOption( $file );
*
* // Manipulate parameter -f/--file after registration
* $file->multiple = true;
*
* // Register another complex parameter that depends on -f and excludes -h
* $dir = new ezcConsoleOption(
* 'd',
* 'dir',
* ezcConsoleInput::TYPE_STRING,
* null,
* true,
* 'Process a directory.',
* 'Processes a complete directory.',
* array( new ezcConsoleOptionRule( $optionHandler->getOption( 'f' ) ) ),
* array( new ezcConsoleOptionRule( $optionHandler->getOption( 'h' ) ) )
* );
* $optionHandler->registerOption( $dir );
*
* // Register an alias for this parameter
* $optionHandler->registerAlias( 'e', 'extended-dir', $dir );
*
* // Process registered parameters and handle errors
* try
* {
* $optionHandler->process( array( 'example_input.php', '-h' ) );
* }
* catch ( ezcConsoleOptionException $e )
* {
* echo $e->getMessage();
* exit( 1 );
* }
*
* // Process a single parameter
* $file = $optionHandler->getOption( 'f' );
* if ( $file->value === false )
* {
* echo "Parameter -{$file->short}/--{$file->long} was not submitted.\n";
* }
* elseif ( $file->value === true )
* {
* echo "Parameter -{$file->short}/--{$file->long} was submitted without value.\n";
* }
* else
* {
* echo "Parameter -{$file->short}/--{$file->long} was submitted with value '".var_export($file->value, true)."'.\n";
* }
*
* // Process all parameters at once:
* foreach ( $optionHandler->getOptionValues() as $paramShort => $val )
* {
* switch ( true )
* {
* case $val === false:
* echo "Parameter $paramShort was not submitted.\n";
* break;
* case $val === true:
* echo "Parameter $paramShort was submitted without a value.\n";
* break;
* case is_array( $val ):
* echo "Parameter $paramShort was submitted multiple times with value: '".implode(', ', $val)."'.\n";
* break;
* default:
* echo "Parameter $paramShort was submitted with value: '$val'.\n";
* break;
* }
* }
*
*
* @package ConsoleTools
* @version 1.4alpha1
* @mainclass
*
* @property ezcConsoleArguments $argumentDefinition Optional argument definition.
*/
class ezcConsoleInput
{
/**
* Option does not carry a value.
*/
const TYPE_NONE = 1;
/**
* Option takes an integer value.
*/
const TYPE_INT = 2;
/**
* Option takes a string value.
*/
const TYPE_STRING = 3;
/**
* Array of option definitions, indexed by number.
*
* This array stores the ezcConsoleOption objects representing
* the options.
*
* For lookup of an option after its short or long values the attributes
* {@link ezcConsoleInput::$optionShort}
* {@link ezcConsoleInput::$optionLong}
* are used.
*
* @var array(int=>array)
*/
private $options = array();
/**
* Short option names.
*
* Each references a key in {@link ezcConsoleInput::$options}.
*
* @var array(string=>int)
*/
private $optionShort = array();
/**
* Long option names.
*
* Each references a key in {@link ezcConsoleInput::$options}.
*
* @var array(string=>int)
*/
private $optionLong = array();
/**
* Arguments, if submitted, are stored here.
*
* @var array(string)
*/
private $arguments = array();
/**
* Wether the process() method has already been called.
*
* @var bool
*/
private $processed = false;
/**
* Indicates if an option was submitted, that has the isHelpOption flag set.
*
* @var bool
*/
private $helpOptionSet = false;
/**
* Collection of arguments.
*
* @var mixed
*/
protected $properties = array();
/**
* Creates an input handler.
*/
public function __construct()
{
$this->argumentDefinition = null;
}
/**
* Registers the new option $option.
*
* This method adds the new option $option to your option collection. If
* already an option with the assigned short or long value exists, an
* exception will be thrown.
*
* @see ezcConsoleInput::unregisterOption()
*
* @param ezcConsoleOption $option
*
* @return ezcConsoleOption The recently registered option.
*/
public function registerOption( ezcConsoleOption $option )
{
foreach ( $this->optionShort as $short => $ref )
{
if ( $short === $option->short )
{
throw new ezcConsoleOptionAlreadyRegisteredException( $short );
}
}
foreach ( $this->optionLong as $long => $ref )
{
if ( $long === $option->long )
{
throw new ezcConsoleOptionAlreadyRegisteredException( $long );
}
}
$this->options[] = $option;
$this->optionLong[$option->long] = $option;
if ( $option->short !== "" )
{
$this->optionShort[$option->short] = $option;
}
return $option;
}
/**
* Registers an alias for an option.
*
* Registers a new alias for an existing option. Aliases can
* be used as if they were a normal option.
*
* The alias is registered with the short option name $short and the
* long option name $long. The alias references to the existing
* option $option.
*
* @see ezcConsoleInput::unregisterAlias()
*
* @param string $short
* @param string $long
* @param ezcConsoleOption $option
*
*
* @throws ezcConsoleOptionNotExistsException
* If the referenced option is not registered.
* @throws ezcConsoleOptionAlreadyRegisteredException
* If another option/alias has taken the provided short or long name.
* @return void
*/
public function registerAlias( $short, $long, ezcConsoleOption $option )
{
if ( !isset( $this->optionShort[$option->short] ) || !isset( $this->optionLong[$option->long] ) )
{
throw new ezcConsoleOptionNotExistsException( $option->long );
}
if ( isset( $this->optionShort[$short] ) || isset( $this->optionLong[$long] ) )
{
throw new ezcConsoleOptionAlreadyRegisteredException( isset( $this->optionShort[$short] ) ? "-$short" : "--$long" );
}
$this->optionShort[$short] = $option;
$this->optionLong[$long] = $option;
}
/**
* Registers options according to a string specification.
*
* Accepts a string to define parameters and registers all parameters as
* options accordingly. String definition, specified in $optionDef, looks
* like this:
*
*
* [s:|size:][u:|user:][a:|all:]
*
*
* This string registers 3 parameters:
* -s / --size
* -u / --user
* -a / --all
*
* @param string $optionDef
* @return void
*
* @throws ezcConsoleOptionStringNotWellformedException
* If provided string does not have the correct format.
*/
public function registerOptionString( $optionDef )
{
$regex = '\[([a-z0-9-]+)([:?*+])?([^|]*)\|([a-z0-9-]+)([:?*+])?\]';
// Check string for wellformedness
if ( preg_match( "/^($regex)+$/", $optionDef ) == 0 )
{
throw new ezcConsoleOptionStringNotWellformedException( "Option definition not wellformed: \"$optionDef\"" );
}
if ( preg_match_all( "/$regex/", $optionDef, $matches ) )
{
foreach ( $matches[1] as $id => $short )
{
$option = null;
$option = new ezcConsoleOption( $short, $matches[4][$id] );
if ( !empty( $matches[2][$id] ) || !empty( $matches[5][$id] ) )
{
switch ( !empty( $matches[2][$id] ) ? $matches[2][$id] : $matches[5][$id] )
{
case '*':
// Allows 0 or more occurances
$option->multiple = true;
break;
case '+':
// Allows 1 or more occurances
$option->multiple = true;
$option->type = self::TYPE_STRING;
break;
case '?':
$option->type = self::TYPE_STRING;
$option->default = '';
break;
default:
break;
}
}
if ( !empty( $matches[3][$id] ) )
{
$option->default = $matches[3][$id];
}
$this->registerOption( $option );
}
}
}
/**
* Removes an option.
*
* This function removes an option. All dependencies to that
* specific option are removed completely from every other registered
* option.
*
* @see ezcConsoleInput::registerOption()
*
* @param ezcConsoleOption $option The option object to unregister.
*
* @throws ezcConsoleOptionNotExistsException
* If requesting a not registered option.
* @return void
*/
public function unregisterOption( ezcConsoleOption $option )
{
$found = false;
foreach ( $this->options as $id => $existParam )
{
if ( $existParam === $option )
{
$found = true;
unset( $this->options[$id] );
continue;
}
$existParam->removeAllExclusions( $option );
$existParam->removeAllDependencies( $option );
}
if ( $found === false )
{
throw new ezcConsoleOptionNotExistsException( $option->long );
}
foreach ( $this->optionLong as $name => $existParam )
{
if ( $existParam === $option )
{
unset( $this->optionLong[$name] );
}
}
foreach ( $this->optionShort as $name => $existParam )
{
if ( $existParam === $option )
{
unset( $this->optionShort[$name] );
}
}
}
/**
* Removes an alias to an option.
*
* This function removes an alias with the short name $short and long
* name $long.
*
* @see ezcConsoleInput::registerAlias()
*
* @throws ezcConsoleOptionNoAliasException
* If the requested short/long name belongs to a real parameter instead.
*
* @param string $short
* @param string $long
* @return void
*
* @todo Check if $short and $long refer to the same option!
*/
public function unregisterAlias( $short, $long )
{
foreach ( $this->options as $id => $option )
{
if ( $option->short === $short )
{
throw new ezcConsoleOptionNoAliasException( $short );
}
if ( $option->long === $long )
{
throw new ezcConsoleOptionNoAliasException( $long );
}
}
if ( isset( $this->optionShort[$short] ) )
{
unset( $this->optionShort[$short] );
}
if ( isset( $this->optionLong[$long] ) )
{
unset( $this->optionLong[$long] );
}
}
/**
* Returns the definition object for the option with the name $name.
*
* This method receives the long or short name of an option and
* returns the ezcConsoleOption object.
*
* @param string $name Short or long name of the option (without - or --).
* @return ezcConsoleOption
*
* @throws ezcConsoleOptionNotExistsException
* If requesting a not registered parameter.
*/
public function getOption( $name )
{
$name = $name;
if ( isset( $this->optionShort[$name] ) )
{
return $this->optionShort[$name];
}
if ( isset( $this->optionLong[$name] ) )
{
return $this->optionLong[$name];
}
throw new ezcConsoleOptionNotExistsException( $name );
}
/**
* Process the input parameters.
*
* Actually process the input options and arguments according to the actual
* settings.
*
* Per default this method uses $argc and $argv for processing. You can
* override this setting with your own input, if necessary, using the
* parameters of this method. (Attention, first argument is always the pro
* gram name itself!)
*
* All exceptions thrown by this method contain an additional attribute "option"
* which specifies the parameter on which the error occurred.
*
* @param array(int=>string) $args The arguments
* @return void
*
* @throws ezcConsoleOptionNotExistsException
* If an option that was submitted does not exist.
* @throws ezcConsoleOptionDependencyViolationException
* If a dependency rule was violated.
* @throws ezcConsoleOptionExclusionViolationException
* If an exclusion rule was violated.
* @throws ezcConsoleOptionTypeViolationException
* If the type of a submitted value violates the options type rule.
* @throws ezcConsoleOptionArgumentsViolationException
* If arguments are passed although a parameter disallowed them.
*
* @see ezcConsoleOptionException
*/
public function process( array $args = null )
{
if ( $this->processed )
{
$this->reset();
}
$this->processed = true;
if ( !isset( $args ) )
{
$args = isset( $argv ) ? $argv : isset( $_SERVER['argv'] ) ? $_SERVER['argv'] : array();
}
$i = 1;
while ( $i < count( $args ) )
{
// Equalize parameter handling (long params with =)
if ( substr( $args[$i], 0, 2 ) == '--' )
{
$this->preprocessLongOption( $args, $i );
}
// Check for parameter
if ( substr( $args[$i], 0, 1 ) === '-' && $this->hasOption( preg_replace( '/^-*/', '', $args[$i] ) ) !== false )
{
$this->processOptions( $args, $i );
}
// Looks like parameter, but is not available??
elseif ( substr( $args[$i], 0, 1 ) === '-' && trim( $args[$i] ) !== '--' )
{
throw new ezcConsoleOptionNotExistsException( $args[$i] );
}
// Must be the arguments
else
{
break;
}
}
// Move pointer
isset( $args[$i] ) && $args[$i] == '--' ? ++$i : $i;
if ( $this->helpOptionSet() )
{
// No need to parse arguments
return;
}
$this->processArguments( $args, $i );
$this->checkRules();
}
/**
* Resets all option and argument values.
*
* This method is called automatically by {@link process()}, if this method
* is called twice or more, and may also be used to manually reset the
* values of all registered {@ezcConsoleOption} and {@link
* ezcConsoleArgument} objects.
*/
public function reset()
{
foreach ( $this->options as $option )
{
$option->value = false;
}
if ( $this->argumentDefinition !== null )
{
foreach ( $this->argumentDefinition as $argument )
{
$argument->value = null;
}
}
$this->arguments = array();
}
/**
* Returns true if an option with the given name exists, otherwise false.
*
* Checks if an option with the given name is registered.
*
* @param string $name Short or long name of the option.
* @return bool True if option exists, otherwise false.
*/
public function hasOption( $name )
{
try
{
$param = $this->getOption( $name );
}
catch ( ezcConsoleOptionNotExistsException $e )
{
return false;
}
return true;
}
/**
* Returns an array of all registered options.
*
* Returns an array of all registered options in the following format:
*
* array(
* 0 => ezcConsoleOption,
* 1 => ezcConsoleOption,
* 2 => ezcConsoleOption,
* ...
* );
*
*
* @return array(string=>ezcConsoleOption) Registered options.
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns the values of all submitted options.
*
* Returns an array of all values submitted to the options. The array is
* indexed by the parameters short name (excluding the '-' prefix). The array
* does not contain any parameter, which value is 'false' (meaning: the
* parameter was not submitted).
*
* @param bool $longnames Wheather to use longnames for indexing.
* @return array(string=>mixed)
*/
public function getOptionValues( $longnames = false )
{
$res = array();
foreach ( $this->options as $param )
{
if ( $param->value !== false )
{
$res[( $longnames === true ) ? $param->long : $param->short] = $param->value;
}
}
return $res;
}
/**
* Returns arguments provided to the program.
*
* This method returns all arguments provided to a program in an
* int indexed array. Arguments are sorted in the way
* they are submitted to the program. You can disable arguments
* through the 'arguments' flag of a parameter, if you want
* to disallow arguments.
*
* Arguments are either the last part of the program call (if the
* last parameter is not a 'multiple' one) or divided via the '--'
* method which is commonly used on Unix (if the last parameter
* accepts multiple values this is required).
*
* @return array(int=>string) Arguments.
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Get help information for your options.
*
* This method returns an array of help information for your options,
* indexed by int. Each help info has 2 fields:
*
* 0 => The options names (" / ")
* 1 => The help text (depending on the $long parameter)
*
* The $long options determines if you want to get the short or long help
* texts. The array returned can be used by {@link ezcConsoleTable}.
*
* If using the second options, you can filter the options shown in the
* help output (e.g. to show short help for related options). Provide
* as simple number indexed array of short and/or long values to set a filter.
*
* @param bool $long Set this to true for getting the long help version.
* @param array(int=>string) $params Set of option names to generate help for, default is all.
* @return array(int=>array(int=>string)) Table structure as explained.
*/
public function getHelp( $long = false, array $params = array() )
{
$help = array();
foreach ( $this->options as $id => $param )
{
if ( count( $params ) === 0 || in_array( $param->short, $params ) || in_array( $param->long, $params ) )
{
$help[] = array(
( $param->short !== "" ? '-' . $param->short . ' / ' : "" ) . '--' . $param->long,
$long == false ? $param->shorthelp : $param->longhelp,
);
}
}
if ( $this->argumentDefinition !== null )
{
$help[] = array( "Arguments:", "" );
if ( count( $this->argumentDefinition ) === 0 )
{
$help[] = array( "", "No arguments available." );
}
foreach ( $this->argumentDefinition as $arg )
{
$argSynopsis = "<%s:%s>";
switch ( $arg->type )
{
case self::TYPE_INT:
$type = "int";
break;
case self::TYPE_STRING:
$type = "string";
break;
}
$argSynopsis = sprintf( $argSynopsis, $type, $arg->name );
$help[] = ( $long === true )
? array(
$argSynopsis,
$arg->longhelp . ( $arg->mandatory === false
? ' (optional' . ( $arg->default !== null
? ', default = ' . ( is_array( $arg->default )
? "'" . implode( "' '", $arg->default ) . "'"
: "'$arg->default'"
)
: ''
) . ')'
: ''
)
)
: array( $argSynopsis, $arg->shorthelp );
}
}
return $help;
}
/**
* Get help information for your options as a table.
*
* This method provides the information returned by
* {@link ezcConsoleInput::getHelp()} in a table.
*
* @param ezcConsoleTable $table The table object to fill.
* @param bool $long Set this to true for getting the
* long help version.
* @param array(int=>string) $params Set of option names to generate help
* for, default is all.
* @return ezcConsoleTable The filled table.
*/
public function getHelpTable( ezcConsoleTable $table, $long = false, array $params = array() )
{
$help = $this->getHelp( $long, $params );
$i = 0;
foreach ( $help as $row )
{
$table[$i][0]->content = $row[0];
$table[$i++][1]->content = $row[1];
}
return $table;
}
/**
* Returns a standard help output for your program.
*
* This method generates a help text as it's commonly known from Unix
* command line programs. The output will contain the synopsis, your
* provided program description and the selected parameter help
* as also provided by {@link ezcConsoleInput::getHelp()}. The returned
* string can directly be printed to the console.
*
* @param string $programDesc The description of your program.
* @param int $width The width to adjust the output text to.
* @param bool $long Set this to true for getting the long
* help version.
* @param array(int=>string) $params Set of option names to generate help
* for, default is all.
* @return string The generated help text.
*/
public function getHelpText( $programDesc, $width = 80, $long = false, array $params = null )
{
$help = $this->getHelp( $long, $params == null ? array() : $params );
// Determine max length of first column text.
$maxLength = 0;
foreach ( $help as $row )
{
$maxLength = max( $maxLength, strlen( $row[0] ) );
}
// Width of left column
$leftColWidth = $maxLength + 2;
// Width of righ column
$rightColWidth = $width - $leftColWidth;
$res = 'Usage: ' . $this->getSynopsis( $params ) . PHP_EOL;
$res .= wordwrap( $programDesc, $width, PHP_EOL );
$res .= PHP_EOL . PHP_EOL;
foreach ( $help as $row )
{
$rowParts = explode( "\n", wordwrap( $row[1], $rightColWidth ) );
$res .= sprintf( "%-{$leftColWidth}s", $row[0] );
$res .= $rowParts[0] . PHP_EOL;
for ( $i = 1; $i < sizeof( $rowParts ); $i++ )
{
$res .= str_repeat( ' ', $leftColWidth ) . $rowParts[$i] . PHP_EOL;
}
}
return $res;
}
/**
* Returns the synopsis string for the program.
*
* This gives you a synopsis definition for the options and arguments
* defined with this instance of ezcConsoleInput. You can filter the
* options named in the synopsis by submitting their short names in an
* array as the parameter of this method. If the parameter $optionNames
* is set, only those options are listed in the synopsis.
*
* @param array(int=>string) $optionNames
* @return string
*/
public function getSynopsis( array $optionNames = null )
{
$usedOptions = array( 'short' => array(), 'long' => array() );
$allowsArgs = true;
$synopsis = '$ ' . ( isset( $argv ) && sizeof( $argv ) > 0 ? $argv[0] : $_SERVER['argv'][0] ) . ' ';
foreach ( $this->getOptions() as $option )
{
if ( $optionNames === null || is_array( $optionNames ) && ( in_array( $option->short, $optionNames ) || in_array( $option->long, $optionNames ) ) )
{
$synopsis .= $this->createOptionSynopsis( $option, $usedOptions, $allowsArgs );
}
}
if ( $this->argumentDefinition === null )
{
// Old handling
$synopsis .= " [[--] ]";
}
else
{
$synopsis .= "[--] " . $this->createArgumentsSynopsis();
}
return $synopsis;
}
/**
* Returns if a help option was set.
* This method returns if an option was submitted, which was defined to be
* a help option, using the isHelpOption flag.
*
* @return bool If a help option was set.
*/
public function helpOptionSet()
{
return $this->helpOptionSet;
}
/**
* Property read access.
*
* @throws ezcBasePropertyNotFoundException
* If the the desired property is not found.
*
* @param string $propertyName Name of the property.
* @return mixed Value of the property or null.
* @ignore
*/
public function __get( $propertyName )
{
if ( !isset( $this->$propertyName ) )
{
throw new ezcBasePropertyNotFoundException( $propertyName );
}
return $this->properties[$propertyName];
}
/**
* Property set access.
*
* @param string $propertyName
* @param string $propertyValue
* @ignore
* @return void
*/
public function __set( $propertyName, $propertyValue )
{
switch ( $propertyName )
{
case "argumentDefinition":
if ( ( $propertyValue instanceof ezcConsoleArguments ) === false && $propertyValue !== null )
{
throw new ezcBaseValueException( $propertyName, $propertyValue, "ezcConsoleArguments" );
}
break;
default:
throw new ezcBasePropertyNotFoundException( $propertyName );
}
$this->properties[$propertyName] = $propertyValue;
}
/**
* Property isset access.
*
* @param string $propertyName Name of the property.
* @return bool True is the property is set, otherwise false.
* @ignore
*/
public function __isset( $propertyName )
{
return array_key_exists( $propertyName, $this->properties );
}
/**
* Returns the synopsis string for a single option and its dependencies.
*
* This method returns a part of the program synopsis, specifically for a
* certain parameter. The method recursively adds depending parameters up
* to the 2nd depth level to the synopsis. The second parameter is used
* to store the short names of all options that have already been used in
* the synopsis (to avoid adding an option twice). The 3rd parameter
* determines the actual deps in the option dependency recursion to
* terminate that after 2 recursions.
*
* @param ezcConsoleOption $option The option to include.
* @param array(int=>string) $usedOptions Array of used option short names.
* @param int $depth Current recursion depth.
* @return string The synopsis for this parameter.
*/
protected function createOptionSynopsis( ezcConsoleOption $option, &$usedOptions, $depth = 0 )
{
$synopsis = '';
// Break after a nesting level of 2
if ( $depth++ > 2 || ( in_array( $option->short, $usedOptions['short'] ) && in_array( $option->long, $usedOptions['long'] ) ) ) return $synopsis;
$usedOptions['short'][] = $option->short;
$usedOptions['long'][] = $option->long;
$synopsis .= $option->short !== "" ? "-{$option->short}" : "--{$option->long}";
if ( isset( $option->default ) )
{
$synopsis .= " " . ( $option->type === ezcConsoleInput::TYPE_STRING ? '"' : '' ) . $option->default . ( $option->type === ezcConsoleInput::TYPE_STRING ? '"' : '' );
}
else if ( $option->type !== ezcConsoleInput::TYPE_NONE )
{
$synopsis .= " ";
switch ( $option->type )
{
case ezcConsoleInput::TYPE_STRING:
$synopsis .= "";
break;
case ezcConsoleInput::TYPE_INT:
$synopsis .= "";
break;
}
}
foreach ( $option->getDependencies() as $rule )
{
$deeperSynopsis = $this->createOptionSynopsis( $rule->option, $usedOptions, $depth );
$synopsis .= strlen( trim( $deeperSynopsis ) ) > 0 ? ' ' . $deeperSynopsis : '';
}
if ( $option->arguments === false )
{
$allowsArgs = false;
}
// Make the whole thing optional?
if ( $option->mandatory === false )
{
$synopsis = "[$synopsis]";
}
return $synopsis . ' ';
}
/**
* Generate synopsis for arguments.
*
* @return string The synopsis string.
*/
private function createArgumentsSynopsis()
{
$mandatory = true;
$synopsises = array();
foreach ( $this->argumentDefinition as $arg )
{
$argSynopsis = "";
if ( $arg->mandatory === false )
{
$mandatory = false;
}
$argSynopsis .= "<%s:%s>";
switch ( $arg->type )
{
case self::TYPE_INT:
$type = "int";
break;
case self::TYPE_STRING:
$type = "string";
break;
}
$argSynopsis = sprintf( $argSynopsis, $type, $arg->name );
$synopsises[] = $mandatory === false ? "[$argSynopsis]" : $argSynopsis;
if ( $arg->multiple === true )
{
$synopsises[] = "[$argSynopsis ...]";
break;
}
}
return implode( " ", $synopsises );
}
/**
* Process an option.
*
* This method does the processing of a single option.
*
* @param array(int=>string) $args The arguments array.
* @param int $i The current position in the arguments array.
* @return void
*
* @throws ezcConsoleOptionTooManyValuesException
* If an option that expects only a single value was submitted
* with multiple values.
* @throws ezcConsoleOptionTypeViolationException
* If an option was submitted with a value of the wrong type.
* @throws ezcConsoleOptionMissingValueException
* If an option thats expects a value was submitted without.
*/
private function processOptions( array $args, &$i )
{
$option = $this->getOption( preg_replace( '/^-+/', '', $args[$i++] ) );
// Is the actual option a help option?
if ( $option->isHelpOption === true )
{
$this->helpOptionSet = true;
}
// No value expected
if ( $option->type === ezcConsoleInput::TYPE_NONE )
{
// No value expected
if ( isset( $args[$i] ) && substr( $args[$i], 0, 1 ) !== '-' && sizeof( $args ) > ( $i + 1 ) )
{
// But one found
throw new ezcConsoleOptionTypeViolationException( $option, $args[$i] );
}
// Multiple occurance possible
if ( $option->multiple === true )
{
$option->value[] = true;
}
else
{
$option->value = true;
}
// Everything fine, nothing to do
return $i;
}
// Value expected, check for it
if ( isset( $args[$i] ) && substr( $args[$i], 0, 1 ) !== '-' )
{
// Type check
if ( $this->isCorrectType( $option->type, $args[$i] ) === false )
{
throw new ezcConsoleOptionTypeViolationException( $option, $args[$i] );
}
// Multiple values possible
if ( $option->multiple === true )
{
$option->value[] = $args[$i];
}
// Only single value expected, check for multiple
elseif ( isset( $option->value ) && $option->value !== false )
{
throw new ezcConsoleOptionTooManyValuesException( $option );
}
else
{
$option->value = $args[$i];
}
$i++;
}
// Value found? If not, use default, if available
if ( !isset( $option->value ) || $option->value === false || ( is_array( $option->value ) && count( $option->value ) === 0) )
{
throw new ezcConsoleOptionMissingValueException( $option );
}
return $i;
}
/**
* Process arguments given to the program.
*
* @param array(int=>string) $args The arguments array.
* @param int $i Current index in arguments array.
* @return void
*/
private function processArguments( array $args, &$i )
{
if ( $this->argumentDefinition === null || $this->argumentsAllowed() === false )
{
// Old argument handling, also used of a set option sets disallowing arguments
while ( $i < count( $args ) )
{
$this->arguments[] = $args[$i++];
}
}
else
{
$mandatory = true;
foreach ( $this->argumentDefinition as $arg )
{
// Check if all followinga arguments are optional
if ( $arg->mandatory === false )
{
$mandatory = false;
}
// Check if the current argument is present and mandatory
if ( $mandatory === true )
{
if ( !isset( $args[$i] ) )
{
throw new ezcConsoleArgumentMandatoryViolationException( $arg );
}
}
else
{
// Arguments are optional, if no more left: return.
if ( !isset( $args[$i] ) )
{
// Optional and no more arguments left, assign default
$arg->value = $arg->default;
continue;
}
}
if ( $arg->multiple === true )
{
$arg->value = array();
for ( $i = $i; $i < count( $args ); ++$i )
{
if ( $this->isCorrectType( $arg->type, $args[$i] ) === false )
{
throw new ezcConsoleArgumentTypeViolationException( $arg, $args[$i] );
}
$arg->value = array_merge( $arg->value, array( $args[$i] ) );
// Keep old handling, too
$this->arguments[] = $args[$i];
}
return;
}
else
{
if ( $this->isCorrectType( $arg->type, $args[$i] ) === false )
{
throw new ezcConsoleArgumentTypeViolationException( $arg, $args[$i] );
}
$arg->value = $args[$i];
// Keep old handling, too
$this->arguments[] = $args[$i];
}
++$i;
}
if ( $i < count( $args ) )
{
throw new ezcConsoleTooManyArgumentsException( $args, $i );
}
}
}
/**
* Returns if arguments are allowed with the current option submition.
*
* @return bool If arguments allowed.
*/
protected function argumentsAllowed()
{
foreach ( $this->options as $id => $option )
{
if ( $option->value !== false && $option->arguments === false )
{
return false;
}
}
return true;
}
/**
* Check the rules that may be associated with an option.
*
* Options are allowed to have rules associated for dependencies to other
* options and exclusion of other options or arguments. This method
* processes the checks.
*
* @throws ezcConsoleOptionDependencyViolationException
* If a dependency was violated.
* @throws ezcConsoleOptionExclusionViolationException
* If an exclusion rule was violated.
* @throws ezcConsoleOptionArgumentsViolationException
* If arguments are passed although a parameter dissallowed them.
* @throws ezcConsoleOptionMandatoryViolationException
* If an option that was marked mandatory was not submitted.
* @throws ezcConsoleOptionMissingValueException
* If an option that expects a value was submitted without one.
* @return void
*/
private function checkRules()
{
// If a help option is set, skip rule checking
if ( $this->helpOptionSet === true )
{
return true;
}
$values = $this->getOptionValues();
foreach ( $this->options as $id => $option )
{
// Mandatory
if ( $option->mandatory === true && $option->value === false )
{
throw new ezcConsoleOptionMandatoryViolationException( $option );
}
// Not set and not mandatory? No checking.
if ( $option->value === false || is_array( $option->value ) && count( $option->value ) === 0 )
{
// Default value to set?
if ( $option->default !== null )
{
$option->value = $option->default;
}
// Parameter was not set so ignore it's rules.
continue;
}
// Option was set, so check further on
// Dependencies
foreach ( $option->getDependencies() as $dep )
{
if ( !isset( $values[$dep->option->short] ) || $values[$dep->option->short] === false )
{
throw new ezcConsoleOptionDependencyViolationException( $option, $dep->option );
}
$depVals = $dep->values;
if ( count( $depVals ) > 0 )
{
$checkVals = is_array( $values[$dep->option->short] ) ? $values[$dep->option->short] : array( $values[$dep->option->short] );
foreach ( $checkVals as $checkVal )
{
if ( !in_array( $checkVal, $depVals ) )
{
throw new ezcConsoleOptionDependencyViolationException( $option, $dep->option, implode( ', ', $depVals ) );
}
}
}
}
// Exclusions
foreach ( $option->getExclusions() as $exc )
{
if ( isset( $values[$exc->option->short] ) && $values[$exc->option->short] !== false && count( $exc->values ) === 0 )
{
throw new ezcConsoleOptionExclusionViolationException( $option, $exc->option );
}
if ( count( $exc->values ) > 0 )
{
$checkVals = is_array( $values[$exc->option->short] ) ? $values[$exc->option->short] : array( $values[$exc->option->short] );
foreach ( $checkVals as $checkVal )
{
if ( in_array( $checkVal, $exc->values ) )
{
throw new ezcConsoleOptionExclusionViolationException( $option, $exc->option, $option->value );
}
}
}
}
// Arguments
if ( $option->arguments === false && is_array( $this->arguments ) && count( $this->arguments ) > 0 )
{
throw new ezcConsoleOptionArgumentsViolationException( $option );
}
}
}
/**
* Checks if a value is of a given type. Converts the value to the
* correct PHP type on success.
*
* @param int $type The type to check for. One of self::TYPE_*.
* @param string $val The value to check. Will possibly altered!
* @return bool True on succesful check, otherwise false.
*/
private function isCorrectType( $type, &$val )
{
$res = false;
switch ( $type )
{
case ezcConsoleInput::TYPE_STRING:
$res = true;
$val = preg_replace( '/^(["\'])(.*)\1$/', '\2', $val );
break;
case ezcConsoleInput::TYPE_INT:
$res = preg_match( '/^[0-9]+$/', $val ) ? true : false;
if ( $res )
{
$val = ( int ) $val;
}
break;
}
return $res;
}
/**
* Split parameter and value for long option names.
*
* This method checks for long options, if the value is passed using =. If
* this is the case parameter and value get split and replaced in the
* arguments array.
*
* @param array(int=>string) $args The arguments array
* @param int $i Current arguments array position
* @return void
*/
private function preprocessLongOption( array &$args, $i )
{
// Value given?
if ( preg_match( '/^--\w+\=[^ ]/i', $args[$i] ) )
{
// Split param and value and replace current param
$parts = explode( '=', $args[$i], 2 );
array_splice( $args, $i, 1, $parts );
}
}
}
?>