1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Console; 11 12/** 13 * Getopt is a class to parse options for command-line 14 * applications. 15 * 16 * Terminology: 17 * Argument: an element of the argv array. This may be part of an option, 18 * or it may be a non-option command-line argument. 19 * Flag: the letter or word set off by a '-' or '--'. Example: in '--output filename', 20 * '--output' is the flag. 21 * Parameter: the additional argument that is associated with the option. 22 * Example: in '--output filename', the 'filename' is the parameter. 23 * Option: the combination of a flag and its parameter, if any. 24 * Example: in '--output filename', the whole thing is the option. 25 * 26 * The following features are supported: 27 * 28 * - Short flags like '-a'. Short flags are preceded by a single 29 * dash. Short flags may be clustered e.g. '-abc', which is the 30 * same as '-a' '-b' '-c'. 31 * - Long flags like '--verbose'. Long flags are preceded by a 32 * double dash. Long flags may not be clustered. 33 * - Options may have a parameter, e.g. '--output filename'. 34 * - Parameters for long flags may also be set off with an equals sign, 35 * e.g. '--output=filename'. 36 * - Parameters for long flags may be checked as string, word, or integer. 37 * - Automatic generation of a helpful usage message. 38 * - Signal end of options with '--'; subsequent arguments are treated 39 * as non-option arguments, even if they begin with '-'. 40 * - Raise exception Zend\Console\Exception\* in several cases 41 * when invalid flags or parameters are given. Usage message is 42 * returned in the exception object. 43 * 44 * The format for specifying options uses a PHP associative array. 45 * The key is has the format of a list of pipe-separated flag names, 46 * followed by an optional '=' to indicate a required parameter or 47 * '-' to indicate an optional parameter. Following that, the type 48 * of parameter may be specified as 's' for string, 'w' for word, 49 * or 'i' for integer. 50 * 51 * Examples: 52 * - 'user|username|u=s' this means '--user' or '--username' or '-u' 53 * are synonyms, and the option requires a string parameter. 54 * - 'p=i' this means '-p' requires an integer parameter. No synonyms. 55 * - 'verbose|v-i' this means '--verbose' or '-v' are synonyms, and 56 * they take an optional integer parameter. 57 * - 'help|h' this means '--help' or '-h' are synonyms, and 58 * they take no parameter. 59 * 60 * The values in the associative array are strings that are used as 61 * brief descriptions of the options when printing a usage message. 62 * 63 * The simpler format for specifying options used by PHP's getopt() 64 * function is also supported. This is similar to GNU getopt and shell 65 * getopt format. 66 * 67 * Example: 'abc:' means options '-a', '-b', and '-c' 68 * are legal, and the latter requires a string parameter. 69 */ 70class Getopt 71{ 72 /** 73 * The options for a given application can be in multiple formats. 74 * modeGnu is for traditional 'ab:c:' style getopt format. 75 * modeZend is for a more structured format. 76 */ 77 const MODE_ZEND = 'zend'; 78 const MODE_GNU = 'gnu'; 79 80 /** 81 * Constant tokens for various symbols used in the mode_zend 82 * rule format. 83 */ 84 const PARAM_REQUIRED = '='; 85 const PARAM_OPTIONAL = '-'; 86 const TYPE_STRING = 's'; 87 const TYPE_WORD = 'w'; 88 const TYPE_INTEGER = 'i'; 89 const TYPE_NUMERIC_FLAG = '#'; 90 91 /** 92 * These are constants for optional behavior of this class. 93 * ruleMode is either 'zend' or 'gnu' or a user-defined mode. 94 * dashDash is true if '--' signifies the end of command-line options. 95 * ignoreCase is true if '--opt' and '--OPT' are implicitly synonyms. 96 * parseAll is true if all options on the command line should be parsed, regardless of 97 * whether an argument appears before them. 98 */ 99 const CONFIG_RULEMODE = 'ruleMode'; 100 const CONFIG_DASHDASH = 'dashDash'; 101 const CONFIG_IGNORECASE = 'ignoreCase'; 102 const CONFIG_PARSEALL = 'parseAll'; 103 const CONFIG_CUMULATIVE_PARAMETERS = 'cumulativeParameters'; 104 const CONFIG_CUMULATIVE_FLAGS = 'cumulativeFlags'; 105 const CONFIG_PARAMETER_SEPARATOR = 'parameterSeparator'; 106 const CONFIG_FREEFORM_FLAGS = 'freeformFlags'; 107 const CONFIG_NUMERIC_FLAGS = 'numericFlags'; 108 109 /** 110 * Defaults for getopt configuration are: 111 * ruleMode is 'zend' format, 112 * dashDash (--) token is enabled, 113 * ignoreCase is not enabled, 114 * parseAll is enabled, 115 * cumulative parameters are disabled, 116 * this means that subsequent options overwrite the parameter value, 117 * cumulative flags are disable, 118 * freeform flags are disable. 119 */ 120 protected $getoptConfig = array( 121 self::CONFIG_RULEMODE => self::MODE_ZEND, 122 self::CONFIG_DASHDASH => true, 123 self::CONFIG_IGNORECASE => false, 124 self::CONFIG_PARSEALL => true, 125 self::CONFIG_CUMULATIVE_PARAMETERS => false, 126 self::CONFIG_CUMULATIVE_FLAGS => false, 127 self::CONFIG_PARAMETER_SEPARATOR => null, 128 self::CONFIG_FREEFORM_FLAGS => false, 129 self::CONFIG_NUMERIC_FLAGS => false 130 ); 131 132 /** 133 * Stores the command-line arguments for the calling application. 134 * 135 * @var array 136 */ 137 protected $argv = array(); 138 139 /** 140 * Stores the name of the calling application. 141 * 142 * @var string 143 */ 144 protected $progname = ''; 145 146 /** 147 * Stores the list of legal options for this application. 148 * 149 * @var array 150 */ 151 protected $rules = array(); 152 153 /** 154 * Stores alternate spellings of legal options. 155 * 156 * @var array 157 */ 158 protected $ruleMap = array(); 159 160 /** 161 * Stores options given by the user in the current invocation 162 * of the application, as well as parameters given in options. 163 * 164 * @var array 165 */ 166 protected $options = array(); 167 168 /** 169 * Stores the command-line arguments other than options. 170 * 171 * @var array 172 */ 173 protected $remainingArgs = array(); 174 175 /** 176 * State of the options: parsed or not yet parsed? 177 * 178 * @var bool 179 */ 180 protected $parsed = false; 181 182 /** 183 * A list of callbacks to call when a particular option is present. 184 * 185 * @var array 186 */ 187 protected $optionCallbacks = array(); 188 189 /** 190 * The constructor takes one to three parameters. 191 * 192 * The first parameter is $rules, which may be a string for 193 * gnu-style format, or a structured array for Zend-style format. 194 * 195 * The second parameter is $argv, and it is optional. If not 196 * specified, $argv is inferred from the global argv. 197 * 198 * The third parameter is an array of configuration parameters 199 * to control the behavior of this instance of Getopt; it is optional. 200 * 201 * @param array $rules 202 * @param array $argv 203 * @param array $getoptConfig 204 * @throws Exception\InvalidArgumentException 205 */ 206 public function __construct($rules, $argv = null, $getoptConfig = array()) 207 { 208 if (!isset($_SERVER['argv'])) { 209 $errorDescription = (ini_get('register_argc_argv') == false) 210 ? "argv is not available, because ini option 'register_argc_argv' is set Off" 211 : '$_SERVER["argv"] is not set, but Zend\Console\Getopt cannot work without this information.'; 212 throw new Exception\InvalidArgumentException($errorDescription); 213 } 214 215 $this->progname = $_SERVER['argv'][0]; 216 $this->setOptions($getoptConfig); 217 $this->addRules($rules); 218 if (!is_array($argv)) { 219 $argv = array_slice($_SERVER['argv'], 1); 220 } 221 if (isset($argv)) { 222 $this->addArguments((array) $argv); 223 } 224 } 225 226 /** 227 * Return the state of the option seen on the command line of the 228 * current application invocation. This function returns true, or the 229 * parameter to the option, if any. If the option was not given, 230 * this function returns null. 231 * 232 * The magic __get method works in the context of naming the option 233 * as a virtual member of this class. 234 * 235 * @param string $key 236 * @return string 237 */ 238 public function __get($key) 239 { 240 return $this->getOption($key); 241 } 242 243 /** 244 * Test whether a given option has been seen. 245 * 246 * @param string $key 247 * @return bool 248 */ 249 public function __isset($key) 250 { 251 $this->parse(); 252 if (isset($this->ruleMap[$key])) { 253 $key = $this->ruleMap[$key]; 254 return isset($this->options[$key]); 255 } 256 return false; 257 } 258 259 /** 260 * Set the value for a given option. 261 * 262 * @param string $key 263 * @param string $value 264 */ 265 public function __set($key, $value) 266 { 267 $this->parse(); 268 if (isset($this->ruleMap[$key])) { 269 $key = $this->ruleMap[$key]; 270 $this->options[$key] = $value; 271 } 272 } 273 274 /** 275 * Return the current set of options and parameters seen as a string. 276 * 277 * @return string 278 */ 279 public function __toString() 280 { 281 return $this->toString(); 282 } 283 284 /** 285 * Unset an option. 286 * 287 * @param string $key 288 */ 289 public function __unset($key) 290 { 291 $this->parse(); 292 if (isset($this->ruleMap[$key])) { 293 $key = $this->ruleMap[$key]; 294 unset($this->options[$key]); 295 } 296 } 297 298 /** 299 * Define additional command-line arguments. 300 * These are appended to those defined when the constructor was called. 301 * 302 * @param array $argv 303 * @throws Exception\InvalidArgumentException When not given an array as parameter 304 * @return self 305 */ 306 public function addArguments($argv) 307 { 308 if (!is_array($argv)) { 309 throw new Exception\InvalidArgumentException("Parameter #1 to addArguments should be an array"); 310 } 311 $this->argv = array_merge($this->argv, $argv); 312 $this->parsed = false; 313 return $this; 314 } 315 316 /** 317 * Define full set of command-line arguments. 318 * These replace any currently defined. 319 * 320 * @param array $argv 321 * @throws Exception\InvalidArgumentException When not given an array as parameter 322 * @return self 323 */ 324 public function setArguments($argv) 325 { 326 if (!is_array($argv)) { 327 throw new Exception\InvalidArgumentException("Parameter #1 to setArguments should be an array"); 328 } 329 $this->argv = $argv; 330 $this->parsed = false; 331 return $this; 332 } 333 334 /** 335 * Define multiple configuration options from an associative array. 336 * These are not program options, but properties to configure 337 * the behavior of Zend\Console\Getopt. 338 * 339 * @param array $getoptConfig 340 * @return self 341 */ 342 public function setOptions($getoptConfig) 343 { 344 if (isset($getoptConfig)) { 345 foreach ($getoptConfig as $key => $value) { 346 $this->setOption($key, $value); 347 } 348 } 349 return $this; 350 } 351 352 /** 353 * Define one configuration option as a key/value pair. 354 * These are not program options, but properties to configure 355 * the behavior of Zend\Console\Getopt. 356 * 357 * @param string $configKey 358 * @param string $configValue 359 * @return self 360 */ 361 public function setOption($configKey, $configValue) 362 { 363 if ($configKey !== null) { 364 $this->getoptConfig[$configKey] = $configValue; 365 } 366 return $this; 367 } 368 369 /** 370 * Define additional option rules. 371 * These are appended to the rules defined when the constructor was called. 372 * 373 * @param array $rules 374 * @return self 375 */ 376 public function addRules($rules) 377 { 378 $ruleMode = $this->getoptConfig['ruleMode']; 379 switch ($this->getoptConfig['ruleMode']) { 380 case self::MODE_ZEND: 381 if (is_array($rules)) { 382 $this->_addRulesModeZend($rules); 383 break; 384 } 385 // intentional fallthrough 386 case self::MODE_GNU: 387 $this->_addRulesModeGnu($rules); 388 break; 389 default: 390 /** 391 * Call addRulesModeFoo() for ruleMode 'foo'. 392 * The developer should subclass Getopt and 393 * provide this method. 394 */ 395 $method = '_addRulesMode' . ucfirst($ruleMode); 396 $this->$method($rules); 397 } 398 $this->parsed = false; 399 return $this; 400 } 401 402 /** 403 * Return the current set of options and parameters seen as a string. 404 * 405 * @return string 406 */ 407 public function toString() 408 { 409 $this->parse(); 410 $s = array(); 411 foreach ($this->options as $flag => $value) { 412 $s[] = $flag . '=' . ($value === true ? 'true' : $value); 413 } 414 return implode(' ', $s); 415 } 416 417 /** 418 * Return the current set of options and parameters seen 419 * as an array of canonical options and parameters. 420 * 421 * Clusters have been expanded, and option aliases 422 * have been mapped to their primary option names. 423 * 424 * @return array 425 */ 426 public function toArray() 427 { 428 $this->parse(); 429 $s = array(); 430 foreach ($this->options as $flag => $value) { 431 $s[] = $flag; 432 if ($value !== true) { 433 $s[] = $value; 434 } 435 } 436 return $s; 437 } 438 439 /** 440 * Return the current set of options and parameters seen in Json format. 441 * 442 * @return string 443 */ 444 public function toJson() 445 { 446 $this->parse(); 447 $j = array(); 448 foreach ($this->options as $flag => $value) { 449 $j['options'][] = array( 450 'option' => array( 451 'flag' => $flag, 452 'parameter' => $value 453 ) 454 ); 455 } 456 457 $json = \Zend\Json\Json::encode($j); 458 return $json; 459 } 460 461 /** 462 * Return the current set of options and parameters seen in XML format. 463 * 464 * @return string 465 */ 466 public function toXml() 467 { 468 $this->parse(); 469 $doc = new \DomDocument('1.0', 'utf-8'); 470 $optionsNode = $doc->createElement('options'); 471 $doc->appendChild($optionsNode); 472 foreach ($this->options as $flag => $value) { 473 $optionNode = $doc->createElement('option'); 474 $optionNode->setAttribute('flag', utf8_encode($flag)); 475 if ($value !== true) { 476 $optionNode->setAttribute('parameter', utf8_encode($value)); 477 } 478 $optionsNode->appendChild($optionNode); 479 } 480 $xml = $doc->saveXML(); 481 return $xml; 482 } 483 484 /** 485 * Return a list of options that have been seen in the current argv. 486 * 487 * @return array 488 */ 489 public function getOptions() 490 { 491 $this->parse(); 492 return array_keys($this->options); 493 } 494 495 /** 496 * Return the state of the option seen on the command line of the 497 * current application invocation. 498 * 499 * This function returns true, or the parameter value to the option, if any. 500 * If the option was not given, this function returns false. 501 * 502 * @param string $flag 503 * @return mixed 504 */ 505 public function getOption($flag) 506 { 507 $this->parse(); 508 if ($this->getoptConfig[self::CONFIG_IGNORECASE]) { 509 $flag = strtolower($flag); 510 } 511 if (isset($this->ruleMap[$flag])) { 512 $flag = $this->ruleMap[$flag]; 513 if (isset($this->options[$flag])) { 514 return $this->options[$flag]; 515 } 516 } 517 return; 518 } 519 520 /** 521 * Return the arguments from the command-line following all options found. 522 * 523 * @return array 524 */ 525 public function getRemainingArgs() 526 { 527 $this->parse(); 528 return $this->remainingArgs; 529 } 530 531 public function getArguments() 532 { 533 $result = $this->getRemainingArgs(); 534 foreach ($this->getOptions() as $option) { 535 $result[$option] = $this->getOption($option); 536 } 537 return $result; 538 } 539 540 /** 541 * Return a useful option reference, formatted for display in an 542 * error message. 543 * 544 * Note that this usage information is provided in most Exceptions 545 * generated by this class. 546 * 547 * @return string 548 */ 549 public function getUsageMessage() 550 { 551 $usage = "Usage: {$this->progname} [ options ]\n"; 552 $maxLen = 20; 553 $lines = array(); 554 foreach ($this->rules as $rule) { 555 if (isset($rule['isFreeformFlag'])) { 556 continue; 557 } 558 $flags = array(); 559 if (is_array($rule['alias'])) { 560 foreach ($rule['alias'] as $flag) { 561 $flags[] = (strlen($flag) == 1 ? '-' : '--') . $flag; 562 } 563 } 564 $linepart['name'] = implode('|', $flags); 565 if (isset($rule['param']) && $rule['param'] != 'none') { 566 $linepart['name'] .= ' '; 567 switch ($rule['param']) { 568 case 'optional': 569 $linepart['name'] .= "[ <{$rule['paramType']}> ]"; 570 break; 571 case 'required': 572 $linepart['name'] .= "<{$rule['paramType']}>"; 573 break; 574 } 575 } 576 if (strlen($linepart['name']) > $maxLen) { 577 $maxLen = strlen($linepart['name']); 578 } 579 $linepart['help'] = ''; 580 if (isset($rule['help'])) { 581 $linepart['help'] .= $rule['help']; 582 } 583 $lines[] = $linepart; 584 } 585 foreach ($lines as $linepart) { 586 $usage .= sprintf( 587 "%s %s\n", 588 str_pad($linepart['name'], $maxLen), 589 $linepart['help'] 590 ); 591 } 592 return $usage; 593 } 594 595 /** 596 * Define aliases for options. 597 * 598 * The parameter $aliasMap is an associative array 599 * mapping option name (short or long) to an alias. 600 * 601 * @param array $aliasMap 602 * @throws Exception\ExceptionInterface 603 * @return self 604 */ 605 public function setAliases($aliasMap) 606 { 607 foreach ($aliasMap as $flag => $alias) { 608 if ($this->getoptConfig[self::CONFIG_IGNORECASE]) { 609 $flag = strtolower($flag); 610 $alias = strtolower($alias); 611 } 612 if (!isset($this->ruleMap[$flag])) { 613 continue; 614 } 615 $flag = $this->ruleMap[$flag]; 616 if (isset($this->rules[$alias]) || isset($this->ruleMap[$alias])) { 617 $o = (strlen($alias) == 1 ? '-' : '--') . $alias; 618 throw new Exception\InvalidArgumentException("Option \"$o\" is being defined more than once."); 619 } 620 $this->rules[$flag]['alias'][] = $alias; 621 $this->ruleMap[$alias] = $flag; 622 } 623 return $this; 624 } 625 626 /** 627 * Define help messages for options. 628 * 629 * The parameter $helpMap is an associative array 630 * mapping option name (short or long) to the help string. 631 * 632 * @param array $helpMap 633 * @return self 634 */ 635 public function setHelp($helpMap) 636 { 637 foreach ($helpMap as $flag => $help) { 638 if (!isset($this->ruleMap[$flag])) { 639 continue; 640 } 641 $flag = $this->ruleMap[$flag]; 642 $this->rules[$flag]['help'] = $help; 643 } 644 return $this; 645 } 646 647 /** 648 * Parse command-line arguments and find both long and short 649 * options. 650 * 651 * Also find option parameters, and remaining arguments after 652 * all options have been parsed. 653 * 654 * @return self 655 */ 656 public function parse() 657 { 658 if ($this->parsed === true) { 659 return $this; 660 } 661 662 $argv = $this->argv; 663 $this->options = array(); 664 $this->remainingArgs = array(); 665 while (count($argv) > 0) { 666 if ($argv[0] == '--') { 667 array_shift($argv); 668 if ($this->getoptConfig[self::CONFIG_DASHDASH]) { 669 $this->remainingArgs = array_merge($this->remainingArgs, $argv); 670 break; 671 } 672 } 673 if (substr($argv[0], 0, 2) == '--') { 674 $this->_parseLongOption($argv); 675 } elseif (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) { 676 $this->_parseShortOptionCluster($argv); 677 } elseif ($this->getoptConfig[self::CONFIG_PARSEALL]) { 678 $this->remainingArgs[] = array_shift($argv); 679 } else { 680 /* 681 * We should put all other arguments in remainingArgs and stop parsing 682 * since CONFIG_PARSEALL is false. 683 */ 684 $this->remainingArgs = array_merge($this->remainingArgs, $argv); 685 break; 686 } 687 } 688 $this->parsed = true; 689 690 //go through parsed args and process callbacks 691 $this->triggerCallbacks(); 692 693 return $this; 694 } 695 696 /** 697 * @param string $option The name of the property which, if present, will call the passed 698 * callback with the value of this parameter. 699 * @param callable $callback The callback that will be called for this option. The first 700 * parameter will be the value of getOption($option), the second 701 * parameter will be a reference to $this object. If the callback returns 702 * false then an Exception\RuntimeException will be thrown indicating that 703 * there is a parse issue with this option. 704 * 705 * @return self 706 */ 707 public function setOptionCallback($option, \Closure $callback) 708 { 709 $this->optionCallbacks[$option] = $callback; 710 711 return $this; 712 } 713 714 /** 715 * Triggers all the registered callbacks. 716 */ 717 protected function triggerCallbacks() 718 { 719 foreach ($this->optionCallbacks as $option => $callback) { 720 if (null === $this->getOption($option)) { 721 continue; 722 } 723 //make sure we've resolved the alias, if using one 724 if (isset($this->ruleMap[$option]) && $option = $this->ruleMap[$option]) { 725 if (false === $callback($this->getOption($option), $this)) { 726 throw new Exception\RuntimeException( 727 "The option $option is invalid. See usage.", 728 $this->getUsageMessage() 729 ); 730 } 731 } 732 } 733 } 734 735 /** 736 * Parse command-line arguments for a single long option. 737 * A long option is preceded by a double '--' character. 738 * Long options may not be clustered. 739 * 740 * @param mixed &$argv 741 */ 742 protected function _parseLongOption(&$argv) 743 { 744 $optionWithParam = ltrim(array_shift($argv), '-'); 745 $l = explode('=', $optionWithParam, 2); 746 $flag = array_shift($l); 747 $param = array_shift($l); 748 if (isset($param)) { 749 array_unshift($argv, $param); 750 } 751 $this->_parseSingleOption($flag, $argv); 752 } 753 754 /** 755 * Parse command-line arguments for short options. 756 * Short options are those preceded by a single '-' character. 757 * Short options may be clustered. 758 * 759 * @param mixed &$argv 760 */ 761 protected function _parseShortOptionCluster(&$argv) 762 { 763 $flagCluster = ltrim(array_shift($argv), '-'); 764 foreach (str_split($flagCluster) as $flag) { 765 $this->_parseSingleOption($flag, $argv); 766 } 767 } 768 769 /** 770 * Parse command-line arguments for a single option. 771 * 772 * @param string $flag 773 * @param mixed $argv 774 * @throws Exception\ExceptionInterface 775 */ 776 protected function _parseSingleOption($flag, &$argv) 777 { 778 if ($this->getoptConfig[self::CONFIG_IGNORECASE]) { 779 $flag = strtolower($flag); 780 } 781 782 // Check if this option is numeric one 783 if (preg_match('/^\d+$/', $flag)) { 784 return $this->_setNumericOptionValue($flag); 785 } 786 787 if (!isset($this->ruleMap[$flag])) { 788 // Don't throw Exception for flag-like param in case when freeform flags are allowed 789 if (!$this->getoptConfig[self::CONFIG_FREEFORM_FLAGS]) { 790 throw new Exception\RuntimeException( 791 "Option \"$flag\" is not recognized.", 792 $this->getUsageMessage() 793 ); 794 } 795 796 // Magic methods in future will use this mark as real flag value 797 $this->ruleMap[$flag] = $flag; 798 $realFlag = $flag; 799 $this->rules[$realFlag] = array( 800 'param' => 'optional', 801 'isFreeformFlag' => true 802 ); 803 } else { 804 $realFlag = $this->ruleMap[$flag]; 805 } 806 807 switch ($this->rules[$realFlag]['param']) { 808 case 'required': 809 if (count($argv) > 0) { 810 $param = array_shift($argv); 811 $this->_checkParameterType($realFlag, $param); 812 } else { 813 throw new Exception\RuntimeException( 814 "Option \"$flag\" requires a parameter.", 815 $this->getUsageMessage() 816 ); 817 } 818 break; 819 case 'optional': 820 if (count($argv) > 0 && substr($argv[0], 0, 1) != '-') { 821 $param = array_shift($argv); 822 $this->_checkParameterType($realFlag, $param); 823 } else { 824 $param = true; 825 } 826 break; 827 default: 828 $param = true; 829 } 830 831 $this->_setSingleOptionValue($realFlag, $param); 832 } 833 834 /** 835 * Set given value as value of numeric option 836 * 837 * Throw runtime exception if this action is deny by configuration 838 * or no one numeric option handlers is defined 839 * 840 * @param int $value 841 * @throws Exception\RuntimeException 842 * @return void 843 */ 844 protected function _setNumericOptionValue($value) 845 { 846 if (!$this->getoptConfig[self::CONFIG_NUMERIC_FLAGS]) { 847 throw new Exception\RuntimeException("Using of numeric flags are deny by configuration"); 848 } 849 850 if (empty($this->getoptConfig['numericFlagsOption'])) { 851 throw new Exception\RuntimeException("Any option for handling numeric flags are specified"); 852 } 853 854 return $this->_setSingleOptionValue($this->getoptConfig['numericFlagsOption'], $value); 855 } 856 857 /** 858 * Add relative to options' flag value 859 * 860 * If options list already has current flag as key 861 * and parser should follow cumulative params by configuration, 862 * we should to add new param to array, not to overwrite 863 * 864 * @param string $flag 865 * @param string $value 866 */ 867 protected function _setSingleOptionValue($flag, $value) 868 { 869 if (true === $value && $this->getoptConfig[self::CONFIG_CUMULATIVE_FLAGS]) { 870 // For boolean values we have to create new flag, or increase number of flags' usage count 871 return $this->_setBooleanFlagValue($flag); 872 } 873 874 // Split multiple values, if necessary 875 // Filter empty values from splited array 876 $separator = $this->getoptConfig[self::CONFIG_PARAMETER_SEPARATOR]; 877 if (is_string($value) && !empty($separator) && is_string($separator) && substr_count($value, $separator)) { 878 $value = array_filter(explode($separator, $value)); 879 } 880 881 if (!array_key_exists($flag, $this->options)) { 882 $this->options[$flag] = $value; 883 } elseif ($this->getoptConfig[self::CONFIG_CUMULATIVE_PARAMETERS]) { 884 $this->options[$flag] = (array) $this->options[$flag]; 885 array_push($this->options[$flag], $value); 886 } else { 887 $this->options[$flag] = $value; 888 } 889 } 890 891 /** 892 * Set TRUE value to given flag, if this option does not exist yet 893 * In other case increase value to show count of flags' usage 894 * 895 * @param string $flag 896 */ 897 protected function _setBooleanFlagValue($flag) 898 { 899 $this->options[$flag] = array_key_exists($flag, $this->options) 900 ? (int) $this->options[$flag] + 1 901 : true; 902 } 903 904 /** 905 * Return true if the parameter is in a valid format for 906 * the option $flag. 907 * Throw an exception in most other cases. 908 * 909 * @param string $flag 910 * @param string $param 911 * @throws Exception\ExceptionInterface 912 * @return bool 913 */ 914 protected function _checkParameterType($flag, $param) 915 { 916 $type = 'string'; 917 if (isset($this->rules[$flag]['paramType'])) { 918 $type = $this->rules[$flag]['paramType']; 919 } 920 switch ($type) { 921 case 'word': 922 if (preg_match('/\W/', $param)) { 923 throw new Exception\RuntimeException( 924 "Option \"$flag\" requires a single-word parameter, but was given \"$param\".", 925 $this->getUsageMessage() 926 ); 927 } 928 break; 929 case 'integer': 930 if (preg_match('/\D/', $param)) { 931 throw new Exception\RuntimeException( 932 "Option \"$flag\" requires an integer parameter, but was given \"$param\".", 933 $this->getUsageMessage() 934 ); 935 } 936 break; 937 case 'string': 938 default: 939 break; 940 } 941 return true; 942 } 943 944 /** 945 * Define legal options using the gnu-style format. 946 * 947 * @param string $rules 948 */ 949 protected function _addRulesModeGnu($rules) 950 { 951 $ruleArray = array(); 952 953 /** 954 * Options may be single alphanumeric characters. 955 * Options may have a ':' which indicates a required string parameter. 956 * No long options or option aliases are supported in GNU style. 957 */ 958 preg_match_all('/([a-zA-Z0-9]:?)/', $rules, $ruleArray); 959 foreach ($ruleArray[1] as $rule) { 960 $r = array(); 961 $flag = substr($rule, 0, 1); 962 if ($this->getoptConfig[self::CONFIG_IGNORECASE]) { 963 $flag = strtolower($flag); 964 } 965 $r['alias'][] = $flag; 966 if (substr($rule, 1, 1) == ':') { 967 $r['param'] = 'required'; 968 $r['paramType'] = 'string'; 969 } else { 970 $r['param'] = 'none'; 971 } 972 $this->rules[$flag] = $r; 973 $this->ruleMap[$flag] = $flag; 974 } 975 } 976 977 /** 978 * Define legal options using the Zend-style format. 979 * 980 * @param array $rules 981 * @throws Exception\ExceptionInterface 982 */ 983 protected function _addRulesModeZend($rules) 984 { 985 foreach ($rules as $ruleCode => $helpMessage) { 986 // this may have to translate the long parm type if there 987 // are any complaints that =string will not work (even though that use 988 // case is not documented) 989 if (in_array(substr($ruleCode, -2, 1), array('-', '='))) { 990 $flagList = substr($ruleCode, 0, -2); 991 $delimiter = substr($ruleCode, -2, 1); 992 $paramType = substr($ruleCode, -1); 993 } else { 994 $flagList = $ruleCode; 995 $delimiter = $paramType = null; 996 } 997 if ($this->getoptConfig[self::CONFIG_IGNORECASE]) { 998 $flagList = strtolower($flagList); 999 } 1000 $flags = explode('|', $flagList); 1001 $rule = array(); 1002 $mainFlag = $flags[0]; 1003 foreach ($flags as $flag) { 1004 if (empty($flag)) { 1005 throw new Exception\InvalidArgumentException("Blank flag not allowed in rule \"$ruleCode\"."); 1006 } 1007 if (strlen($flag) == 1) { 1008 if (isset($this->ruleMap[$flag])) { 1009 throw new Exception\InvalidArgumentException( 1010 "Option \"-$flag\" is being defined more than once." 1011 ); 1012 } 1013 $this->ruleMap[$flag] = $mainFlag; 1014 $rule['alias'][] = $flag; 1015 } else { 1016 if (isset($this->rules[$flag]) || isset($this->ruleMap[$flag])) { 1017 throw new Exception\InvalidArgumentException( 1018 "Option \"--$flag\" is being defined more than once." 1019 ); 1020 } 1021 $this->ruleMap[$flag] = $mainFlag; 1022 $rule['alias'][] = $flag; 1023 } 1024 } 1025 if (isset($delimiter)) { 1026 switch ($delimiter) { 1027 case self::PARAM_REQUIRED: 1028 $rule['param'] = 'required'; 1029 break; 1030 case self::PARAM_OPTIONAL: 1031 default: 1032 $rule['param'] = 'optional'; 1033 } 1034 switch (substr($paramType, 0, 1)) { 1035 case self::TYPE_WORD: 1036 $rule['paramType'] = 'word'; 1037 break; 1038 case self::TYPE_INTEGER: 1039 $rule['paramType'] = 'integer'; 1040 break; 1041 case self::TYPE_NUMERIC_FLAG: 1042 $rule['paramType'] = 'numericFlag'; 1043 $this->getoptConfig['numericFlagsOption'] = $mainFlag; 1044 break; 1045 case self::TYPE_STRING: 1046 default: 1047 $rule['paramType'] = 'string'; 1048 } 1049 } else { 1050 $rule['param'] = 'none'; 1051 } 1052 $rule['help'] = $helpMessage; 1053 $this->rules[$mainFlag] = $rule; 1054 } 1055 } 1056} 1057