diff --git a/.travis.yml b/.travis.yml index 6c7b0e4..10f12dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: php php: + - 7.0 - 5.6 - 5.5 - 5.4 diff --git a/src/Ulrichsg/Getopt/CommandLineParser.php b/src/Ulrichsg/Getopt/CommandLineParser.php index 2020680..8327cec 100644 --- a/src/Ulrichsg/Getopt/CommandLineParser.php +++ b/src/Ulrichsg/Getopt/CommandLineParser.php @@ -2,6 +2,8 @@ namespace Ulrichsg\Getopt; +use Ulrichsg\Getopt\Util\String; + /** * Parses command line arguments according to a list of allowed options. */ @@ -10,9 +12,6 @@ class CommandLineParser /** @var Option[] */ private $optionList; - private $options = array(); - private $operands = array(); - /** * Creates a new instance. * @@ -27,12 +26,17 @@ public function __construct(array $optionList) * Parses the given arguments and converts them into options and operands. * * @param mixed $arguments a string or an array with one argument per element + * @return Result */ public function parse($arguments) { if (!is_array($arguments)) { - $arguments = explode(' ', $arguments); + $tokenizer = new Tokenizer(); + // $arguments = explode(' ', $arguments); + $arguments = $tokenizer->tokenize($arguments); } + echo json_encode($arguments)."\n"; + $options = array(); $operands = array(); $numArgs = count($arguments); for ($i = 0; $i < $numArgs; ++$i) { @@ -40,141 +44,106 @@ public function parse($arguments) if (empty($arg)) { continue; } - if (($arg === '--') || ($arg === '-') || (mb_substr($arg, 0, 1) !== '-')){ + if (($arg === '--') || ($arg === '-') || !String::startsWith($arg, '-')){ // no more options, treat the remaining arguments as operands $firstOperandIndex = ($arg == '--') ? $i + 1 : $i; $operands = array_slice($arguments, $firstOperandIndex); break; } - if (mb_substr($arg, 0, 2) == '--') { - $this->addLongOption($arguments, $i); + if (String::startsWith($arg, '--')) { + $options = $this->addLongOption($options, $arguments, $i); } else { - $this->addShortOption($arguments, $i); - } - } // endfor - - $this->addDefaultValues(); - - // remove '--' from operands array - foreach ($operands as $operand) { - if ($operand !== '--') { - $this->operands[] = $operand; + $options = $this->addShortOption($options, $arguments, $i); } } - } - /** - * Returns the options created by a previous invocation of parse(). - * - * @return array - */ - public function getOptions() - { - return $this->options; + $options = $this->addDefaultValues($options); + $operands = array_values(array_diff($operands, array('--'))); + return new Result($options, $operands); } - - /** - * Returns the operands created by a previous invocation of parse(), - * - * @return array - */ - public function getOperands() - { - return $this->operands; - } - - private function addShortOption($arguments, &$i) + private function addShortOption(array $options, $arguments, &$i) { - $numArgs = count($arguments); - $option = mb_substr($arguments[$i], 1); - if (mb_strlen($option) > 1) { + $nextArg = $this->nextElement($arguments, $i); + $option = String::substr($arguments[$i], 1); + if (String::length($option) > 1) { // multiple options strung together - $options = $this->splitString($option, 1); - foreach ($options as $j => $ch) { - if ($j < count($options) - 1 - || !( - $i < $numArgs - 1 - && ((mb_substr($arguments[$i + 1], 0, 1) !== '-') || ($arguments[$i + 1] === '-')) - && $this->optionHasArgument($ch) - ) - ) { - $this->addOption($ch, null); - } else { // e.g. `ls -sw 100` - $value = $arguments[$i + 1]; + $flags = String::split($option); + foreach ($flags as $j => $flag) { + if ($j === count($flags) - 1 && $this->canBeArgument($nextArg) && $this->optionHasArgument($flag)) { + // e.g. `ls -sw 100` + $options = $this->addOption($options, $flag, $nextArg); ++$i; - $this->addOption($ch, $value); + } else { + $options = $this->addOption($options, $flag, null); } } } else { - if ($i < $numArgs - 1 - && ((mb_substr($arguments[$i + 1], 0, 1) !== '-') || ($arguments[$i + 1] === '-')) - && $this->optionHasArgument($option) - ) { - $value = $arguments[$i + 1]; + if ($this->canBeArgument($nextArg) && $this->optionHasArgument($option)) { + $options = $this->addOption($options, $option, $nextArg); ++$i; } else { - $value = null; + $options = $this->addOption($options, $option, null); } - $this->addOption($option, $value); } + return $options; } - private function addLongOption($arguments, &$i) + private function addLongOption(array $options, $arguments, &$i) { - $option = mb_substr($arguments[$i], 2); - if (strpos($option, '=') === false) { - if ($i < count($arguments) - 1 - && ((mb_substr($arguments[$i + 1], 0, 1) !== '-') || ($arguments[$i + 1] === '-')) - && $this->optionHasArgument($option) - ) { - $value = $arguments[$i + 1]; + $option = String::substr($arguments[$i], 2); + if (String::contains($option, '=')) { + list($option, $value) = explode('=', $option, 2); + } else { + $nextArg = $this->nextElement($arguments, $i); + if ($this->canBeArgument($nextArg) && $this->optionHasArgument($option)) { + $value = $nextArg; ++$i; } else { $value = null; } - } else { - list($option, $value) = explode('=', $option, 2); } - $this->addOption($option, $value); + return $this->addOption($options, $option, $value); } /** * Add an option to the list of known options. * + * @param Option[] $options * @param string $string the option's name * @param string $value the option's value (or null) * @throws \UnexpectedValueException - * @return void + * @return Option[] */ - private function addOption($string, $value) + private function addOption(array $options, $string, $value) { foreach ($this->optionList as $option) { - if ($option->matches($string)) { - if ($option->mode() == Getopt::REQUIRED_ARGUMENT && !mb_strlen($value)) { - throw new \UnexpectedValueException("Option '$string' must have a value"); - } - if ($option->getArgument()->hasValidation()) { - if ((mb_strlen($value) > 0) && !$option->getArgument()->validates($value)) { - throw new \UnexpectedValueException("Option '$string' has an invalid value"); - } - } - // for no-argument options, check if they are duplicate - if ($option->mode() == Getopt::NO_ARGUMENT) { - $oldValue = isset($this->options[$string]) ? $this->options[$string] : null; - $value = is_null($oldValue) ? 1 : $oldValue + 1; - } - // for optional-argument options, set value to 1 if none was given - $value = (mb_strlen($value) > 0) ? $value : 1; - // add both long and short names (if they exist) to the option array to facilitate lookup - if ($option->short()) { - $this->options[$option->short()] = $value; - } - if ($option->long()) { - $this->options[$option->long()] = $value; + if (!$option->matches($string)) { + continue; + } + if ($option->mode() == Getopt::REQUIRED_ARGUMENT && String::length($value) === 0) { + throw new \UnexpectedValueException("Option '$string' must have a value"); + } + if ($option->getArgument()->hasValidation()) { + if ((String::length($value) > 0) && !$option->getArgument()->validates($value)) { + throw new \UnexpectedValueException("Option '$string' has an invalid value"); } - return; } + // for no-argument options, check if they are duplicate (eg. '-vvv') + if ($option->mode() == Getopt::NO_ARGUMENT) { + $oldValue = isset($options[$string]) ? $options[$string] : null; + $value = is_null($oldValue) ? 1 : $oldValue + 1; + } + // for optional-argument options, set value to 1 if none was given + $value = (String::length($value) > 0) ? $value : 1; + // add both long and short names (if they exist) to the option array to facilitate lookup + if ($option->short()) { + $options[$option->short()] = $value; + } + if ($option->long()) { + $options[$option->long()] = $value; + } + return $options; } throw new \UnexpectedValueException("Option '$string' is unknown"); } @@ -182,22 +151,26 @@ private function addOption($string, $value) /** * If there are options with default values that were not overridden by the parsed option string, * add them to the list of known options. + * + * @param Option[] $options + * @return Option[] */ - private function addDefaultValues() + private function addDefaultValues(array $options) { foreach ($this->optionList as $option) { if ($option->getArgument()->hasDefaultValue() - && !isset($this->options[$option->short()]) - && !isset($this->options[$option->long()]) + && !isset($options[$option->short()]) + && !isset($options[$option->long()]) ) { if ($option->short()) { - $this->addOption($option->short(), $option->getArgument()->getDefaultValue()); + $options = $this->addOption($options, $option->short(), $option->getArgument()->getDefaultValue()); } if ($option->long()) { - $this->addOption($option->long(), $option->getArgument()->getDefaultValue()); + $options = $this->addOption($options, $option->long(), $option->getArgument()->getDefaultValue()); } } } + return $options; } /** @@ -216,18 +189,13 @@ private function optionHasArgument($name) return false; } - /** - * Split the string into individual characters, - * - * @param string $string string to split - * @return array - */ - private function splitString($string) + private function nextElement(array $array, $index) { - $result = array(); - for ($i = 0; $i < mb_strlen($string, "UTF-8"); ++$i) { - $result[] = mb_substr($string, $i, 1, "UTF-8"); - } - return $result; + return ($index < count($array) - 1) ? $array[$index + 1] : null; + } + + private function canBeArgument($string) + { + return !is_null($string) && (($string === '-') || !String::startsWith($string, '-')); } } diff --git a/src/Ulrichsg/Getopt/DefaultHelpTextFormatter.php b/src/Ulrichsg/Getopt/DefaultHelpTextFormatter.php new file mode 100644 index 0000000..987234c --- /dev/null +++ b/src/Ulrichsg/Getopt/DefaultHelpTextFormatter.php @@ -0,0 +1,61 @@ +getBanner(), $this->scriptName); + $helpText .= "Options:\n"; + foreach ($options as $option) { + $mode = ''; + switch ($option->mode()) { + case Getopt::NO_ARGUMENT: + $mode = ''; + break; + case Getopt::REQUIRED_ARGUMENT: + $mode = "<".$option->getArgument()->getName().">"; + break; + case Getopt::OPTIONAL_ARGUMENT: + $mode = "[<".$option->getArgument()->getName().">]"; + break; + } + $short = ($option->short()) ? '-'.$option->short() : ''; + $long = ($option->long()) ? '--'.$option->long() : ''; + if ($short && $long) { + $options = $short.', '.$long; + } else { + $options = $short ? : $long; + } + $padded = str_pad(sprintf(" %s %s", $options, $mode), $padding); + $helpText .= sprintf("%s %s\n", $padded, $option->getDescription()); + } + return $helpText; + } + + public function getBanner() + { + return $this->banner; + } + + public function setBanner($banner) + { + $this->banner = $banner; + } + + public function setScriptName($scriptName) + { + $this->scriptName = $scriptName; + } +} diff --git a/src/Ulrichsg/Getopt/Getopt.php b/src/Ulrichsg/Getopt/Getopt.php index bd6b7ad..3e1d4c2 100644 --- a/src/Ulrichsg/Getopt/Getopt.php +++ b/src/Ulrichsg/Getopt/Getopt.php @@ -6,11 +6,11 @@ * Getopt.PHP allows for easy processing of command-line arguments. * It is a more powerful, object-oriented alternative to PHP's built-in getopt() function. * - * @version 2.1.0 + * @version 3 * @license MIT * @link http://ulrichsg.github.io/getopt-php */ -class Getopt implements \Countable, \ArrayAccess, \IteratorAggregate +class Getopt { const NO_ARGUMENT = 0; const REQUIRED_ARGUMENT = 1; @@ -18,16 +18,10 @@ class Getopt implements \Countable, \ArrayAccess, \IteratorAggregate /** @var OptionParser */ private $optionParser; - /** @var string */ - private $scriptName; /** @var Option[] */ - private $optionList = array(); - /** @var array */ - private $options = array(); - /** @var array */ - private $operands = array(); - /** @var string */ - private $banner = "Usage: %s [options] [operands]\n"; + protected $optionList = array(); + /** @var HelpTextFormatter */ + protected $helpTextFormatter; /** * Creates a new Getopt object. @@ -47,6 +41,7 @@ public function __construct($options = null, $defaultType = Getopt::NO_ARGUMENT) if ($options !== null) { $this->addOptions($options); } + $this->helpTextFormatter = new DefaultHelpTextFormatter(); } /** @@ -83,10 +78,10 @@ private function mergeOptions(array $options) if (($option === $otherOption) || in_array($otherOption, $duplicates)) { continue; } - if ($this->optionsConflict($option, $otherOption)) { + if ($option->conflictsWith($otherOption)) { throw new \InvalidArgumentException('Failed to add options due to conflict'); } - if (($option->short() === $otherOption->short()) && ($option->long() === $otherOption->long())) { + if ($option->equals($otherOption)) { $duplicates[] = $option; } } @@ -99,15 +94,6 @@ private function mergeOptions(array $options) $this->optionList = array_values($mergedList); } - private function optionsConflict(Option $option1, Option $option2) { - if ((is_null($option1->short()) && is_null($option2->short())) - || (is_null($option1->long()) && is_null($option2->long()))) { - return false; - } - return ((($option1->short() === $option2->short()) && ($option1->long() !== $option2->long())) - || (($option1->short() !== $option2->short()) && ($option1->long() === $option2->long()))); - } - /** * Evaluate the given arguments. These can be passed either as a string or as an array. * If nothing is passed, the running script's command line arguments are used. @@ -116,180 +102,31 @@ private function optionsConflict(Option $option1, Option $option2) { * when the arguments are not well-formed or do not conform to the options passed by the user. * * @param mixed $arguments optional ARGV array or space separated string + * @return Result */ public function parse($arguments = null) { - $this->options = array(); + $scriptName = $_SERVER['PHP_SELF']; if (!isset($arguments)) { global $argv; $arguments = $argv; - $this->scriptName = array_shift($arguments); // $argv[0] is the script's name + $scriptName = array_shift($arguments); // $argv[0] is the script's name } elseif (is_string($arguments)) { - $this->scriptName = $_SERVER['PHP_SELF']; $arguments = explode(' ', $arguments); } + $this->helpTextFormatter->setScriptName($scriptName); $parser = new CommandLineParser($this->optionList); - $parser->parse($arguments); - $this->options = $parser->getOptions(); - $this->operands = $parser->getOperands(); - } - - /** - * Returns the value of the given option. Must be invoked after parse(). - * - * The return value can be any of the following: - * - * - * @param string $name The (short or long) option name. - * @return mixed - */ - public function getOption($name) - { - return isset($this->options[$name]) ? $this->options[$name] : null; - } - - /** - * Returns the list of options. Must be invoked after parse() (otherwise it returns an empty array). - * - * @return array - */ - public function getOptions() - { - return $this->options; - } - - /** - * Returns the list of operands. Must be invoked after parse(). - * - * @return array - */ - public function getOperands() - { - return $this->operands; + return $parser->parse($arguments); } - /** - * Returns the i-th operand (starting with 0), or null if it does not exist. Must be invoked after parse(). - * - * @param int $i - * @return string - */ - public function getOperand($i) + public function setHelpTextFormatter(HelpTextFormatter $formatter) { - return ($i < count($this->operands)) ? $this->operands[$i] : null; + $this->helpTextFormatter = $formatter; } - /** - * Returns the banner string - * - * @return string - */ - public function getBanner() - { - return $this->banner; - } - - /** - * Set the banner string - * - * @param string $banner The banner string; will be passed to sprintf(), can include %s for current scripts name. - * Be sure to include a trailing line feed. - * @return Getopt - */ - public function setBanner($banner) - { - $this->banner = $banner; - return $this; - } - - /** - * Returns an usage information text generated from the given options. - * @param int $padding Number of characters to pad output of options to - * @return string - */ public function getHelpText($padding = 25) { - $helpText = sprintf($this->getBanner(), $this->scriptName); - $helpText .= "Options:\n"; - foreach ($this->optionList as $option) { - $mode = ''; - switch ($option->mode()) { - case self::NO_ARGUMENT: - $mode = ''; - break; - case self::REQUIRED_ARGUMENT: - $mode = "<".$option->getArgument()->getName().">"; - break; - case self::OPTIONAL_ARGUMENT: - $mode = "[<".$option->getArgument()->getName().">]"; - break; - } - $short = ($option->short()) ? '-'.$option->short() : ''; - $long = ($option->long()) ? '--'.$option->long() : ''; - if ($short && $long) { - $options = $short.', '.$long; - } else { - $options = $short ? : $long; - } - $padded = str_pad(sprintf(" %s %s", $options, $mode), $padding); - $helpText .= sprintf("%s %s\n", $padded, $option->getDescription()); - } - return $helpText; - } - - - /* - * Interface support functions - */ - - public function count() - { - return count($this->options); - } - - public function offsetExists($offset) - { - return isset($this->options[$offset]); - } - - public function offsetGet($offset) - { - return $this->getOption($offset); - } - - public function offsetSet($offset, $value) - { - throw new \LogicException('Getopt is read-only'); - } - - public function offsetUnset($offset) - { - throw new \LogicException('Getopt is read-only'); - } - - public function getIterator() - { - // For options that have both short and long names, $this->options has two entries. - // We don't want this when iterating, so we have to filter the duplicates out. - $filteredOptions = array(); - foreach ($this->options as $name => $value) { - $keep = true; - foreach ($this->optionList as $option) { - if ($option->long() == $name && !is_null($option->short())) { - $keep = false; - } - } - if ($keep) { - $filteredOptions[$name] = $value; - } - } - return new \ArrayIterator($filteredOptions); + return $this->helpTextFormatter->getHelpText($this->optionList, $padding); } } diff --git a/src/Ulrichsg/Getopt/Getoptv2.php b/src/Ulrichsg/Getopt/Getoptv2.php new file mode 100644 index 0000000..42fac99 --- /dev/null +++ b/src/Ulrichsg/Getopt/Getoptv2.php @@ -0,0 +1,142 @@ +result = parent::parse($arguments); + return $this->result; + } + + /** + * Returns the value of the given option. Must be invoked after parse(). + * + * The return value can be any of the following: + * + * + * @param string $name The (short or long) option name. + * @return mixed + */ + public function getOption($name) + { + return isset($this->result) ? $this->result->getOption($name) : null; + } + + /** + * Returns the list of options. Must be invoked after parse() (otherwise it returns an empty array). + * + * @return array + */ + public function getOptions() + { + return isset($this->result) ? $this->result->getOptions() : array(); + } + + /** + * Returns the list of operands. Must be invoked after parse(). + * + * @return array + */ + public function getOperands() + { + return isset($this->result) ? $this->result->getOperands() : array(); + } + + /** + * Returns the i-th operand (starting with 0), or null if it does not exist. Must be invoked after parse(). + * + * @param int $i + * @return string + */ + public function getOperand($i) + { + return isset($this->result) ? $this->result->getOperand($i) : null; + } + + + /* + * Interface support functions + */ + + public function count() + { + return isset($this->result) ? count($this->result->getOptions()) : 0; + } + + public function offsetExists($offset) + { + $options = $this->getOptions(); + return isset($options[$offset]); + } + + public function offsetGet($offset) + { + return $this->getOption($offset); + } + + public function offsetSet($offset, $value) + { + throw new \LogicException('Getopt is read-only'); + } + + public function offsetUnset($offset) + { + throw new \LogicException('Getopt is read-only'); + } + + public function getIterator() + { + // For options that have both short and long names, $this->options has two entries. + // We don't want this when iterating, so we have to filter the duplicates out. + $filteredOptions = array(); + foreach ($this->getOptions() as $name => $value) { + $keep = true; + foreach ($this->optionList as $option) { + if ($option->long() == $name && !is_null($option->short())) { + $keep = false; + } + } + if ($keep) { + $filteredOptions[$name] = $value; + } + } + return new \ArrayIterator($filteredOptions); + } + + /** + * Returns the banner string + * + * @return string + */ + public function getBanner() + { + return $this->helpTextFormatter->getBanner(); + } + + /** + * Set the banner string + * + * @param string $banner The banner string; will be passed to sprintf(), can include %s for current scripts name. + * Be sure to include a trailing line feed. + * @return Getopt + */ + public function setBanner($banner) + { + $this->helpTextFormatter->setBanner($banner); + return $this; + } +} diff --git a/src/Ulrichsg/Getopt/HelpTextFormatter.php b/src/Ulrichsg/Getopt/HelpTextFormatter.php new file mode 100644 index 0000000..384722d --- /dev/null +++ b/src/Ulrichsg/Getopt/HelpTextFormatter.php @@ -0,0 +1,12 @@ +argument = new Argument(); } + /** + * Fluent interface for constructor so options can be added during construction + * @see Options::__construct() + */ + public static function create($short, $long, $mode = Getopt::NO_ARGUMENT) + { + return new self($short, $long, $mode); + } + /** * Defines a description for the option. This is only used for generating usage information. * @@ -46,31 +55,31 @@ public function setDescription($description) return $this; } - /** - * Defines a default value for the option. - * - * @param mixed $value + /** + * Defines a default value for the option. + * + * @param mixed $value * @return Option this object (for chaining calls) - */ - public function setDefaultValue($value) - { - $this->argument->setDefaultValue($value); - return $this; - } - - /** - * Defines a validation function for the option. - * - * @param callable $function - * @return Option this object (for chaining calls) - */ - public function setValidation($function) - { - $this->argument->setValidation($function); - return $this; - } - - /** + */ + public function setDefaultValue($value) + { + $this->argument->setDefaultValue($value); + return $this; + } + + /** + * Defines a validation function for the option. + * + * @param callable $function + * @return Option this object (for chaining calls) + */ + public function setValidation($function) + { + $this->argument->setValidation($function); + return $this; + } + + /** * Sets the argument object directly. * * @param Argument $arg @@ -96,6 +105,32 @@ public function matches($string) return ($string === $this->short) || ($string === $this->long); } + /** + * Returns true if the given option is considered a duplicate of this one. + * + * @param Option $other + * @return bool + */ + public function equals(Option $other) { + return ($this->short() === $other->short() && $this->long() === $other->long()); + } + + /** + * Returns true if the given option cannot appear in the same option list as this one due to a name conflict. + * + * @param Option $other + * @return bool + */ + public function conflictsWith(Option $other) + { + if ((is_null($this->short()) && is_null($other->short())) + || (is_null($this->long()) && is_null($other->long()))) { + return false; + } + return ((($this->short() === $other->short()) && ($this->long() !== $other->long())) + || (($this->short() !== $other->short()) && ($this->long() === $other->long()))); + } + public function short() { return $this->short; @@ -118,22 +153,13 @@ public function getDescription() /** * Retrieve the argument object - * + * * @return Argument */ public function getArgument() { return $this->argument; } - - /** - * Fluent interface for constructor so options can be added during construction - * @see Options::__construct() - */ - public static function create($short, $long, $mode = Getopt::NO_ARGUMENT) - { - return new self($short, $long, $mode); - } private function setShort($short) { diff --git a/src/Ulrichsg/Getopt/Result.php b/src/Ulrichsg/Getopt/Result.php new file mode 100644 index 0000000..24dfb75 --- /dev/null +++ b/src/Ulrichsg/Getopt/Result.php @@ -0,0 +1,69 @@ +options = $options; + $this->operands = $operands; + } + + /** + * Returns the value of the given option. + * + * The return value can be any of the following: + * + * + * @param string $name The (short or long) option name. + * @return mixed + */ + public function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Returns the list of options. Must be invoked after parse() (otherwise it returns an empty array). + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns the list of operands. + * + * @return array + */ + public function getOperands() + { + return $this->operands; + } + + /** + * Returns the i-th operand (starting with 0), or null if it does not exist. + * + * @param int $i + * @return string + */ + public function getOperand($i) + { + return ($i < count($this->operands)) ? $this->operands[$i] : null; + } +} diff --git a/src/Ulrichsg/Getopt/Tokenizer.php b/src/Ulrichsg/Getopt/Tokenizer.php new file mode 100644 index 0000000..4feff73 --- /dev/null +++ b/src/Ulrichsg/Getopt/Tokenizer.php @@ -0,0 +1,100 @@ +nextToken($string, $pos); + } + return $tokens; + } + + private function nextToken($string, &$pos) + { + while (ctype_space($string[$pos])) { + ++$pos; + if ($pos >= String::length($string)) { + return null; + } + } + if (String::at($string, $pos) === '"') { + ++$pos; + return $this->getQuotedToken($string, $pos); + } + if (String::at($string, $pos) === '-') { + return $this->getHyphenatedToken($string, $pos); + } + return $this->getPlainToken($string, $pos); + } + + private function getQuotedToken($string, &$pos) + { + $token = ''; + while ($pos < String::length($string) && $string[$pos] !== '"') { + $token .= $this->nextChar($string, $pos); + echo "$token\n"; + } + if (!String::isSpaceOrEnd($string, $pos)) { + throw new \UnexpectedValueException('Syntax error'); + // error + } + return $token; + } + + private function getHyphenatedToken($string, &$pos) + { + $hyphens = ""; + while (String::at($string, $pos) === '-') { + $hyphens .= $string[$pos++]; + } + if (String::isSpaceOrEnd($string, $pos)) { + return $hyphens; + } + if (!ctype_alnum($string[$pos])) { + // error + throw new \UnexpectedValueException("Syntax error: expected letter or digit, found {$string[$pos]} in ".$hyphens.String::substr($string, $pos)); + } + $token = ''; + while (ctype_alnum(String::at($string, $pos))) { + $token .= $string[$pos++]; + } + if (String::at($string, $pos) === '=' || String::isSpaceOrEnd($string, $pos)) { + ++$pos; + echo $hyphens.$token."\n"; + return $hyphens.$token; + } + throw new \UnexpectedValueException('Syntax error'); + // error + } + + private function getPlainToken($string, &$pos) + { + $token = ''; + while (!String::isSpaceOrEnd($string, $pos)) { + $token .= $this->nextChar($string, $pos); + } + return $token; + } + + private function nextChar($string, &$pos) + { + if ($string[$pos] === '\\') { + $followingChar = $string[$pos+1]; + if (in_array($followingChar, array('\\', '"'))) { + $pos += 2; + return $followingChar; + } else { + throw new \UnexpectedValueException('Syntax error'); + // error + } + } + return $string[$pos++]; + } +} diff --git a/src/Ulrichsg/Getopt/Util/String.php b/src/Ulrichsg/Getopt/Util/String.php new file mode 100644 index 0000000..b2cb536 --- /dev/null +++ b/src/Ulrichsg/Getopt/Util/String.php @@ -0,0 +1,57 @@ += self::length($string)) { + return null; + } + return $string[$pos]; + } + + public static function length($string) + { + return mb_strlen($string, "UTF-8"); + } + + public static function substr($string, $start, $length = null) + { + return mb_substr($string, $start, $length, "UTF-8"); + } + + public static function startsWith($string, $prefix) + { + return self::substr($string, 0, self::length($prefix)) === $prefix; + } + + /** + * Split the string into individual characters, + * + * @param string $string string to split + * @return array + */ + public static function split($string) + { + $result = array(); + for ($i = 0; $i < self::length($string); ++$i) { + $result[] = mb_substr($string, $i, 1, "UTF-8"); + } + return $result; + } + + public static function contains($string, $substr) + { + return mb_strpos($string, $substr, null, "UTF-8"); + } + + public static function isSpaceOrEnd($string, $pos) + { + if ($pos >= self::length($string)) { + return true; + } + return ctype_space($string[$pos]); + } +} diff --git a/test/Ulrichsg/Getopt/CommandLineParserTest.php b/test/Ulrichsg/Getopt/CommandLineParserTest.php index 8f5cb3d..ff0ac23 100644 --- a/test/Ulrichsg/Getopt/CommandLineParserTest.php +++ b/test/Ulrichsg/Getopt/CommandLineParserTest.php @@ -9,9 +9,9 @@ public function testParseNoOptions() $parser = new CommandLineParser(array( new Option('a', null) )); - $parser->parse('something'); - $this->assertCount(0, $parser->getOptions()); - $operands = $parser->getOperands(); + $result = $parser->parse('something'); + $this->assertCount(0, $result->getOptions()); + $operands = $result->getOperands(); $this->assertCount(1, $operands); $this->assertEquals('something', $operands[0]); } @@ -40,11 +40,10 @@ public function testParseMultipleOptionsWithOneHyphen() new Option('a', null), new Option('b', null) )); - $parser->parse('-ab'); + $result = $parser->parse('-ab'); - $options = $parser->getOptions(); - $this->assertEquals(1, $options['a']); - $this->assertEquals(1, $options['b']); + $this->assertEquals(1, $result->getOption('a')); + $this->assertEquals(1, $result->getOption('b')); } public function testParseCumulativeOption() @@ -53,11 +52,10 @@ public function testParseCumulativeOption() new Option('a', null), new Option('b', null) )); - $parser->parse('-a -b -a -a'); + $result = $parser->parse('-a -b -a -a'); - $options = $parser->getOptions(); - $this->assertEquals(3, $options['a']); - $this->assertEquals(1, $options['b']); + $this->assertEquals(3, $result->getOption('a')); + $this->assertEquals(1, $result->getOption('b')); } public function testParseCumulativeOptionShort() @@ -66,11 +64,10 @@ public function testParseCumulativeOptionShort() new Option('a', null), new Option('b', null) )); - $parser->parse('-abaa'); + $result = $parser->parse('-abaa'); - $options = $parser->getOptions(); - $this->assertEquals(3, $options['a']); - $this->assertEquals(1, $options['b']); + $this->assertEquals(3, $result->getOption('a')); + $this->assertEquals(1, $result->getOption('b')); } public function testParseShortOptionWithArgument() @@ -78,10 +75,20 @@ public function testParseShortOptionWithArgument() $parser = new CommandLineParser(array( new Option('a', null, Getopt::REQUIRED_ARGUMENT) )); - $parser->parse('-a value'); + $result = $parser->parse('-a value'); + + $this->assertEquals('value', $result->getOption('a')); + } + + public function testParseShortOptionWithQuotedArgument() + { + $this->markTestSkipped(); + $parser = new CommandLineParser(array( + new Option('a', null, Getopt::REQUIRED_ARGUMENT) + )); + $result = $parser->parse('-a "hello world"'); - $options = $parser->getOptions(); - $this->assertEquals('value', $options['a']); + $this->assertEquals('hello world', $result->getOption('a')); } public function testParseZeroArgument() @@ -89,10 +96,9 @@ public function testParseZeroArgument() $parser = new CommandLineParser(array( new Option('a', null, Getopt::REQUIRED_ARGUMENT) )); - $parser->parse('-a 0'); + $result = $parser->parse('-a 0'); - $options = $parser->getOptions(); - $this->assertEquals('0', $options['a']); + $this->assertEquals('0', $result->getOption('a')); } public function testParseNumericOption() @@ -101,11 +107,10 @@ public function testParseNumericOption() new Option('a', null, Getopt::REQUIRED_ARGUMENT), new Option('2', null) )); - $parser->parse('-a 2 -2'); + $result = $parser->parse('-a 2 -2'); - $options = $parser->getOptions(); - $this->assertEquals('2', $options['a']); - $this->assertEquals(1, $options['2']); + $this->assertEquals('2', $result->getOption('a')); + $this->assertEquals(1, $result->getOption('2')); } public function testParseCollapsedShortOptionsRequiredArgumentMissing() @@ -124,11 +129,10 @@ public function testParseCollapsedShortOptionsWithArgument() new Option('a', null), new Option('b', null, Getopt::REQUIRED_ARGUMENT) )); - $parser->parse('-ab value'); + $result = $parser->parse('-ab value'); - $options = $parser->getOptions(); - $this->assertEquals(1, $options['a']); - $this->assertEquals('value', $options['b']); + $this->assertEquals(1, $result->getOption('a')); + $this->assertEquals('value', $result->getOption('b')); } public function testParseNoArgumentOptionAndOperand() @@ -136,11 +140,10 @@ public function testParseNoArgumentOptionAndOperand() $parser = new CommandLineParser(array( new Option('a', null), )); - $parser->parse('-a b'); + $result = $parser->parse('-a b'); - $options = $parser->getOptions(); - $this->assertEquals(1, $options['a']); - $operands = $parser->getOperands(); + $this->assertEquals(1, $result->getOption('a')); + $operands = $result->getOperands(); $this->assertCount(1, $operands); $this->assertEquals('b', $operands[0]); } @@ -151,10 +154,10 @@ public function testParseOperandsOnly() new Option('a', null, Getopt::REQUIRED_ARGUMENT), new Option('b', null) )); - $parser->parse('-- -a -b'); + $result = $parser->parse('-- -a -b'); - $this->assertCount(0, $parser->getOptions()); - $operands = $parser->getOperands(); + $this->assertCount(0, $result->getOptions()); + $operands = $result->getOperands(); $this->assertCount(2, $operands); $this->assertEquals('-a', $operands[0]); $this->assertEquals('-b', $operands[1]); @@ -165,10 +168,9 @@ public function testParseLongOptionWithoutArgument() $parser = new CommandLineParser(array( new Option('o', 'option', Getopt::OPTIONAL_ARGUMENT) )); - $parser->parse('--option'); + $result = $parser->parse('--option'); - $options = $parser->getOptions(); - $this->assertEquals(1, $options['option']); + $this->assertEquals(1, $result->getOption('option')); } public function testParseLongOptionWithoutArgumentAndOperand() @@ -176,11 +178,10 @@ public function testParseLongOptionWithoutArgumentAndOperand() $parser = new CommandLineParser(array( new Option('o', 'option', Getopt::NO_ARGUMENT) )); - $parser->parse('--option something'); + $result = $parser->parse('--option something'); - $options = $parser->getOptions(); - $this->assertEquals(1, $options['option']); - $operands = $parser->getOperands(); + $this->assertEquals(1, $result->getOption('option')); + $operands = $result->getOperands(); $this->assertCount(1, $operands); $this->assertEquals('something', $operands[0]); } @@ -190,11 +191,10 @@ public function testParseLongOptionWithArgument() $parser = new CommandLineParser(array( new Option('o', 'option', Getopt::OPTIONAL_ARGUMENT) )); - $parser->parse('--option value'); + $result = $parser->parse('--option value'); - $options = $parser->getOptions(); - $this->assertEquals('value', $options['option']); - $this->assertEquals('value', $options['o']); + $this->assertEquals('value', $result->getOption('option')); + $this->assertEquals('value', $result->getOption('o')); } public function testParseLongOptionWithEqualsSignAndArgument() @@ -202,24 +202,23 @@ public function testParseLongOptionWithEqualsSignAndArgument() $parser = new CommandLineParser(array( new Option('o', 'option', Getopt::OPTIONAL_ARGUMENT) )); - $parser->parse('--option=value something'); + $result = $parser->parse('--option=value something'); - $options = $parser->getOptions(); - $this->assertEquals('value', $options['option']); - $operands = $parser->getOperands(); + $this->assertEquals('value', $result->getOption('option')); + $operands = $result->getOperands(); $this->assertCount(1, $operands); $this->assertEquals('something', $operands[0]); } public function testParseLongOptionWithValueStartingWithHyphen() { + //$this->markTestSkipped(); $parser = new CommandLineParser(array( new Option('o', 'option', Getopt::REQUIRED_ARGUMENT) )); - $parser->parse('--option=-value'); + $result = $parser->parse('--option=-value'); - $options = $parser->getOptions(); - $this->assertEquals('-value', $options['option']); + $this->assertEquals('-value', $result->getOption('option')); } public function testParseNoValueStartingWithHyphenRequired() @@ -238,11 +237,10 @@ public function testParseNoValueStartingWithHyphenOptional() new Option('a', null, Getopt::OPTIONAL_ARGUMENT), new Option('b', null) )); - $parser->parse('-a -b'); + $result = $parser->parse('-a -b'); - $options = $parser->getOptions(); - $this->assertEquals(1, $options['a']); - $this->assertEquals(1, $options['b']); + $this->assertEquals(1, $result->getOption('a')); + $this->assertEquals(1, $result->getOption('b')); } public function testParseOptionWithDefaultValue() @@ -252,12 +250,11 @@ public function testParseOptionWithDefaultValue() $optionB = new Option('b', 'beta', Getopt::REQUIRED_ARGUMENT); $optionB->setArgument(new Argument(20)); $parser = new CommandLineParser(array($optionA, $optionB)); - $parser->parse('-a 12'); + $result = $parser->parse('-a 12'); - $options = $parser->getOptions(); - $this->assertEquals(12, $options['a']); - $this->assertEquals(20, $options['b']); - $this->assertEquals(20, $options['beta']); + $this->assertEquals(12, $result->getOption('a')); + $this->assertEquals(20, $result->getOption('b')); + $this->assertEquals(20, $result->getOption('beta')); } public function testDoubleHyphenNotInOperands() @@ -265,11 +262,10 @@ public function testDoubleHyphenNotInOperands() $parser = new CommandLineParser(array( new Option('a', null, Getopt::REQUIRED_ARGUMENT) )); - $parser->parse('-a 0 foo -- bar baz'); + $result = $parser->parse('-a 0 foo -- bar baz'); - $options = $parser->getOptions(); - $this->assertEquals('0', $options['a']); - $operands = $parser->getOperands(); + $this->assertEquals('0', $result->getOption('a')); + $operands = $result->getOperands(); $this->assertCount(3, $operands); $this->assertEquals('foo', $operands[0]); $this->assertEquals('bar', $operands[1]); @@ -282,18 +278,16 @@ public function testSingleHyphenValue() new Option('a', 'alpha', Getopt::REQUIRED_ARGUMENT) )); - $parser->parse('-a -'); + $result = $parser->parse('-a -'); - $options = $parser->getOptions(); - $this->assertEquals('-', $options['a']); - $operands = $parser->getOperands(); + $this->assertEquals('-', $result->getOption('a')); + $operands = $result->getOperands(); $this->assertCount(0, $operands); - $parser->parse('--alpha -'); + $result = $parser->parse('--alpha -'); - $options = $parser->getOptions(); - $this->assertEquals('-', $options['a']); - $operands = $parser->getOperands(); + $this->assertEquals('-', $result->getOption('a')); + $operands = $result->getOperands(); $this->assertCount(0, $operands); } @@ -302,11 +296,10 @@ public function testSingleHyphenOperand() $parser = new CommandLineParser(array( new Option('a', null, Getopt::REQUIRED_ARGUMENT) )); - $parser->parse('-a 0 -'); + $result = $parser->parse('-a 0 -'); - $options = $parser->getOptions(); - $this->assertEquals('0', $options['a']); - $operands = $parser->getOperands(); + $this->assertEquals('0', $result->getOption('a')); + $operands = $result->getOperands(); $this->assertCount(1, $operands); $this->assertEquals('-', $operands[0]); } @@ -321,12 +314,11 @@ public function testParseWithArgumentValidation() $optionC = new Option('c', null, Getopt::OPTIONAL_ARGUMENT); $optionC->setArgument(new Argument(null, $validation)); $parser = new CommandLineParser(array($optionA, $optionB, $optionC)); - $parser->parse('-a 1 -b 2 -c'); + $result = $parser->parse('-a 1 -b 2 -c'); - $options = $parser->getOptions(); - $this->assertSame('1', $options['a']); - $this->assertSame('2', $options['b']); - $this->assertSame(1, $options['c']); + $this->assertSame('1', $result->getOption('a')); + $this->assertSame('2', $result->getOption('b')); + $this->assertSame(1, $result->getOption('c')); } public function testParseInvalidArgument() diff --git a/test/Ulrichsg/Getopt/DefaultHelpTextFormatterTest.php b/test/Ulrichsg/Getopt/DefaultHelpTextFormatterTest.php new file mode 100644 index 0000000..1a66c58 --- /dev/null +++ b/test/Ulrichsg/Getopt/DefaultHelpTextFormatterTest.php @@ -0,0 +1,66 @@ +setDescription('Short and long options with no argument'); + + $option2 = new Option(null, 'beta', Getopt::OPTIONAL_ARGUMENT); + $option2->setDescription('Long option only with an optional argument'); + + $option3 = new Option('c', null, Getopt::REQUIRED_ARGUMENT); + $option3->setDescription('Short option only with a mandatory argument'); + + $options = array($option1, $option2, $option3); + + $expected = "Usage: test [options] [operands]\n"; + $expected .= "Options:\n"; + $expected .= " -a, --alpha Short and long options with no argument\n"; + $expected .= " --beta [] Long option only with an optional argument\n"; + $expected .= " -c Short option only with a mandatory argument\n"; + + $formatter = new DefaultHelpTextFormatter(); + $formatter->setScriptName('test'); + $this->assertEquals($expected, $formatter->getHelpText($options, 20)); + } + + public function testHelpTextWithoutDescriptions() + { + $options = array( + new Option('a', 'alpha', Getopt::NO_ARGUMENT), + new Option(null, 'beta', Getopt::OPTIONAL_ARGUMENT), + new Option('c', null, Getopt::REQUIRED_ARGUMENT) + ); + + $expected = "Usage: test [options] [operands]\n"; + $expected .= "Options:\n"; + $expected .= " -a, --alpha \n"; + $expected .= " --beta [] \n"; + $expected .= " -c \n"; + + $formatter = new DefaultHelpTextFormatter(); + $formatter->setScriptName('test'); + $this->assertEquals($expected, $formatter->getHelpText($options)); + } + + public function testHelpTextNoOptions() + { + $formatter = new DefaultHelpTextFormatter(); + $expected = "Usage: [options] [operands]\nOptions:\n"; + $this->assertSame($expected, $formatter->getHelpText(array())); + } + + public function testHelpTextWithCustomBanner() + { + $formatter = new DefaultHelpTextFormatter(); + $formatter->setBanner("My custom Banner %s\n"); + $this->assertSame("My custom Banner \nOptions:\n", $formatter->getHelpText(array())); + + $formatter->setScriptName('test'); + $this->assertSame("My custom Banner test\nOptions:\n", $formatter->getHelpText(array())); + } +} diff --git a/test/Ulrichsg/Getopt/GetoptTest.php b/test/Ulrichsg/Getopt/GetoptTest.php index 0fc13f4..6ea437f 100644 --- a/test/Ulrichsg/Getopt/GetoptTest.php +++ b/test/Ulrichsg/Getopt/GetoptTest.php @@ -16,10 +16,10 @@ public function testAddOptions() ) ); - $getopt->parse('-a aparam -s sparam --long longparam'); - $this->assertEquals('aparam', $getopt->getOption('a')); - $this->assertEquals('longparam', $getopt->getOption('long')); - $this->assertEquals('sparam', $getopt->getOption('s')); + $result = $getopt->parse('-a aparam -s sparam --long longparam'); + $this->assertEquals('aparam', $result->getOption('a')); + $this->assertEquals('longparam', $result->getOption('long')); + $this->assertEquals('sparam', $result->getOption('s')); } public function testAddOptionsChooseShortOrLongAutomatically() @@ -32,9 +32,9 @@ public function testAddOptionsChooseShortOrLongAutomatically() ) ); - $getopt->parse('-s --long longparam'); - $this->assertEquals('longparam', $getopt->getOption('long')); - $this->assertEquals('1', $getopt->getOption('s')); + $result = $getopt->parse('-s --long longparam'); + $this->assertEquals('longparam', $result->getOption('long')); + $this->assertEquals('1', $result->getOption('s')); } public function testAddOptionsUseDefaultArgumentType() @@ -46,11 +46,11 @@ public function testAddOptionsUseDefaultArgumentType() ) ); - $getopt->parse('-l something'); - $this->assertEquals('something', $getopt->getOption('l')); + $result = $getopt->parse('-l something'); + $this->assertEquals('something', $result->getOption('l')); - $getopt->parse('--long someOtherThing'); - $this->assertEquals('someOtherThing', $getopt->getOption('long')); + $result = $getopt->parse('--long someOtherThing'); + $this->assertEquals('someOtherThing', $result->getOption('long')); } public function testAddOptionsFailsOnInvalidArgument() @@ -68,10 +68,10 @@ public function testAddOptionsOverwritesExistingOptions() $getopt->addOptions(array( array('a', null, Getopt::NO_ARGUMENT) )); - $getopt->parse('-a foo'); + $result = $getopt->parse('-a foo'); - $this->assertEquals(1, $getopt->getOption('a')); - $this->assertEquals('foo', $getopt->getOperand(0)); + $this->assertEquals(1, $result->getOption('a')); + $this->assertEquals('foo', $result->getOperand(0)); } public function testAddOptionsFailsOnConflict() @@ -91,51 +91,8 @@ public function testParseUsesGlobalArgvWhenNoneGiven() $argv = array('foo.php', '-a'); $getopt = new Getopt('a'); - $getopt->parse(); - $this->assertEquals(1, $getopt->getOption('a')); - } - - public function testAccessMethods() - { - $getopt = new Getopt('a'); - $getopt->parse('-a foo'); - - $options = $getopt->getOptions(); - $this->assertCount(1, $options); - $this->assertEquals(1, $options['a']); - $this->assertEquals(1, $getopt->getOption('a')); - - $operands = $getopt->getOperands(); - $this->assertCount(1, $operands); - $this->assertEquals('foo', $operands[0]); - $this->assertEquals('foo', $getopt->getOperand(0)); - } - - public function testCountable() - { - $getopt = new Getopt('abc'); - $getopt->parse('-abc'); - $this->assertEquals(3, count($getopt)); - } - - public function testArrayAccess() - { - $getopt = new Getopt('q'); - $getopt->parse('-q'); - $this->assertEquals(1, $getopt['q']); - } - - public function testIterable() - { - $getopt = new Getopt(array( - array(null, 'alpha', Getopt::NO_ARGUMENT), - array('b', 'beta', Getopt::REQUIRED_ARGUMENT) - )); - $getopt->parse('--alpha -b foo'); - $expected = array('alpha' => 1, 'b' => 'foo'); // 'beta' should not occur - foreach ($getopt as $option => $value) { - $this->assertEquals($expected[$option], $value); - } + $result = $getopt->parse(); + $this->assertEquals(1, $result->getOption('a')); } public function testHelpText() @@ -157,43 +114,4 @@ public function testHelpText() $this->assertEquals($expected, $getopt->getHelpText()); } - - public function testHelpTextWithoutDescriptions() - { - $getopt = new Getopt(array( - array('a', 'alpha', Getopt::NO_ARGUMENT), - array(null, 'beta', Getopt::OPTIONAL_ARGUMENT), - array('c', null, Getopt::REQUIRED_ARGUMENT) - )); - $getopt->parse(''); - - $script = $_SERVER['PHP_SELF']; - - $expected = "Usage: $script [options] [operands]\n"; - $expected .= "Options:\n"; - $expected .= " -a, --alpha \n"; - $expected .= " --beta [] \n"; - $expected .= " -c \n"; - - $this->assertEquals($expected, $getopt->getHelpText()); - } - - public function testHelpTextNoParse() - { - $getopt = new Getopt(); - $expected = "Usage: [options] [operands]\nOptions:\n"; - $this->assertSame($expected, $getopt->getHelpText()); - } - - public function testHelpTextWithCustomBanner() - { - $script = $_SERVER['PHP_SELF']; - - $getopt = new Getopt(); - $getopt->setBanner("My custom Banner %s\n"); - $this->assertSame("My custom Banner \nOptions:\n", $getopt->getHelpText()); - - $getopt->parse(''); - $this->assertSame("My custom Banner $script\nOptions:\n", $getopt->getHelpText()); - } } diff --git a/test/Ulrichsg/Getopt/Getoptv2Test.php b/test/Ulrichsg/Getopt/Getoptv2Test.php new file mode 100644 index 0000000..6b07dce --- /dev/null +++ b/test/Ulrichsg/Getopt/Getoptv2Test.php @@ -0,0 +1,49 @@ +parse('-a foo'); + + $options = $getopt->getOptions(); + $this->assertCount(1, $options); + $this->assertEquals(1, $options['a']); + $this->assertEquals(1, $getopt->getOption('a')); + + $operands = $getopt->getOperands(); + $this->assertCount(1, $operands); + $this->assertEquals('foo', $operands[0]); + $this->assertEquals('foo', $getopt->getOperand(0)); + } + + public function testCountable() + { + $getopt = new Getoptv2('abc'); + $getopt->parse('-abc'); + $this->assertEquals(3, count($getopt)); + } + + public function testArrayAccess() + { + $getopt = new Getoptv2('q'); + $getopt->parse('-q'); + $this->assertEquals(1, $getopt['q']); + } + + public function testIterable() + { + $getopt = new Getoptv2(array( + array(null, 'alpha', Getopt::NO_ARGUMENT), + array('b', 'beta', Getopt::REQUIRED_ARGUMENT) + )); + $getopt->parse('--alpha -b foo'); + $expected = array('alpha' => 1, 'b' => 'foo'); // 'beta' should not occur + foreach ($getopt as $option => $value) { + $this->assertEquals($expected[$option], $value); + } + } +} diff --git a/test/Ulrichsg/Getopt/OptionTest.php b/test/Ulrichsg/Getopt/OptionTest.php index 2c88107..a59d30a 100644 --- a/test/Ulrichsg/Getopt/OptionTest.php +++ b/test/Ulrichsg/Getopt/OptionTest.php @@ -11,35 +11,72 @@ public function testConstruct() $this->assertEquals('az-AZ09_', $option->long()); $this->assertEquals(Getopt::OPTIONAL_ARGUMENT, $option->mode()); } - - public function testConstructEmptyOption() + + /** @dataProvider dataConstructFails */ + public function testConstructFails($short, $long, $mode) { $this->setExpectedException('InvalidArgumentException'); - new Option(null, null, Getopt::NO_ARGUMENT); + new Option($short, $long, $mode); + } + + public function dataConstructFails() + { + return array( + array(null, null, Getopt::NO_ARGUMENT), // long and short are both empty + array('?', null, Getopt::NO_ARGUMENT), // short name must be letter or digit + array(null, 'öption', Getopt::NO_ARGUMENT), // long name may contain only alphanumeric chars, _ and - + array('a', null, 'no_argument'), // invalid type + array(null, 'a', Getopt::NO_ARGUMENT) // long name must be at least 2 characters long + ); } - public function testConstructNoLetter() + /** @dataProvider dataMatches */ + public function testMatches(Option $option, $string, $matches) { - $this->setExpectedException('InvalidArgumentException'); - new Option('?', null, Getopt::NO_ARGUMENT); + $this->assertEquals($matches, $option->matches($string)); } - public function testConstructInvalidCharacter() + public function dataMatches() { - $this->setExpectedException('InvalidArgumentException'); - new Option(null, 'öption', Getopt::NO_ARGUMENT); + return array( + array(new Option('v', null), 'v', true), + array(new Option(null, 'verbose'), 'verbose', true), + array(new Option(null, 'verbose'), 'v', false), + array(new Option('v', 'verbose'), 'v', true) + ); } - public function testConstructInvalidArgumentType() + /** @dataProvider dataEquals */ + public function testEquals(Option $first, Option $second, $equals) { - $this->setExpectedException('InvalidArgumentException'); - new Option('a', null, 'no_argument'); + $this->assertEquals($equals, $first->equals($second)); } - public function testConstructLongOptionTooShort() + public function dataEquals() { - $this->setExpectedException('InvalidArgumentException'); - new Option(null, 'a', Getopt::REQUIRED_ARGUMENT); + return array( + array(new Option('v', null), new Option('v', null), true), + array(new Option(null, 'verbose'), new Option(null, 'verbose'), true), + array(new Option('v', 'verbose'), new Option('v', 'verbose'), true), + array(new Option('v', 'verbose'), new Option('v', 'version'), false), + array(new Option('v', 'verbose'), new Option('V', 'verbose'), false) + ); + } + + /** @dataProvider dataConflictsWith */ + public function testConflictsWith(Option $first, Option $second, $conflict) + { + $this->assertEquals($conflict, $first->conflictsWith($second)); + } + + public function dataConflictsWith() + { + return array( + array(new Option('v', 'verbose'), new Option('v', 'version'), true), + array(new Option('v', 'verbose'), new Option('v', 'verbose'), false), + array(new Option('v', 'verbose'), new Option('v', null), true), + array(new Option('v', null), new Option('v', null), false) + ); } public function testSetArgument() @@ -56,17 +93,17 @@ public function testSetArgumentWrongMode() $option->setArgument(new Argument()); } - public function testSetDefaultValue() - { - $option = new Option('a', null, Getopt::OPTIONAL_ARGUMENT); - $this->assertEquals($option, $option->setDefaultValue(10)); - $this->assertEquals(10, $option->getArgument()->getDefaultValue()); - } + public function testSetDefaultValue() + { + $option = new Option('a', null, Getopt::OPTIONAL_ARGUMENT); + $this->assertEquals($option, $option->setDefaultValue(10)); + $this->assertEquals(10, $option->getArgument()->getDefaultValue()); + } - public function testSetValidation() - { - $option = new Option('a', null, Getopt::OPTIONAL_ARGUMENT); - $this->assertEquals($option, $option->setValidation('is_numeric')); - $this->assertTrue($option->getArgument()->hasValidation()); - } + public function testSetValidation() + { + $option = new Option('a', null, Getopt::OPTIONAL_ARGUMENT); + $this->assertEquals($option, $option->setValidation('is_numeric')); + $this->assertTrue($option->getArgument()->hasValidation()); + } }