<?php
/**
* Filters an array and extracts and validates command line arguments
*
* @author Patrick Forget <patforg at webtrendi.com>
*/
namespace Clapp;
/**
* Filters an array and extracts and validates command line arguments
*
* @author Patrick Forget <patforg at webtrendi.com>
*/
class CommandArgumentFilter
{
/**
* Command line arguments
* @var array
*/
private $arguments = array();
/**
* Definition of allowed parameters
* @var \Clapp\CommandLineArgumentDefinition
*/
private $definitions = null;
/**
* Flag if arguments have been parsed in to params
* @var boolean
*/
private $parsed = false;
/**
* Parsed params
* @var array
*/
private $params = array();
/**
* Trailing values
* @var string
*/
private $trailingValues = "";
/**
* program name
* @var string
*/
private $programName = "";
/**
* class constructor
*
* @author Patrick Forget <patforg at webtrendi.com>
*
* @param \Clapp\CommandLineDefinition $definitions contains list of allowed parameters
* @param array $args list of arguments to filter.
*/
public function __construct(\Clapp\CommandLineArgumentDefinition $definitions, $args)
{
if (is_array($args)) {
$this->arguments = $args;
} //if
$this->definitions = $definitions;
} // __construct()
/**
* returns parameter matching provided name
*
* @author Patrick Forget <patforg at webtrendi.com>
*
* @param string name of the paramter to retreive
*
* @return mixed if param the param appears only once the method will
* return 1 if the parameter doesn't take a value. The specified value
* for that param will returned if it does take value.
*
* If many occurence of the param appear the number of occurences will
* be returned for params that do not take values. An array of values
* will be returned for the parameters that do take values.
*
* If the parameter is not present null if it takes a value and false if
* it's not present and doesn't allow values
*/
public function getParam($name)
{
if (!$this->parsed) {
$this->parseParams();
} //if
$longName = strlen($name) === 1 ? $this->definitions->getLongName($name) : $name;
if (isset($this->params[$longName])) {
return $this->params[$longName];
} else {
if ($this->definitions->allowsValue($longName)) {
return null;
} else {
return false;
} //if
} //if
} // getParam()
/**
* retreive the program name
*
* @author Patrick Forget <patforg at webtrendi.com>
*/
public function getProgramName()
{
if (!$this->parsed) {
$this->parseParams();
} //if
return $this->programName;
} // getProgramName()
/**
* retreive the trailing values
*
* @author Patrick Forget <patforg at webtrendi.com>
*/
public function getTrailingValues()
{
if (!$this->parsed) {
$this->parseParams();
} //if
return $this->trailingValues;
} // getTrailingValues()
/**
* extracts params from arguments
*
* @author Patrick Forget <patforg at webtrendi.com>
*/
protected function parseParams()
{
$argumentStack = $this->arguments;
$expectingValue = false;
$currentLongName = null;
$currentValue = null;
$trailingValues = "";
$endOfDashedArguments = false;
$addParam = false;
$argumentsLeft = sizeof($argumentStack);
$multiShortParams = array();
$this->programName = array_shift($argumentStack); // remove first argument which is the program name
while ($currentArgument = array_shift($argumentStack)) {
$argumentsLeft--;
$currentArgumentLength = strlen($currentArgument);
// arguments that don't start with a dash
if (substr($currentArgument, 0, 1) !== '-') {
if ($expectingValue) {
$currentValue = $currentArgument;
$addParam = true;
} else {
$trailingValues .= " ". $currentArgument;
$endOfDashedArguments = true;
} //if
// double dash detected
} elseif (substr($currentArgument, 1, 1) === '-') {
if ($expectingValue) {
throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
} //if
/* stop parsing arguments if double dash
only param is encountered */
if ($currentArgumentLength == 2) {
if ($trailingValues !== "") {
throw new \UnexpectedValueException("Trailing values must appear after double dash");
} //if
$trailingValues = " ". implode(" ", $argumentStack);
$argumentStack = array();
$endOfDashedArguments = true;
break;
} //if
$longNameParts = explode("=", substr($currentArgument, 2), 2);
$currentLongName = $longNameParts[0];
if (sizeof($longNameParts) > 1) {
$currentValue = $longNameParts[1];
$addParam = true;
} elseif ($this->definitions->allowsValue($currentLongName)) {
$expectingValue = true;
} else {
$addParam = true;
} //if
// single dash
} else {
if ($expectingValue) {
throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
} //if
$shortNameParts = explode("=", substr($currentArgument, 1), 2);
$shortName = $shortNameParts[0];
if (strlen($shortName) <= 1) {
$currentLongName = $this->definitions->getLongName($shortName);
if ($currentLongName === null) {
throw new \InvalidArgumentException("Unable to find name with ".
"provided parameter ({$shortName})");
} //if
if (sizeof($shortNameParts) > 1) {
$currentValue = $shortNameParts[1];
$addParam = true;
} elseif ($this->definitions->allowsValue($currentLongName)) {
$expectingValue = true;
} else {
$addParam = true;
} //if
} else {
$multiShortParams = str_split($shortName);
/* process the last one (which is the only one that can have a value) */
$lastParam = array_pop($multiShortParams);
$currentLongName = $this->definitions->getLongName($lastParam);
if (sizeof($shortNameParts) > 1) {
$currentValue = $shortNameParts[1];
$addParam = true;
} elseif ($this->definitions->allowsValue($lastParam)) {
$expectingValue = true;
} else {
$addParam = true;
} //if
} //if
} //if
if ($addParam) {
if ($endOfDashedArguments) {
throw new \UnexpectedValueException("Unexpected argument after undashed values");
} //if
/* Not sure how this could happen */
// @codeCoverageIgnoreStart
if ($currentLongName === false || $currentLongName === null) {
throw new \UnexpectedValueException("Missing argument name");
} //if
// @codeCoverageIgnoreEnd
if (!$this->definitions->paramExists($currentLongName)) {
throw new \InvalidArgumentException("Invalid argument name");
} //if
$allowsMultiple = $this->definitions->allowsMultiple($currentLongName);
$allowsValue = $this->definitions->allowsValue($currentLongName);
if (isset($this->params[$currentLongName]) && !$allowsMultiple) {
throw new \UnexpectedValueException("Multiple instace of parameter {$currentLongName} not allowed");
} //if
if ($allowsValue) {
/* Missing values should always be detected before addParam is true */
// @codeCoverageIgnoreStart
if ($currentValue === null) {
throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
} //if
// @codeCoverageIgnoreEnd
} elseif ($currentValue !== null) {
throw new \UnexpectedValueException("Parameter {$currentLongName} does not accept values");
} else {
$currentValue = true;
} //if
if ($allowsMultiple) {
if ($allowsValue) {
if (!isset($this->params[$currentLongName])) {
$this->params[$currentLongName] = array();
} //if
$this->params[$currentLongName][] = $currentValue;
} else {
if (!isset($this->params[$currentLongName])) {
$this->params[$currentLongName] = 0;
} //if
$this->params[$currentLongName]++;
} //if
} else {
$this->params[$currentLongName] = $currentValue;
} //if
foreach ($multiShortParams as $shortName) {
$argumentStack[] = "-{$shortName}";
$argumentsLeft++;
} //foreach
/* reset stuff for next param */
$expectingValue = false;
$currentLongName = null;
$currentValue = null;
$addParam = false;
$multiShortParams = array();
} //if
} //while
if ($expectingValue !== false) {
throw new \UnexpectedValueException("Parameter {$currentLongName} expects a values");
} //if
/* Not sure how this could happen */
// @codeCoverageIgnoreStart
if ($currentLongName !== null ||
$addParam !== false ||
$currentValue !== null ||
sizeof($multiShortParams) !== 0) {
throw new \UnexpectedValueException("Unable to process some parameters");
} //if
// @codeCoverageIgnoreEnd
if ($trailingValues !== "") {
$this->trailingValues = substr($trailingValues, 1); // remove extra space at the begging
} //if
$this->parsed = true;
} // parseParams()
}