1<?php 2/** 3 * File containing the ezcConsoleInput class. 4 * 5 * @package ConsoleTools 6 * @version 1.6.1 7 * @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved. 8 * @license http://ez.no/licenses/new_bsd New BSD License 9 * @filesource 10 */ 11 12/** 13 * The ezcConsoleInput class handles the given options and arguments on the console. 14 * 15 * This class allows the complete handling of options and arguments submitted 16 * to a console based application. 17 * 18 * The next example demonstrate how to capture the console options: 19 * 20 * <code> 21 * $optionHandler = new ezcConsoleInput(); 22 * 23 * // Register simple parameter -h/--help 24 * $optionHandler->registerOption( new ezcConsoleOption( 'h', 'help' ) ); 25 * 26 * // Register complex parameter -f/--file 27 * $file = new ezcConsoleOption( 28 * 'f', 29 * 'file', 30 * ezcConsoleInput::TYPE_STRING, 31 * null, 32 * false, 33 * 'Process a file.', 34 * 'Processes a single file.' 35 * ); 36 * $optionHandler->registerOption( $file ); 37 * 38 * // Manipulate parameter -f/--file after registration 39 * $file->multiple = true; 40 * 41 * // Register another complex parameter that depends on -f and excludes -h 42 * $dir = new ezcConsoleOption( 43 * 'd', 44 * 'dir', 45 * ezcConsoleInput::TYPE_STRING, 46 * null, 47 * true, 48 * 'Process a directory.', 49 * 'Processes a complete directory.', 50 * array( new ezcConsoleOptionRule( $optionHandler->getOption( 'f' ) ) ), 51 * array( new ezcConsoleOptionRule( $optionHandler->getOption( 'h' ) ) ) 52 * ); 53 * $optionHandler->registerOption( $dir ); 54 * 55 * // Register an alias for this parameter 56 * $optionHandler->registerAlias( 'e', 'extended-dir', $dir ); 57 * 58 * // Process registered parameters and handle errors 59 * try 60 * { 61 * $optionHandler->process( array( 'example_input.php', '-h' ) ); 62 * } 63 * catch ( ezcConsoleOptionException $e ) 64 * { 65 * echo $e->getMessage(); 66 * exit( 1 ); 67 * } 68 * 69 * // Process a single parameter 70 * $file = $optionHandler->getOption( 'f' ); 71 * if ( $file->value === false ) 72 * { 73 * echo "Parameter -{$file->short}/--{$file->long} was not submitted.\n"; 74 * } 75 * elseif ( $file->value === true ) 76 * { 77 * echo "Parameter -{$file->short}/--{$file->long} was submitted without value.\n"; 78 * } 79 * else 80 * { 81 * echo "Parameter -{$file->short}/--{$file->long} was submitted with value '".var_export($file->value, true)."'.\n"; 82 * } 83 * 84 * // Process all parameters at once: 85 * foreach ( $optionHandler->getOptionValues() as $paramShort => $val ) 86 * { 87 * switch ( true ) 88 * { 89 * case $val === false: 90 * echo "Parameter $paramShort was not submitted.\n"; 91 * break; 92 * case $val === true: 93 * echo "Parameter $paramShort was submitted without a value.\n"; 94 * break; 95 * case is_array( $val ): 96 * echo "Parameter $paramShort was submitted multiple times with value: '".implode(', ', $val)."'.\n"; 97 * break; 98 * default: 99 * echo "Parameter $paramShort was submitted with value: '$val'.\n"; 100 * break; 101 * } 102 * } 103 * </code> 104 * 105 * @package ConsoleTools 106 * @version 1.6.1 107 * @mainclass 108 * 109 * @property ezcConsoleArguments $argumentDefinition Optional argument definition. 110 */ 111class ezcConsoleInput 112{ 113 /** 114 * Option does not carry a value. 115 */ 116 const TYPE_NONE = 1; 117 118 /** 119 * Option takes an integer value. 120 */ 121 const TYPE_INT = 2; 122 123 /** 124 * Option takes a string value. 125 */ 126 const TYPE_STRING = 3; 127 128 /** 129 * Array of option definitions, indexed by number. 130 * 131 * This array stores the ezcConsoleOption objects representing 132 * the options. 133 * 134 * For lookup of an option after its short or long values the attributes 135 * {@link ezcConsoleInput::$optionShort} 136 * {@link ezcConsoleInput::$optionLong} 137 * are used. 138 * 139 * @var array(array) 140 */ 141 private $options = array(); 142 143 /** 144 * Short option names. 145 * 146 * Each references a key in {@link ezcConsoleInput::$options}. 147 * 148 * @var array(string=>int) 149 */ 150 private $optionShort = array(); 151 152 /** 153 * Long option names. 154 * 155 * Each references a key in {@link ezcConsoleInput::$options}. 156 * 157 * @var array(string=>int) 158 */ 159 private $optionLong = array(); 160 161 /** 162 * Arguments, if submitted, are stored here. 163 * 164 * @var array(string) 165 */ 166 private $arguments = array(); 167 168 /** 169 * Wether the process() method has already been called. 170 * 171 * @var bool 172 */ 173 private $processed = false; 174 175 /** 176 * Indicates if an option was submitted, that has the isHelpOption flag set. 177 * 178 * @var bool 179 */ 180 private $helpOptionSet = false; 181 182 /** 183 * Tool object for multi-byte encoding safe string operations. 184 * 185 * @var ezcConsoleStringTool 186 */ 187 private $stringTool; 188 189 /** 190 * Input validator. 191 * 192 * @var ezcConsoleInputValidator 193 */ 194 private $validator; 195 196 /** 197 * Help generator. 198 * 199 * @var ezcConsoleInputHelpGenerator 200 */ 201 private $helpGenerator; 202 203 /** 204 * Collection of properties. 205 * 206 * @var array(string=>mixed) 207 */ 208 protected $properties = array(); 209 210 /** 211 * Creates an input handler. 212 */ 213 public function __construct() 214 { 215 $this->argumentDefinition = null; 216 $this->stringTool = new ezcConsoleStringTool(); 217 218 // @TODO Verify interface and make plugable 219 $this->validator = new ezcConsoleStandardInputValidator(); 220 $this->helpGenerator = new ezcConsoleInputStandardHelpGenerator( $this ); 221 } 222 223 /** 224 * Registers the new option $option. 225 * 226 * This method adds the new option $option to your option collection. If 227 * already an option with the assigned short or long value exists, an 228 * exception will be thrown. 229 * 230 * @see ezcConsoleInput::unregisterOption() 231 * 232 * @param ezcConsoleOption $option 233 * 234 * @return ezcConsoleOption The recently registered option. 235 */ 236 public function registerOption( ezcConsoleOption $option ) 237 { 238 foreach ( $this->optionShort as $short => $ref ) 239 { 240 if ( $short === $option->short ) 241 { 242 throw new ezcConsoleOptionAlreadyRegisteredException( $short ); 243 } 244 } 245 foreach ( $this->optionLong as $long => $ref ) 246 { 247 if ( $long === $option->long ) 248 { 249 throw new ezcConsoleOptionAlreadyRegisteredException( $long ); 250 } 251 } 252 $this->options[] = $option; 253 $this->optionLong[$option->long] = $option; 254 if ( $option->short !== "" ) 255 { 256 $this->optionShort[$option->short] = $option; 257 } 258 return $option; 259 } 260 261 /** 262 * Registers an alias for an option. 263 * 264 * Registers a new alias for an existing option. Aliases can 265 * be used as if they were a normal option. 266 * 267 * The alias is registered with the short option name $short and the 268 * long option name $long. The alias references to the existing 269 * option $option. 270 * 271 * @see ezcConsoleInput::unregisterAlias() 272 * 273 * @param string $short 274 * @param string $long 275 * @param ezcConsoleOption $option 276 * 277 * 278 * @throws ezcConsoleOptionNotExistsException 279 * If the referenced option is not registered. 280 * @throws ezcConsoleOptionAlreadyRegisteredException 281 * If another option/alias has taken the provided short or long name. 282 * @return void 283 */ 284 public function registerAlias( $short, $long, ezcConsoleOption $option ) 285 { 286 if ( !isset( $this->optionShort[$option->short] ) || !isset( $this->optionLong[$option->long] ) ) 287 { 288 throw new ezcConsoleOptionNotExistsException( $option->long ); 289 } 290 if ( isset( $this->optionShort[$short] ) || isset( $this->optionLong[$long] ) ) 291 { 292 throw new ezcConsoleOptionAlreadyRegisteredException( isset( $this->optionShort[$short] ) ? "-$short" : "--$long" ); 293 } 294 $this->optionShort[$short] = $option; 295 $this->optionLong[$long] = $option; 296 } 297 298 /** 299 * Registers options according to a string specification. 300 * 301 * Accepts a string to define parameters and registers all parameters as 302 * options accordingly. String definition, specified in $optionDef, looks 303 * like this: 304 * 305 * <code> 306 * [s:|size:][u:|user:][a:|all:] 307 * </code> 308 * 309 * This string registers 3 parameters: 310 * -s / --size 311 * -u / --user 312 * -a / --all 313 * 314 * @param string $optionDef 315 * @return void 316 * 317 * @throws ezcConsoleOptionStringNotWellformedException 318 * If provided string does not have the correct format. 319 */ 320 public function registerOptionString( $optionDef ) 321 { 322 $regex = '\[([a-z0-9-]+)([:?*+])?([^|]*)\|([a-z0-9-]+)([:?*+])?\]'; 323 // Check string for wellformedness 324 if ( preg_match( "/^($regex)+$/", $optionDef ) == 0 ) 325 { 326 throw new ezcConsoleOptionStringNotWellformedException( "Option definition not wellformed: \"$optionDef\"" ); 327 } 328 if ( preg_match_all( "/$regex/", $optionDef, $matches ) ) 329 { 330 foreach ( $matches[1] as $id => $short ) 331 { 332 $option = null; 333 $option = new ezcConsoleOption( $short, $matches[4][$id] ); 334 if ( !empty( $matches[2][$id] ) || !empty( $matches[5][$id] ) ) 335 { 336 switch ( !empty( $matches[2][$id] ) ? $matches[2][$id] : $matches[5][$id] ) 337 { 338 case '*': 339 // Allows 0 or more occurances 340 $option->multiple = true; 341 break; 342 case '+': 343 // Allows 1 or more occurances 344 $option->multiple = true; 345 $option->type = self::TYPE_STRING; 346 break; 347 case '?': 348 $option->type = self::TYPE_STRING; 349 $option->default = ''; 350 break; 351 default: 352 break; 353 } 354 } 355 if ( !empty( $matches[3][$id] ) ) 356 { 357 $option->default = $matches[3][$id]; 358 } 359 $this->registerOption( $option ); 360 } 361 } 362 } 363 364 /** 365 * Removes an option. 366 * 367 * This function removes an option. All dependencies to that 368 * specific option are removed completely from every other registered 369 * option. 370 * 371 * @see ezcConsoleInput::registerOption() 372 * 373 * @param ezcConsoleOption $option The option object to unregister. 374 * 375 * @throws ezcConsoleOptionNotExistsException 376 * If requesting a not registered option. 377 * @return void 378 */ 379 public function unregisterOption( ezcConsoleOption $option ) 380 { 381 $found = false; 382 foreach ( $this->options as $id => $existParam ) 383 { 384 if ( $existParam === $option ) 385 { 386 $found = true; 387 unset( $this->options[$id] ); 388 continue; 389 } 390 $existParam->removeAllExclusions( $option ); 391 $existParam->removeAllDependencies( $option ); 392 } 393 if ( $found === false ) 394 { 395 throw new ezcConsoleOptionNotExistsException( $option->long ); 396 } 397 foreach ( $this->optionLong as $name => $existParam ) 398 { 399 if ( $existParam === $option ) 400 { 401 unset( $this->optionLong[$name] ); 402 } 403 } 404 foreach ( $this->optionShort as $name => $existParam ) 405 { 406 if ( $existParam === $option ) 407 { 408 unset( $this->optionShort[$name] ); 409 } 410 } 411 } 412 413 /** 414 * Removes an alias to an option. 415 * 416 * This function removes an alias with the short name $short and long 417 * name $long. 418 * 419 * @see ezcConsoleInput::registerAlias() 420 * 421 * @throws ezcConsoleOptionNoAliasException 422 * If the requested short/long name belongs to a real parameter instead. 423 * 424 * @param string $short 425 * @param string $long 426 * @return void 427 * 428 * @todo Check if $short and $long refer to the same option! 429 */ 430 public function unregisterAlias( $short, $long ) 431 { 432 foreach ( $this->options as $id => $option ) 433 { 434 if ( $option->short === $short ) 435 { 436 throw new ezcConsoleOptionNoAliasException( $short ); 437 } 438 if ( $option->long === $long ) 439 { 440 throw new ezcConsoleOptionNoAliasException( $long ); 441 } 442 } 443 if ( isset( $this->optionShort[$short] ) ) 444 { 445 unset( $this->optionShort[$short] ); 446 } 447 if ( isset( $this->optionLong[$long] ) ) 448 { 449 unset( $this->optionLong[$long] ); 450 } 451 } 452 453 /** 454 * Returns the definition object for the option with the name $name. 455 * 456 * This method receives the long or short name of an option and 457 * returns the ezcConsoleOption object. 458 * 459 * @param string $name Short or long name of the option (without - or --). 460 * @return ezcConsoleOption 461 * 462 * @throws ezcConsoleOptionNotExistsException 463 * If requesting a not registered parameter. 464 */ 465 public function getOption( $name ) 466 { 467 $name = $name; 468 if ( isset( $this->optionShort[$name] ) ) 469 { 470 return $this->optionShort[$name]; 471 } 472 if ( isset( $this->optionLong[$name] ) ) 473 { 474 return $this->optionLong[$name]; 475 } 476 throw new ezcConsoleOptionNotExistsException( $name ); 477 } 478 479 /** 480 * Process the input parameters. 481 * 482 * Actually process the input options and arguments according to the actual 483 * settings. 484 * 485 * Per default this method uses $argc and $argv for processing. You can 486 * override this setting with your own input, if necessary, using the 487 * parameters of this method. (Attention, first argument is always the pro 488 * gram name itself!) 489 * 490 * All exceptions thrown by this method contain an additional attribute "option" 491 * which specifies the parameter on which the error occurred. 492 * 493 * @param array(string) $args The arguments 494 * @return void 495 * 496 * @throws ezcConsoleOptionNotExistsException 497 * If an option that was submitted does not exist. 498 * @throws ezcConsoleOptionDependencyViolationException 499 * If a dependency rule was violated. 500 * @throws ezcConsoleOptionExclusionViolationException 501 * If an exclusion rule was violated. 502 * @throws ezcConsoleOptionTypeViolationException 503 * If the type of a submitted value violates the options type rule. 504 * @throws ezcConsoleOptionArgumentsViolationException 505 * If arguments are passed although a parameter disallowed them. 506 * 507 * @see ezcConsoleOptionException 508 */ 509 public function process( array $args = null ) 510 { 511 if ( $this->processed ) 512 { 513 $this->reset(); 514 } 515 $this->processed = true; 516 517 if ( !isset( $args ) ) 518 { 519 $args = isset( $argv ) ? $argv : isset( $_SERVER['argv'] ) ? $_SERVER['argv'] : array(); 520 } 521 522 $nextIndex = $this->processOptions( $args ); 523 524 if ( $this->helpOptionSet() ) 525 { 526 // No need to parse arguments 527 return; 528 } 529 530 $this->processArguments( $args, $nextIndex ); 531 532 $this->checkRules(); 533 534 $this->setOptionDefaults(); 535 } 536 537 /** 538 * Sets defaults for options that have not been submitted. 539 * 540 * Checks all options if they have been submited. If not and a default 541 * values is present, this is set as the options value. 542 */ 543 private function setOptionDefaults() 544 { 545 foreach ( $this->options as $option ) 546 { 547 if ( $option->value === false || $option->value === array() ) 548 { 549 // Default value to set? 550 if ( $option->default !== null ) 551 { 552 $option->value = $option->default; 553 } 554 } 555 } 556 } 557 558 /** 559 * Reads the submitted options from $args array. 560 * 561 * Returns the next index to check for arguments. 562 * 563 * @param array(string) $args 564 * @returns int 565 * 566 * @throws ezcConsoleOptionNotExistsException 567 * if a submitted option does not exist. 568 * @throws ezcConsoleOptionTooManyValuesException 569 * if an option that expects only a single value was submitted 570 * with multiple values. 571 * @throws ezcConsoleOptionTypeViolationException 572 * if an option was submitted with a value of the wrong type. 573 * @throws ezcConsoleOptionMissingValueException 574 * if an option thats expects a value was submitted without. 575 */ 576 private function processOptions( array $args ) 577 { 578 $numArgs = count( $args ); 579 $i = 1; 580 581 while ( $i < $numArgs ) 582 { 583 if ( $args[$i] === '--' ) 584 { 585 break; 586 } 587 588 // Equalize parameter handling (long params with =) 589 if ( iconv_substr( $args[$i], 0, 2, 'UTF-8' ) == '--' ) 590 { 591 $this->preprocessLongOption( $args, $i ); 592 // Update number of args, changed by preprocessLongOption() 593 $numArgs = count( $args ); 594 } 595 596 // Check for parameter 597 if ( iconv_substr( $args[$i], 0, 1, 'UTF-8' ) === '-' ) 598 { 599 if ( !$this->hasOption( preg_replace( '/^-*/', '', $args[$i] ) ) ) 600 { 601 throw new ezcConsoleOptionNotExistsException( $args[$i] ); 602 } 603 $this->processOption( $args, $i ); 604 } 605 // Must be the arguments 606 else 607 { 608 break; 609 } 610 } 611 612 // Move pointer over argument sign 613 isset( $args[$i] ) && $args[$i] == '--' ? ++$i : $i; 614 615 return $i; 616 } 617 618 /** 619 * Resets all option and argument values. 620 * 621 * This method is called automatically by {@link process()}, if this method 622 * is called twice or more, and may also be used to manually reset the 623 * values of all registered {@ezcConsoleOption} and {@link 624 * ezcConsoleArgument} objects. 625 */ 626 public function reset() 627 { 628 foreach ( $this->options as $option ) 629 { 630 $option->value = false; 631 } 632 if ( $this->argumentDefinition !== null ) 633 { 634 foreach ( $this->argumentDefinition as $argument ) 635 { 636 $argument->value = null; 637 } 638 } 639 $this->arguments = array(); 640 } 641 642 /** 643 * Returns true if an option with the given name exists, otherwise false. 644 * 645 * Checks if an option with the given name is registered. 646 * 647 * @param string $name Short or long name of the option. 648 * @return bool True if option exists, otherwise false. 649 */ 650 public function hasOption( $name ) 651 { 652 try 653 { 654 $param = $this->getOption( $name ); 655 } 656 catch ( ezcConsoleOptionNotExistsException $e ) 657 { 658 return false; 659 } 660 return true; 661 } 662 663 /** 664 * Returns an array of all registered options. 665 * 666 * Returns an array of all registered options in the following format: 667 * <code> 668 * array( 669 * 0 => ezcConsoleOption, 670 * 1 => ezcConsoleOption, 671 * 2 => ezcConsoleOption, 672 * ... 673 * ); 674 * </code> 675 * 676 * @return array(string=>ezcConsoleOption) Registered options. 677 */ 678 public function getOptions() 679 { 680 return $this->options; 681 } 682 683 /** 684 * Returns the values of all submitted options. 685 * 686 * Returns an array of all values submitted to the options. The array is 687 * indexed by the parameters short name (excluding the '-' prefix). The array 688 * does not contain any parameter, which value is 'false' (meaning: the 689 * parameter was not submitted). 690 * 691 * @param bool $longnames Wheather to use longnames for indexing. 692 * @return array(string=>mixed) 693 */ 694 public function getOptionValues( $longnames = false ) 695 { 696 $res = array(); 697 foreach ( $this->options as $param ) 698 { 699 if ( $param->value !== false ) 700 { 701 $res[( $longnames === true ) ? $param->long : $param->short] = $param->value; 702 } 703 } 704 return $res; 705 } 706 707 /** 708 * Returns arguments provided to the program. 709 * 710 * This method returns all arguments provided to a program in an 711 * int indexed array. Arguments are sorted in the way 712 * they are submitted to the program. You can disable arguments 713 * through the 'arguments' flag of a parameter, if you want 714 * to disallow arguments. 715 * 716 * Arguments are either the last part of the program call (if the 717 * last parameter is not a 'multiple' one) or divided via the '--' 718 * method which is commonly used on Unix (if the last parameter 719 * accepts multiple values this is required). 720 * 721 * @return array(string) Arguments. 722 */ 723 public function getArguments() 724 { 725 return $this->arguments; 726 } 727 728 /** 729 * Get help information for your options. 730 * 731 * This method returns an array of help information for your options, 732 * indexed by int. Each help info has 2 fields: 733 * 734 * 0 => The options names ("<short> / <long>") 735 * 1 => The help text (depending on the $long parameter) 736 * 737 * The $long options determines if you want to get the short or long help 738 * texts. The array returned can be used by {@link ezcConsoleTable}. 739 * 740 * If using the second options, you can filter the options shown in the 741 * help output (e.g. to show short help for related options). Provide 742 * as simple number indexed array of short and/or long values to set a filter. 743 * 744 * The $paramGrouping option can be used to group options in the help 745 * output. The structure of this array parameter is as follows: 746 * 747 * <code> 748 * array( 749 * 'First section' => array( 750 * 'input', 751 * 'output' 752 * 'overwrite', 753 * ), 754 * 'Second section' => array( 755 * 'v', 756 * 'h', 757 * ), 758 * ) 759 * </code> 760 * 761 * As can be seen, short option names are possible as well as long ones. 762 * The key of the first array level is the name of the section, which is 763 * assigned to an array of options to group under this section. The $params 764 * parameter still influences if an option is displayed at all. 765 * 766 * @param bool $long 767 * @param array(string) $params 768 * @param array(string=>array(string)) $paramGrouping 769 * @return array(array(string)) Table structure as explained. 770 * 771 * @apichange In future versions, the default values of $params will change 772 * to null instead of an empty array. Giving an empty array for 773 * these will then be taken literally. 774 */ 775 public function getHelp( $long = false, array $params = array(), array $paramGrouping = null ) 776 { 777 // New handling 778 $params = ( $params === array() || $params === null ? null : $params ); 779 780 $help = array(); 781 if ( $paramGrouping === null ) 782 { 783 // Original handling 784 $help = $this->getOptionHelpWithoutGrouping( $long, $params ); 785 } 786 else 787 { 788 $help = $this->getOptionHelpWithGrouping( $long, $params, $paramGrouping ); 789 } 790 791 if ( $this->argumentDefinition !== null ) 792 { 793 $help[] = array( "Arguments:", '' ); 794 795 $argumentsHelp = $this->helpGenerator->generateArgumentHelp( $long ); 796 if ( $argumentsHelp === array() ) 797 { 798 $help[] = array( '', "No arguments available." ); 799 } 800 else 801 { 802 $help = array_merge( $help, $argumentsHelp ); 803 } 804 } 805 806 return $help; 807 } 808 809 /** 810 * Creates the option help array in the original, ungrouped way. 811 * 812 * Creates the original help array generated by {@link getHelp()}. The 813 * $long and $params options are the same as they are for this method. 814 * 815 * @param bool $long 816 * @param array $params 817 * @return array 818 */ 819 private function getOptionHelpWithoutGrouping( $long, $params ) 820 { 821 return $this->helpGenerator->generateUngroupedOptionHelp( 822 $long, 823 $params 824 ); 825 } 826 827 /** 828 * Generates options helo array with ordering and grouping. 829 * 830 * @param mixed $long 831 * @param mixed $params 832 * @param mixed $paramGrouping 833 * @return array() 834 */ 835 private function getOptionHelpWithGrouping( $long, $params, $paramGrouping ) 836 { 837 $rawHelp = $this->helpGenerator->generateGroupedOptionHelp( 838 $paramGrouping, 839 $long, 840 $params 841 ); 842 843 $help = array(); 844 $first = true; 845 foreach ( $rawHelp as $category => $optionsHelp ) 846 { 847 if ( !$first ) 848 { 849 $help[] = array( '', '' ); 850 } 851 else 852 { 853 $first = false; 854 } 855 856 $help[] = array( $category, '' ); 857 $help = array_merge( $help, $optionsHelp ); 858 } 859 return $help; 860 } 861 862 863 /** 864 * Get help information for your options as a table. 865 * 866 * This method provides the information returned by 867 * {@link ezcConsoleInput::getHelp()} in a table. 868 * 869 * The $paramGrouping option can be used to group options in the help 870 * output. The structure of this array parameter is as follows: 871 * 872 * <code> 873 * array( 874 * 'First section' => array( 875 * 'input', 876 * 'output' 877 * 'overwrite', 878 * ), 879 * 'Second section' => array( 880 * 'v', 881 * 'h', 882 * ), 883 * ) 884 * </code> 885 * 886 * As can be seen, short option names are possible as well as long ones. 887 * The key of the first array level is the name of the section, which is 888 * assigned to an array of options to group under this section. The $params 889 * parameter still influences if an option as displayed at all. 890 * 891 * @param ezcConsoleTable $table The table object to fill. 892 * @param bool $long Set this to true for getting the 893 * long help version. 894 * @param array(string) $params Set of option names to generate help 895 * for, default is all. 896 * @param array(string=>array(string)) $paramGrouping 897 * @return ezcConsoleTable The filled table. 898 */ 899 public function getHelpTable( ezcConsoleTable $table, $long = false, array $params = array(), $paramGrouping = null ) 900 { 901 $help = $this->getHelp( $long, $params, $paramGrouping ); 902 $i = 0; 903 foreach ( $help as $row ) 904 { 905 $table[$i][0]->content = $row[0]; 906 $table[$i++][1]->content = $row[1]; 907 } 908 return $table; 909 } 910 911 /** 912 * Returns a standard help output for your program. 913 * 914 * This method generates a help text as it's commonly known from Unix 915 * command line programs. The output will contain the synopsis, your 916 * provided program description and the selected parameter help 917 * as also provided by {@link ezcConsoleInput::getHelp()}. The returned 918 * string can directly be printed to the console. 919 * 920 * The $paramGrouping option can be used to group options in the help 921 * output. The structure of this array parameter is as follows: 922 * 923 * <code> 924 * array( 925 * 'First section' => array( 926 * 'input', 927 * 'output' 928 * 'overwrite', 929 * ), 930 * 'Second section' => array( 931 * 'v', 932 * 'h', 933 * ), 934 * ) 935 * </code> 936 * 937 * As can be seen, short option names are possible as well as long ones. 938 * The key of the first array level is the name of the section, which is 939 * assigned to an array of options to group under this section. The $params 940 * parameter still influences if an option as displayed at all. 941 * 942 * @param string $programDesc The description of your program. 943 * @param int $width The width to adjust the output text to. 944 * @param bool $long Set this to true for getting the long 945 * help version. 946 * @param array(string) $params Set of option names to generate help 947 * for, default is all. 948 * @param array(string=>array(string)) $paramGrouping 949 * @return string The generated help text. 950 */ 951 public function getHelpText( $programDesc, $width = 80, $long = false, array $params = null, $paramGrouping = null ) 952 { 953 $help = $this->getHelp( $long, ( $params == null ? array() : $params ), $paramGrouping ); 954 955 // Determine max length of first column text. 956 $maxLength = 0; 957 foreach ( $help as $row ) 958 { 959 $maxLength = max( $maxLength, iconv_strlen( $row[0], 'UTF-8' ) ); 960 } 961 962 // Width of left column 963 $leftColWidth = $maxLength + 2; 964 // Width of righ column 965 $rightColWidth = $width - $leftColWidth; 966 967 $res = 'Usage: ' . $this->getSynopsis( $params ) . PHP_EOL; 968 $res .= $this->stringTool->wordwrap( $programDesc, $width, PHP_EOL ); 969 $res .= PHP_EOL . PHP_EOL; 970 foreach ( $help as $row ) 971 { 972 $rowParts = explode( 973 "\n", 974 $this->stringTool->wordwrap( $row[1], $rightColWidth ) 975 ); 976 977 $res .= $this->stringTool->strPad( $row[0], $leftColWidth, ' ' ); 978 $res .= $rowParts[0] . PHP_EOL; 979 // @TODO: Fix function call in loop header 980 for ( $i = 1; $i < sizeof( $rowParts ); $i++ ) 981 { 982 $res .= str_repeat( ' ', $leftColWidth ) . $rowParts[$i] . PHP_EOL; 983 } 984 } 985 return $res; 986 } 987 988 /** 989 * Returns the synopsis string for the program. 990 * 991 * This gives you a synopsis definition for the options and arguments 992 * defined with this instance of ezcConsoleInput. You can filter the 993 * options named in the synopsis by submitting their short names in an 994 * array as the parameter of this method. If the parameter $optionNames 995 * is set, only those options are listed in the synopsis. 996 * 997 * @param array(string) $optionNames 998 * @return string 999 */ 1000 public function getSynopsis( array $optionNames = null ) 1001 { 1002 return $this->helpGenerator->generateSynopsis( $optionNames ); 1003 } 1004 1005 /** 1006 * Returns if a help option was set. 1007 * This method returns if an option was submitted, which was defined to be 1008 * a help option, using the isHelpOption flag. 1009 * 1010 * @return bool If a help option was set. 1011 */ 1012 public function helpOptionSet() 1013 { 1014 return $this->helpOptionSet; 1015 } 1016 1017 /** 1018 * Property read access. 1019 * 1020 * @throws ezcBasePropertyNotFoundException 1021 * If the the desired property is not found. 1022 * 1023 * @param string $propertyName Name of the property. 1024 * @return mixed Value of the property or null. 1025 * @ignore 1026 */ 1027 public function __get( $propertyName ) 1028 { 1029 if ( !isset( $this->$propertyName ) ) 1030 { 1031 throw new ezcBasePropertyNotFoundException( $propertyName ); 1032 } 1033 return $this->properties[$propertyName]; 1034 } 1035 1036 /** 1037 * Property set access. 1038 * 1039 * @param string $propertyName 1040 * @param string $propertyValue 1041 * @ignore 1042 * @return void 1043 */ 1044 public function __set( $propertyName, $propertyValue ) 1045 { 1046 switch ( $propertyName ) 1047 { 1048 case "argumentDefinition": 1049 if ( ( $propertyValue instanceof ezcConsoleArguments ) === false && $propertyValue !== null ) 1050 { 1051 throw new ezcBaseValueException( $propertyName, $propertyValue, "ezcConsoleArguments" ); 1052 } 1053 break; 1054 default: 1055 throw new ezcBasePropertyNotFoundException( $propertyName ); 1056 } 1057 $this->properties[$propertyName] = $propertyValue; 1058 } 1059 1060 /** 1061 * Property isset access. 1062 * 1063 * @param string $propertyName Name of the property. 1064 * @return bool True if the property is set, otherwise false. 1065 * @ignore 1066 */ 1067 public function __isset( $propertyName ) 1068 { 1069 return array_key_exists( $propertyName, $this->properties ); 1070 } 1071 1072 /** 1073 * Returns the synopsis string for a single option and its dependencies. 1074 * 1075 * This method returns a part of the program synopsis, specifically for a 1076 * certain parameter. The method recursively adds depending parameters up 1077 * to the 2nd depth level to the synopsis. The second parameter is used 1078 * to store the short names of all options that have already been used in 1079 * the synopsis (to avoid adding an option twice). The 3rd parameter 1080 * determines the actual deps in the option dependency recursion to 1081 * terminate that after 2 recursions. 1082 * 1083 * @param ezcConsoleOption $option The option to include. 1084 * @param array(string) $usedOptions Array of used option short names. 1085 * @param int $depth Current recursion depth. 1086 * @return string The synopsis for this parameter. 1087 * 1088 * @apichange This method is deprecates. Implement your own {@link 1089 * ezcConsoleInputHelpGenerator} instead, as soon as the 1090 * interface is made public. 1091 */ 1092 protected function createOptionSynopsis( ezcConsoleOption $option, &$usedOptions, $depth = 0 ) 1093 { 1094 $synopsis = ''; 1095 1096 // Break after a nesting level of 2 1097 if ( $depth++ > 2 || ( in_array( $option->short, $usedOptions['short'] ) && in_array( $option->long, $usedOptions['long'] ) ) ) return $synopsis; 1098 1099 $usedOptions['short'][] = $option->short; 1100 $usedOptions['long'][] = $option->long; 1101 1102 $synopsis .= $option->short !== "" ? "-{$option->short}" : "--{$option->long}"; 1103 1104 if ( isset( $option->default ) ) 1105 { 1106 $synopsis .= " " . ( $option->type === ezcConsoleInput::TYPE_STRING ? '"' : '' ) . $option->default . ( $option->type === ezcConsoleInput::TYPE_STRING ? '"' : '' ); 1107 } 1108 else if ( $option->type !== ezcConsoleInput::TYPE_NONE ) 1109 { 1110 $synopsis .= " "; 1111 switch ( $option->type ) 1112 { 1113 case ezcConsoleInput::TYPE_STRING: 1114 $synopsis .= "<string>"; 1115 break; 1116 case ezcConsoleInput::TYPE_INT: 1117 $synopsis .= "<int>"; 1118 break; 1119 } 1120 } 1121 1122 foreach ( $option->getDependencies() as $rule ) 1123 { 1124 $deeperSynopsis = $this->createOptionSynopsis( $rule->option, $usedOptions, $depth ); 1125 $synopsis .= ( iconv_strlen( trim( $deeperSynopsis ), 'UTF-8' ) > 0 1126 ? ' ' . $deeperSynopsis 1127 : '' 1128 ); 1129 } 1130 1131 if ( $option->arguments === false ) 1132 { 1133 $allowsArgs = false; 1134 } 1135 1136 // Make the whole thing optional? 1137 if ( $option->mandatory === false ) 1138 { 1139 $synopsis = "[$synopsis]"; 1140 } 1141 1142 return $synopsis . ' '; 1143 } 1144 1145 /** 1146 * Process an option. 1147 * 1148 * This method does the processing of a single option. 1149 * 1150 * @param array(string) $args The arguments array. 1151 * @param int $i The current position in the arguments array. 1152 * @return void 1153 * 1154 * @throws ezcConsoleOptionTooManyValuesException 1155 * If an option that expects only a single value was submitted 1156 * with multiple values. 1157 * @throws ezcConsoleOptionTypeViolationException 1158 * If an option was submitted with a value of the wrong type. 1159 * @throws ezcConsoleOptionMissingValueException 1160 * If an option thats expects a value was submitted without. 1161 */ 1162 private function processOption( array $args, &$i ) 1163 { 1164 $option = $this->getOption( preg_replace( '/^-+/', '', $args[$i++] ) ); 1165 1166 // Is the actual option a help option? 1167 if ( $option->isHelpOption === true ) 1168 { 1169 $this->helpOptionSet = true; 1170 } 1171 // No value expected 1172 if ( $option->type === ezcConsoleInput::TYPE_NONE ) 1173 { 1174 // No value expected 1175 if ( isset( $args[$i] ) && iconv_substr( $args[$i], 0, 1, 'UTF-8' ) !== '-' && sizeof( $args ) > ( $i + 1 ) ) 1176 { 1177 // But one found 1178 throw new ezcConsoleOptionTypeViolationException( $option, $args[$i] ); 1179 } 1180 // Multiple occurance possible 1181 if ( $option->multiple === true ) 1182 { 1183 $option->value[] = true; 1184 } 1185 else 1186 { 1187 $option->value = true; 1188 } 1189 // Everything fine, nothing to do 1190 return $i; 1191 } 1192 // Value expected, check for it 1193 if ( isset( $args[$i] ) && iconv_substr( $args[$i], 0, 1, 'UTF-8' ) !== '-' ) 1194 { 1195 // Type check 1196 if ( $this->isCorrectType( $option->type, $args[$i] ) === false ) 1197 { 1198 throw new ezcConsoleOptionTypeViolationException( $option, $args[$i] ); 1199 } 1200 // Multiple values possible 1201 if ( $option->multiple === true ) 1202 { 1203 $option->value[] = $args[$i]; 1204 } 1205 // Only single value expected, check for multiple 1206 elseif ( isset( $option->value ) && $option->value !== false ) 1207 { 1208 throw new ezcConsoleOptionTooManyValuesException( $option ); 1209 } 1210 else 1211 { 1212 $option->value = $args[$i]; 1213 } 1214 $i++; 1215 } 1216 // Value found? If not, use default, if available 1217 if ( !isset( $option->value ) || $option->value === false || ( is_array( $option->value ) && count( $option->value ) === 0) ) 1218 { 1219 throw new ezcConsoleOptionMissingValueException( $option ); 1220 } 1221 } 1222 1223 /** 1224 * Process arguments given to the program. 1225 * 1226 * @param array(string) $args The arguments array. 1227 * @param int $i Current index in arguments array. 1228 * @return void 1229 */ 1230 private function processArguments( array $args, &$i ) 1231 { 1232 $numArgs = count( $args ); 1233 if ( $this->argumentDefinition === null || $this->argumentsAllowed() === false ) 1234 { 1235 // Old argument handling, also used of a set option sets disallowing arguments 1236 while ( $i < $numArgs ) 1237 { 1238 $this->arguments[] = $args[$i++]; 1239 } 1240 } 1241 else 1242 { 1243 $mandatory = true; 1244 foreach ( $this->argumentDefinition as $arg ) 1245 { 1246 // Check if all followinga arguments are optional 1247 if ( $arg->mandatory === false ) 1248 { 1249 $mandatory = false; 1250 } 1251 1252 // Check if the current argument is present and mandatory 1253 if ( $mandatory === true ) 1254 { 1255 if ( !isset( $args[$i] ) ) 1256 { 1257 throw new ezcConsoleArgumentMandatoryViolationException( $arg ); 1258 } 1259 } 1260 else 1261 { 1262 // Arguments are optional, if no more left: return. 1263 if ( !isset( $args[$i] ) ) 1264 { 1265 // Optional and no more arguments left, assign default 1266 $arg->value = $arg->default; 1267 continue; 1268 } 1269 } 1270 1271 if ( $arg->multiple === true ) 1272 { 1273 $arg->value = array(); 1274 for ( $i = $i; $i < $numArgs; ++$i ) 1275 { 1276 if ( $this->isCorrectType( $arg->type, $args[$i] ) === false ) 1277 { 1278 throw new ezcConsoleArgumentTypeViolationException( $arg, $args[$i] ); 1279 } 1280 $arg->value = array_merge( $arg->value, array( $args[$i] ) ); 1281 // Keep old handling, too 1282 $this->arguments[] = $args[$i]; 1283 } 1284 return; 1285 } 1286 else 1287 { 1288 if ( $this->isCorrectType( $arg->type, $args[$i] ) === false ) 1289 { 1290 throw new ezcConsoleArgumentTypeViolationException( $arg, $args[$i] ); 1291 } 1292 $arg->value = $args[$i]; 1293 // Keep old handling, too 1294 $this->arguments[] = $args[$i]; 1295 } 1296 ++$i; 1297 } 1298 1299 if ( $i < $numArgs ) 1300 { 1301 throw new ezcConsoleTooManyArgumentsException( $args, $i ); 1302 } 1303 } 1304 } 1305 1306 /** 1307 * Returns if arguments are allowed with the current option submition. 1308 * 1309 * @return bool If arguments allowed. 1310 */ 1311 protected function argumentsAllowed() 1312 { 1313 foreach ( $this->options as $id => $option ) 1314 { 1315 if ( $option->value !== false && $option->arguments === false ) 1316 { 1317 return false; 1318 } 1319 } 1320 return true; 1321 } 1322 1323 /** 1324 * Check the rules that may be associated with an option. 1325 * 1326 * Options are allowed to have rules associated for dependencies to other 1327 * options and exclusion of other options or arguments. This method 1328 * processes the checks. 1329 * 1330 * @throws ezcConsoleException 1331 * in case validation fails. 1332 */ 1333 private function checkRules() 1334 { 1335 // If a help option is set, skip rule checking 1336 if ( $this->helpOptionSet === true ) 1337 { 1338 return; 1339 } 1340 $this->validator->validateOptions( 1341 $this->options, 1342 ( $this->arguments !== array() ) 1343 ); 1344 } 1345 1346 /** 1347 * Checks if a value is of a given type. Converts the value to the 1348 * correct PHP type on success. 1349 * 1350 * @param int $type The type to check for. One of self::TYPE_*. 1351 * @param string $val The value to check. Will possibly altered! 1352 * @return bool True on succesful check, otherwise false. 1353 */ 1354 private function isCorrectType( $type, &$val ) 1355 { 1356 $res = false; 1357 switch ( $type ) 1358 { 1359 case ezcConsoleInput::TYPE_STRING: 1360 $res = true; 1361 $val = preg_replace( '/^(["\'])(.*)\1$/', '\2', $val ); 1362 break; 1363 case ezcConsoleInput::TYPE_INT: 1364 $res = preg_match( '/^[0-9]+$/', $val ) ? true : false; 1365 if ( $res ) 1366 { 1367 $val = ( int ) $val; 1368 } 1369 break; 1370 } 1371 return $res; 1372 } 1373 1374 /** 1375 * Split parameter and value for long option names. 1376 * 1377 * This method checks for long options, if the value is passed using =. If 1378 * this is the case parameter and value get split and replaced in the 1379 * arguments array. 1380 * 1381 * @param array(string) $args The arguments array 1382 * @param int $i Current arguments array position 1383 * @return void 1384 */ 1385 private function preprocessLongOption( array &$args, $i ) 1386 { 1387 // Value given? 1388 if ( preg_match( '/^--\w+\=[^ ]/i', $args[$i] ) ) 1389 { 1390 // Split param and value and replace current param 1391 $parts = explode( '=', $args[$i], 2 ); 1392 array_splice( $args, $i, 1, $parts ); 1393 } 1394 } 1395} 1396?> 1397