public function parse()
{
$this->parsed = true;
try {
$tokens = $this->tokens;
// the executed filename
$this->name = array_shift($tokens);
$keyvals = array();
$count = 0;
// standalone argument count
while (!empty($tokens)) {
$token = array_shift($tokens);
list($name, $type) = $this->_parseOption($token);
// We allow short groups
if (strlen($name) > 1 && $type === self::OPTION_TYPE_SHORT) {
$group = str_split($name);
// correct option name
$name = array_shift($group);
// Iterate in reverse order to keep the option order correct
// options that don't require an argument can be mixed.
foreach (array_reverse($group) as $nextShort) {
// put it back into $tokens for another loop
array_unshift($tokens, "-{$nextShort}");
}
}
if ($type === self::OPTION_TYPE_ARGUMENT) {
// its an argument, use an int as the index
$keyvals[$count] = $name;
// We allow for "dynamic" anonymous arguments, so we
// add an option for any anonymous arguments that
// weren't predefined
if (!$this->hasOption($count)) {
$this->options[$count] = new Option($count);
}
$count++;
} else {
// Short circuit if the help flag was set and we're using default help
if ($this->use_default_help === true && $name === 'help') {
$this->printHelp();
exit;
}
$option = $this->getOption($name);
if ($option->isBoolean()) {
$keyvals[$name] = !$option->getDefault();
// inverse of the default, as expected
} elseif ($option->isIncrement()) {
if (!isset($keyvals[$name])) {
$keyvals[$name] = $option->getDefault() + 1;
} else {
$keyvals[$name]++;
}
} else {
// the next token MUST be an "argument" and not another flag/option
$token = array_shift($tokens);
list($val, $type) = $this->_parseOption($token);
if ($type !== self::OPTION_TYPE_ARGUMENT) {
throw new \Exception(sprintf('Unable to parse option %s: Expected an argument', $token));
}
$keyvals[$name] = $val;
}
}
}
// Set values (validates and performs map when applicable)
foreach ($keyvals as $key => $value) {
$this->getOption($key)->setValue($value);
}
// todo protect against duplicates caused by aliases
foreach ($this->options as $option) {
if (is_null($option->getValue()) && $option->isRequired()) {
throw new \Exception(sprintf('Required %s %s must be specified', $option->getType() & Option::TYPE_NAMED ? 'option' : 'argument', $option->getName()));
}
}
// See if our options have what they require
foreach ($this->options as $option) {
$needs = $option->hasNeeds($this->options);
if ($needs !== true) {
throw new \InvalidArgumentException('Option "' . $option->getName() . '" does not have required option(s): ' . implode(', ', $needs));
}
}
// keep track of our argument vs. flag keys
// done here to allow for flags/arguments added
// at run time. okay because option values are
// not mutable after parsing.
foreach ($this->options as $k => $v) {
if (is_numeric($k)) {
$this->arguments[$k] = $v;
} else {
$this->flags[$k] = $v;
}
}
// Used in the \Iterator implementation
$this->sorted_keys = array_keys($this->options);
natsort($this->sorted_keys);
// // See if our options have what they require
// foreach ($this->options as $option) {
// $needs = $option->hasNeeds($keyvals);
// if ($needs !== true) {
// throw new \InvalidArgumentException(
// 'Option "'.$option->getName().'" does not have required option(s): '.implode(', ', $needs)
// );
// }
// }
} catch (\Exception $e) {
$this->error($e);
}
}