PageRenderTime 60ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/ConsoleTools/src/input.php

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