diff --git a/library/Zend/Console/Getopt.php b/library/Zend/Console/Getopt.php index b986a005a24..653acc77f0c 100644 --- a/library/Zend/Console/Getopt.php +++ b/library/Zend/Console/Getopt.php @@ -66,17 +66,6 @@ * * Example: 'abc:' means options '-a', '-b', and '-c' * are legal, and the latter requires a string parameter. - * - * @todo Handle flags that implicitly print usage message, e.g. --help - * - * @todo Enable user to specify header and footer content in the help message. - * - * @todo Feature request to handle option interdependencies. - * e.g. if -b is specified, -a must be specified or else the - * usage is invalid. - * - * @todo Feature request to implement callbacks. - * e.g. if -a is specified, run function 'handleOptionA'(). */ class Getopt { @@ -191,6 +180,13 @@ class Getopt */ protected $parsed = false; + /** + * A list of callbacks to call when a particular option is present. + * + * @var array + */ + protected $optionCallbacks = array(); + /** * The constructor takes one to three parameters. * @@ -389,7 +385,7 @@ public function addRules($rules) $this->_addRulesModeZend($rules); break; } - // intentional fallthrough + // intentional fallthrough case self::MODE_GNU: $this->_addRulesModeGnu($rules); break; @@ -591,8 +587,8 @@ public function getUsageMessage() } foreach ($lines as $linepart) { $usage .= sprintf("%s %s\n", - str_pad($linepart['name'], $maxLen), - $linepart['help']); + str_pad($linepart['name'], $maxLen), + $linepart['help']); } return $usage; } @@ -676,7 +672,7 @@ public function parse() } if (substr($argv[0], 0, 2) == '--') { $this->_parseLongOption($argv); - } elseif (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) { + } elseif (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) { $this->_parseShortOptionCluster($argv); } elseif ($this->getoptConfig[self::CONFIG_PARSEALL]) { $this->remainingArgs[] = array_shift($argv); @@ -690,9 +686,52 @@ public function parse() } } $this->parsed = true; + + //go through parsed args and process callbacks + $this->triggerCallbacks(); + + return $this; + } + + /** + * @param string $option The name of the property which, if present, will call the passed + * callback with the value of this parameter. + * @param callable $callback The callback that will be called for this option. The first + * parameter will be the value of getOption($option), the second + * parameter will be a reference to $this object. If the callback returns + * false then an Exception\RuntimeException will be thrown indicating that + * there is a parse issue with this option. + * + * @return \Zend\Console\Getopt Fluent interface. + */ + public function setOptionCallback($option, \Closure $callback) + { + $this->optionCallbacks[$option] = $callback; + return $this; } + /** + * Triggers all the registered callbacks. + */ + protected function triggerCallbacks() + { + foreach ($this->optionCallbacks as $option => $callback) { + if (null === $this->getOption($option)) { + continue; + } + //make sure we've resolved the alias, if using one + if (isset($this->ruleMap[$option]) && $option = $this->ruleMap[$option]) { + if (false === $callback($this->getOption($option), $this)) { + throw new Exception\RuntimeException( + "The option $option is invalid. See usage.", + $this->getUsageMessage() + ); + } + } + } + } + /** * Parse command-line arguments for a single long option. * A long option is preceded by a double '--' character. @@ -754,7 +793,7 @@ protected function _parseSingleOption($flag, &$argv) throw new Exception\RuntimeException( "Option \"$flag\" is not recognized.", $this->getUsageMessage() - ); + ); } // Magic methods in future will use this mark as real flag value @@ -777,7 +816,7 @@ protected function _parseSingleOption($flag, &$argv) throw new Exception\RuntimeException( "Option \"$flag\" requires a parameter.", $this->getUsageMessage() - ); + ); } break; case 'optional': @@ -864,8 +903,8 @@ protected function _setSingleOptionValue($flag, $value) protected function _setBooleanFlagValue($flag) { $this->options[$flag] = array_key_exists($flag, $this->options) - ? (int) $this->options[$flag] + 1 - : true; + ? (int) $this->options[$flag] + 1 + : true; } /** @@ -1018,4 +1057,4 @@ protected function _addRulesModeZend($rules) $this->rules[$mainFlag] = $rule; } } -} +} \ No newline at end of file diff --git a/tests/ZendTest/Console/GetoptTest.php b/tests/ZendTest/Console/GetoptTest.php index 7709c6f5e5b..b04d4c21303 100644 --- a/tests/ZendTest/Console/GetoptTest.php +++ b/tests/ZendTest/Console/GetoptTest.php @@ -647,4 +647,74 @@ public function testGetoptRaiseExceptionForNumericOptionsIfAneHandlerIsSpecified $this->setExpectedException('\Zend\Console\Exception\RuntimeException'); $opts->parse(); } + + public function testOptionCallback() + { + $opts = new Getopt('a', array('-a')); + $testVal = null; + $opts->setOptionCallback('a', function($val, $opts) use (&$testVal) { + $testVal = $val; + }); + $opts->parse(); + + $this->assertTrue($testVal); + } + + public function testOptionCallbackAddedByAlias() + { + $opts = new Getopt(array( + 'a|apples|apple=s' => "APPLES", + 'b|bears|bear=s' => "BEARS" + ), array( + '--apples=Gala', + '--bears=Grizzly' + )); + + $appleCallbackCalled = null; + $bearCallbackCalled = null; + + $opts->setOptionCallback('a', function($val) use (&$appleCallbackCalled){ + $appleCallbackCalled = $val; + }); + + $opts->setOptionCallback('bear', function($val) use (&$bearCallbackCalled){ + $bearCallbackCalled = $val; + }); + + $opts->parse(); + + $this->assertSame('Gala', $appleCallbackCalled); + $this->assertSame('Grizzly', $bearCallbackCalled); + } + + public function testOptionCallbackNotCalled() + { + $opts = new Getopt(array( + 'a|apples|apple' => "APPLES", + 'b|bears|bear' => "BEARS" + ), array( + '--apples=Gala' + )); + + $bearCallbackCalled = null; + + $opts->setOptionCallback('bear', function($val) use (&$bearCallbackCalled){ + $bearCallbackCalled = $val; + }); + + $opts->parse(); + + $this->assertNull($bearCallbackCalled); + } + + /** + * @expectedException \Zend\Console\Exception\RuntimeException + * @expectedExceptionMessage The option x is invalid. See usage. + */ + public function testOptionCallbackReturnsFallsAndThrowException() + { + $opts = new Getopt('x', array('-x')); + $opts->setOptionCallback('x', function(){return false;}); + $opts->parse(); + } }