You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommandLine.php 45KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4. * This file is part of the PEAR Console_CommandLine package.
  5. *
  6. * A full featured package for managing command-line options and arguments
  7. * hightly inspired from python optparse module, it allows the developper to
  8. * easily build complex command line interfaces.
  9. *
  10. * PHP version 5
  11. *
  12. * LICENSE: This source file is subject to the MIT license that is available
  13. * through the world-wide-web at the following URI:
  14. * http://opensource.org/licenses/mit-license.php
  15. *
  16. * @category Console
  17. * @package Console_CommandLine
  18. * @author David JEAN LOUIS <izimobil@gmail.com>
  19. * @copyright 2007 David JEAN LOUIS
  20. * @license http://opensource.org/licenses/mit-license.php MIT License
  21. * @version CVS: $Id$
  22. * @link http://pear.php.net/package/Console_CommandLine
  23. * @since Class available since release 0.1.0
  24. * @filesource
  25. */
  26. /**
  27. * Required unconditionally
  28. */
  29. require_once 'Console/CommandLine/Exception.php';
  30. require_once 'Console/CommandLine/Outputter/Default.php';
  31. require_once 'Console/CommandLine/Renderer/Default.php';
  32. require_once 'Console/CommandLine/MessageProvider/Default.php';
  33. /**
  34. * Main class for parsing command line options and arguments.
  35. *
  36. * There are three ways to create parsers with this class:
  37. * <code>
  38. * // direct usage
  39. * $parser = new Console_CommandLine();
  40. *
  41. * // with an xml definition file
  42. * $parser = Console_CommandLine::fromXmlFile('path/to/file.xml');
  43. *
  44. * // with an xml definition string
  45. * $validXmlString = '..your xml string...';
  46. * $parser = Console_CommandLine::fromXmlString($validXmlString);
  47. * </code>
  48. *
  49. * @category Console
  50. * @package Console_CommandLine
  51. * @author David JEAN LOUIS <izimobil@gmail.com>
  52. * @copyright 2007 David JEAN LOUIS
  53. * @license http://opensource.org/licenses/mit-license.php MIT License
  54. * @version Release: 1.2.2
  55. * @link http://pear.php.net/package/Console_CommandLine
  56. * @since File available since release 0.1.0
  57. * @example docs/examples/ex1.php
  58. * @example docs/examples/ex2.php
  59. */
  60. class Console_CommandLine
  61. {
  62. // Public properties {{{
  63. /**
  64. * Error messages.
  65. *
  66. * @var array $errors Error messages
  67. * @todo move this to Console_CommandLine_MessageProvider
  68. */
  69. public static $errors = array(
  70. 'option_bad_name' => 'option name must be a valid php variable name (got: {$name})',
  71. 'argument_bad_name' => 'argument name must be a valid php variable name (got: {$name})',
  72. 'argument_no_default' => 'only optional arguments can have a default value',
  73. 'option_long_and_short_name_missing' => 'you must provide at least an option short name or long name for option "{$name}"',
  74. 'option_bad_short_name' => 'option "{$name}" short name must be a dash followed by a letter (got: "{$short_name}")',
  75. 'option_bad_long_name' => 'option "{$name}" long name must be 2 dashes followed by a word (got: "{$long_name}")',
  76. 'option_unregistered_action' => 'unregistered action "{$action}" for option "{$name}".',
  77. 'option_bad_action' => 'invalid action for option "{$name}".',
  78. 'option_invalid_callback' => 'you must provide a valid callback for option "{$name}"',
  79. 'action_class_does_not_exists' => 'action "{$name}" class "{$class}" not found, make sure that your class is available before calling Console_CommandLine::registerAction()',
  80. 'invalid_xml_file' => 'XML definition file "{$file}" does not exists or is not readable',
  81. 'invalid_rng_file' => 'RNG file "{$file}" does not exists or is not readable'
  82. );
  83. /**
  84. * The name of the program, if not given it defaults to argv[0].
  85. *
  86. * @var string $name Name of your program
  87. */
  88. public $name;
  89. /**
  90. * A description text that will be displayed in the help message.
  91. *
  92. * @var string $description Description of your program
  93. */
  94. public $description = '';
  95. /**
  96. * A string that represents the version of the program, if this property is
  97. * not empty and property add_version_option is not set to false, the
  98. * command line parser will add a --version option, that will display the
  99. * property content.
  100. *
  101. * @var string $version
  102. * @access public
  103. */
  104. public $version = '';
  105. /**
  106. * Boolean that determine if the command line parser should add the help
  107. * (-h, --help) option automatically.
  108. *
  109. * @var bool $add_help_option Whether to add a help option or not
  110. */
  111. public $add_help_option = true;
  112. /**
  113. * Boolean that determine if the command line parser should add the version
  114. * (-v, --version) option automatically.
  115. * Note that the version option is also generated only if the version
  116. * property is not empty, it's up to you to provide a version string of
  117. * course.
  118. *
  119. * @var bool $add_version_option Whether to add a version option or not
  120. */
  121. public $add_version_option = true;
  122. /**
  123. * Boolean that determine if providing a subcommand is mandatory.
  124. *
  125. * @var bool $subcommand_required Whether a subcommand is required or not
  126. */
  127. public $subcommand_required = false;
  128. /**
  129. * The command line parser renderer instance.
  130. *
  131. * @var object that implements Console_CommandLine_Renderer interface
  132. * @access protected
  133. */
  134. public $renderer = false;
  135. /**
  136. * The command line parser outputter instance.
  137. *
  138. * @var Console_CommandLine_Outputter An outputter
  139. */
  140. public $outputter = false;
  141. /**
  142. * The command line message provider instance.
  143. *
  144. * @var Console_CommandLine_MessageProvider A message provider instance
  145. */
  146. public $message_provider = false;
  147. /**
  148. * Boolean that tells the parser to be POSIX compliant, POSIX demands the
  149. * following behavior: the first non-option stops option processing.
  150. *
  151. * @var bool $force_posix Whether to force posix compliance or not
  152. */
  153. public $force_posix = false;
  154. /**
  155. * Boolean that tells the parser to set relevant options default values,
  156. * according to the option action.
  157. *
  158. * @see Console_CommandLine_Option::setDefaults()
  159. * @var bool $force_options_defaults Whether to force option default values
  160. */
  161. public $force_options_defaults = false;
  162. /**
  163. * Boolean that tells the parser to treat a single - option as an argument
  164. * instead of trying to read STDIN.
  165. *
  166. * @var bool $avoid_reading_stdin Whether to treat - as an argument
  167. */
  168. public $avoid_reading_stdin = false;
  169. /**
  170. * An array of Console_CommandLine_Option objects.
  171. *
  172. * @var array $options The options array
  173. */
  174. public $options = array();
  175. /**
  176. * An array of Console_CommandLine_Argument objects.
  177. *
  178. * @var array $args The arguments array
  179. */
  180. public $args = array();
  181. /**
  182. * An array of Console_CommandLine_Command objects (sub commands).
  183. *
  184. * @var array $commands The commands array
  185. */
  186. public $commands = array();
  187. /**
  188. * Parent, only relevant in Command objects but left here for interface
  189. * convenience.
  190. *
  191. * @var Console_CommandLine The parent instance
  192. * @todo move Console_CommandLine::parent to Console_CommandLine_Command
  193. */
  194. public $parent = false;
  195. /**
  196. * Array of valid actions for an option, this array will also store user
  197. * registered actions.
  198. *
  199. * The array format is:
  200. * <pre>
  201. * array(
  202. * <ActionName:string> => array(<ActionClass:string>, <builtin:bool>)
  203. * )
  204. * </pre>
  205. *
  206. * @var array $actions List of valid actions
  207. */
  208. public static $actions = array(
  209. 'StoreTrue' => array('Console_CommandLine_Action_StoreTrue', true),
  210. 'StoreFalse' => array('Console_CommandLine_Action_StoreFalse', true),
  211. 'StoreString' => array('Console_CommandLine_Action_StoreString', true),
  212. 'StoreInt' => array('Console_CommandLine_Action_StoreInt', true),
  213. 'StoreFloat' => array('Console_CommandLine_Action_StoreFloat', true),
  214. 'StoreArray' => array('Console_CommandLine_Action_StoreArray', true),
  215. 'Callback' => array('Console_CommandLine_Action_Callback', true),
  216. 'Counter' => array('Console_CommandLine_Action_Counter', true),
  217. 'Help' => array('Console_CommandLine_Action_Help', true),
  218. 'Version' => array('Console_CommandLine_Action_Version', true),
  219. 'Password' => array('Console_CommandLine_Action_Password', true),
  220. 'List' => array('Console_CommandLine_Action_List', true),
  221. );
  222. /**
  223. * Custom errors messages for this command
  224. *
  225. * This array is of the form:
  226. * <code>
  227. * <?php
  228. * array(
  229. * $messageName => $messageText,
  230. * $messageName => $messageText,
  231. * ...
  232. * );
  233. * ?>
  234. * </code>
  235. *
  236. * If specified, these messages override the messages provided by the
  237. * default message provider. For example:
  238. * <code>
  239. * <?php
  240. * $messages = array(
  241. * 'ARGUMENT_REQUIRED' => 'The argument foo is required.',
  242. * );
  243. * ?>
  244. * </code>
  245. *
  246. * @var array
  247. * @see Console_CommandLine_MessageProvider_Default
  248. */
  249. public $messages = array();
  250. // }}}
  251. // {{{ Private properties
  252. /**
  253. * Array of options that must be dispatched at the end.
  254. *
  255. * @var array $_dispatchLater Options to be dispatched
  256. */
  257. private $_dispatchLater = array();
  258. private $_lastopt = false;
  259. private $_stopflag = false;
  260. // }}}
  261. // __construct() {{{
  262. /**
  263. * Constructor.
  264. * Example:
  265. *
  266. * <code>
  267. * $parser = new Console_CommandLine(array(
  268. * 'name' => 'yourprogram', // defaults to argv[0]
  269. * 'description' => 'Description of your program',
  270. * 'version' => '0.0.1', // your program version
  271. * 'add_help_option' => true, // or false to disable --help option
  272. * 'add_version_option' => true, // or false to disable --version option
  273. * 'force_posix' => false // or true to force posix compliance
  274. * ));
  275. * </code>
  276. *
  277. * @param array $params An optional array of parameters
  278. *
  279. * @return void
  280. */
  281. public function __construct(array $params = array())
  282. {
  283. if (isset($params['name'])) {
  284. $this->name = $params['name'];
  285. } else if (isset($argv) && count($argv) > 0) {
  286. $this->name = $argv[0];
  287. } else if (isset($_SERVER['argv']) && count($_SERVER['argv']) > 0) {
  288. $this->name = $_SERVER['argv'][0];
  289. } else if (isset($_SERVER['SCRIPT_NAME'])) {
  290. $this->name = basename($_SERVER['SCRIPT_NAME']);
  291. }
  292. if (isset($params['description'])) {
  293. $this->description = $params['description'];
  294. }
  295. if (isset($params['version'])) {
  296. $this->version = $params['version'];
  297. }
  298. if (isset($params['add_version_option'])) {
  299. $this->add_version_option = $params['add_version_option'];
  300. }
  301. if (isset($params['add_help_option'])) {
  302. $this->add_help_option = $params['add_help_option'];
  303. }
  304. if (isset($params['subcommand_required'])) {
  305. $this->subcommand_required = $params['subcommand_required'];
  306. }
  307. if (isset($params['force_posix'])) {
  308. $this->force_posix = $params['force_posix'];
  309. } else if (getenv('POSIXLY_CORRECT')) {
  310. $this->force_posix = true;
  311. }
  312. if (isset($params['messages']) && is_array($params['messages'])) {
  313. $this->messages = $params['messages'];
  314. }
  315. // set default instances
  316. $this->renderer = new Console_CommandLine_Renderer_Default($this);
  317. $this->outputter = new Console_CommandLine_Outputter_Default();
  318. $this->message_provider = new Console_CommandLine_MessageProvider_Default();
  319. }
  320. // }}}
  321. // accept() {{{
  322. /**
  323. * Method to allow Console_CommandLine to accept either:
  324. * + a custom renderer,
  325. * + a custom outputter,
  326. * + or a custom message provider
  327. *
  328. * @param mixed $instance The custom instance
  329. *
  330. * @return void
  331. * @throws Console_CommandLine_Exception if wrong argument passed
  332. */
  333. public function accept($instance)
  334. {
  335. if ($instance instanceof Console_CommandLine_Renderer) {
  336. if (property_exists($instance, 'parser') && !$instance->parser) {
  337. $instance->parser = $this;
  338. }
  339. $this->renderer = $instance;
  340. } else if ($instance instanceof Console_CommandLine_Outputter) {
  341. $this->outputter = $instance;
  342. } else if ($instance instanceof Console_CommandLine_MessageProvider) {
  343. $this->message_provider = $instance;
  344. } else {
  345. throw Console_CommandLine_Exception::factory(
  346. 'INVALID_CUSTOM_INSTANCE',
  347. array(),
  348. $this,
  349. $this->messages
  350. );
  351. }
  352. }
  353. // }}}
  354. // fromXmlFile() {{{
  355. /**
  356. * Returns a command line parser instance built from an xml file.
  357. *
  358. * Example:
  359. * <code>
  360. * require_once 'Console/CommandLine.php';
  361. * $parser = Console_CommandLine::fromXmlFile('path/to/file.xml');
  362. * $result = $parser->parse();
  363. * </code>
  364. *
  365. * @param string $file Path to the xml file
  366. *
  367. * @return Console_CommandLine The parser instance
  368. */
  369. public static function fromXmlFile($file)
  370. {
  371. include_once 'Console/CommandLine/XmlParser.php';
  372. return Console_CommandLine_XmlParser::parse($file);
  373. }
  374. // }}}
  375. // fromXmlString() {{{
  376. /**
  377. * Returns a command line parser instance built from an xml string.
  378. *
  379. * Example:
  380. * <code>
  381. * require_once 'Console/CommandLine.php';
  382. * $xmldata = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
  383. * <command>
  384. * <description>Compress files</description>
  385. * <option name="quiet">
  386. * <short_name>-q</short_name>
  387. * <long_name>--quiet</long_name>
  388. * <description>be quiet when run</description>
  389. * <action>StoreTrue/action>
  390. * </option>
  391. * <argument name="files">
  392. * <description>a list of files</description>
  393. * <multiple>true</multiple>
  394. * </argument>
  395. * </command>';
  396. * $parser = Console_CommandLine::fromXmlString($xmldata);
  397. * $result = $parser->parse();
  398. * </code>
  399. *
  400. * @param string $string The xml data
  401. *
  402. * @return Console_CommandLine The parser instance
  403. */
  404. public static function fromXmlString($string)
  405. {
  406. include_once 'Console/CommandLine/XmlParser.php';
  407. return Console_CommandLine_XmlParser::parseString($string);
  408. }
  409. // }}}
  410. // addArgument() {{{
  411. /**
  412. * Adds an argument to the command line parser and returns it.
  413. *
  414. * Adds an argument with the name $name and set its attributes with the
  415. * array $params, then return the Console_CommandLine_Argument instance
  416. * created.
  417. * The method accepts another form: you can directly pass a
  418. * Console_CommandLine_Argument object as the sole argument, this allows
  419. * you to contruct the argument separately, in order to reuse it in
  420. * different command line parsers or commands for example.
  421. *
  422. * Example:
  423. * <code>
  424. * $parser = new Console_CommandLine();
  425. * // add an array argument
  426. * $parser->addArgument('input_files', array('multiple'=>true));
  427. * // add a simple argument
  428. * $parser->addArgument('output_file');
  429. * $result = $parser->parse();
  430. * print_r($result->args['input_files']);
  431. * print_r($result->args['output_file']);
  432. * // will print:
  433. * // array('file1', 'file2')
  434. * // 'file3'
  435. * // if the command line was:
  436. * // myscript.php file1 file2 file3
  437. * </code>
  438. *
  439. * In a terminal, the help will be displayed like this:
  440. * <code>
  441. * $ myscript.php install -h
  442. * Usage: myscript.php <input_files...> <output_file>
  443. * </code>
  444. *
  445. * @param mixed $name A string containing the argument name or an
  446. * instance of Console_CommandLine_Argument
  447. * @param array $params An array containing the argument attributes
  448. *
  449. * @return Console_CommandLine_Argument the added argument
  450. * @see Console_CommandLine_Argument
  451. */
  452. public function addArgument($name, $params = array())
  453. {
  454. if ($name instanceof Console_CommandLine_Argument) {
  455. $argument = $name;
  456. } else {
  457. include_once 'Console/CommandLine/Argument.php';
  458. $argument = new Console_CommandLine_Argument($name, $params);
  459. }
  460. $argument->validate();
  461. $this->args[$argument->name] = $argument;
  462. return $argument;
  463. }
  464. // }}}
  465. // addCommand() {{{
  466. /**
  467. * Adds a sub-command to the command line parser.
  468. *
  469. * Adds a command with the given $name to the parser and returns the
  470. * Console_CommandLine_Command instance, you can then populate the command
  471. * with options, configure it, etc... like you would do for the main parser
  472. * because the class Console_CommandLine_Command inherits from
  473. * Console_CommandLine.
  474. *
  475. * An example:
  476. * <code>
  477. * $parser = new Console_CommandLine();
  478. * $install_cmd = $parser->addCommand('install');
  479. * $install_cmd->addOption(
  480. * 'verbose',
  481. * array(
  482. * 'short_name' => '-v',
  483. * 'long_name' => '--verbose',
  484. * 'description' => 'be noisy when installing stuff',
  485. * 'action' => 'StoreTrue'
  486. * )
  487. * );
  488. * $parser->parse();
  489. * </code>
  490. * Then in a terminal:
  491. * <code>
  492. * $ myscript.php install -h
  493. * Usage: myscript.php install [options]
  494. *
  495. * Options:
  496. * -h, --help display this help message and exit
  497. * -v, --verbose be noisy when installing stuff
  498. *
  499. * $ myscript.php install --verbose
  500. * Installing whatever...
  501. * $
  502. * </code>
  503. *
  504. * @param mixed $name A string containing the command name or an
  505. * instance of Console_CommandLine_Command
  506. * @param array $params An array containing the command attributes
  507. *
  508. * @return Console_CommandLine_Command the added subcommand
  509. * @see Console_CommandLine_Command
  510. */
  511. public function addCommand($name, $params = array())
  512. {
  513. if ($name instanceof Console_CommandLine_Command) {
  514. $command = $name;
  515. } else {
  516. include_once 'Console/CommandLine/Command.php';
  517. $params['name'] = $name;
  518. $command = new Console_CommandLine_Command($params);
  519. // some properties must cascade to the child command if not
  520. // passed explicitely. This is done only in this case, because if
  521. // we have a Command object we have no way to determine if theses
  522. // properties have already been set
  523. $cascade = array(
  524. 'add_help_option',
  525. 'add_version_option',
  526. 'outputter',
  527. 'message_provider',
  528. 'force_posix',
  529. 'force_options_defaults'
  530. );
  531. foreach ($cascade as $property) {
  532. if (!isset($params[$property])) {
  533. $command->$property = $this->$property;
  534. }
  535. }
  536. if (!isset($params['renderer'])) {
  537. $renderer = clone $this->renderer;
  538. $renderer->parser = $command;
  539. $command->renderer = $renderer;
  540. }
  541. }
  542. $command->parent = $this;
  543. $this->commands[$command->name] = $command;
  544. return $command;
  545. }
  546. // }}}
  547. // addOption() {{{
  548. /**
  549. * Adds an option to the command line parser and returns it.
  550. *
  551. * Adds an option with the name $name and set its attributes with the
  552. * array $params, then return the Console_CommandLine_Option instance
  553. * created.
  554. * The method accepts another form: you can directly pass a
  555. * Console_CommandLine_Option object as the sole argument, this allows
  556. * you to contruct the option separately, in order to reuse it in different
  557. * command line parsers or commands for example.
  558. *
  559. * Example:
  560. * <code>
  561. * $parser = new Console_CommandLine();
  562. * $parser->addOption('path', array(
  563. * 'short_name' => '-p', // a short name
  564. * 'long_name' => '--path', // a long name
  565. * 'description' => 'path to the dir', // a description msg
  566. * 'action' => 'StoreString',
  567. * 'default' => '/tmp' // a default value
  568. * ));
  569. * $parser->parse();
  570. * </code>
  571. *
  572. * In a terminal, the help will be displayed like this:
  573. * <code>
  574. * $ myscript.php --help
  575. * Usage: myscript.php [options]
  576. *
  577. * Options:
  578. * -h, --help display this help message and exit
  579. * -p, --path path to the dir
  580. *
  581. * </code>
  582. *
  583. * Various methods to specify an option, these 3 commands are equivalent:
  584. * <code>
  585. * $ myscript.php --path=some/path
  586. * $ myscript.php -p some/path
  587. * $ myscript.php -psome/path
  588. * </code>
  589. *
  590. * @param mixed $name A string containing the option name or an
  591. * instance of Console_CommandLine_Option
  592. * @param array $params An array containing the option attributes
  593. *
  594. * @return Console_CommandLine_Option The added option
  595. * @see Console_CommandLine_Option
  596. */
  597. public function addOption($name, $params = array())
  598. {
  599. include_once 'Console/CommandLine/Option.php';
  600. if ($name instanceof Console_CommandLine_Option) {
  601. $opt = $name;
  602. } else {
  603. $opt = new Console_CommandLine_Option($name, $params);
  604. }
  605. $opt->validate();
  606. if ($this->force_options_defaults) {
  607. $opt->setDefaults();
  608. }
  609. $this->options[$opt->name] = $opt;
  610. if (!empty($opt->choices) && $opt->add_list_option) {
  611. $this->addOption('list_' . $opt->name, array(
  612. 'long_name' => '--list-' . $opt->name,
  613. 'description' => $this->message_provider->get(
  614. 'LIST_OPTION_MESSAGE',
  615. array('name' => $opt->name)
  616. ),
  617. 'action' => 'List',
  618. 'action_params' => array('list' => $opt->choices),
  619. ));
  620. }
  621. return $opt;
  622. }
  623. // }}}
  624. // displayError() {{{
  625. /**
  626. * Displays an error to the user via stderr and exit with $exitCode if its
  627. * value is not equals to false.
  628. *
  629. * @param string $error The error message
  630. * @param int $exitCode The exit code number (default: 1). If set to
  631. * false, the exit() function will not be called
  632. *
  633. * @return void
  634. */
  635. public function displayError($error, $exitCode = 1)
  636. {
  637. $this->outputter->stderr($this->renderer->error($error));
  638. if ($exitCode !== false) {
  639. exit($exitCode);
  640. }
  641. }
  642. // }}}
  643. // displayUsage() {{{
  644. /**
  645. * Displays the usage help message to the user via stdout and exit with
  646. * $exitCode if its value is not equals to false.
  647. *
  648. * @param int $exitCode The exit code number (default: 0). If set to
  649. * false, the exit() function will not be called
  650. *
  651. * @return void
  652. */
  653. public function displayUsage($exitCode = 0)
  654. {
  655. $this->outputter->stdout($this->renderer->usage());
  656. if ($exitCode !== false) {
  657. exit($exitCode);
  658. }
  659. }
  660. // }}}
  661. // displayVersion() {{{
  662. /**
  663. * Displays the program version to the user via stdout and exit with
  664. * $exitCode if its value is not equals to false.
  665. *
  666. *
  667. * @param int $exitCode The exit code number (default: 0). If set to
  668. * false, the exit() function will not be called
  669. *
  670. * @return void
  671. */
  672. public function displayVersion($exitCode = 0)
  673. {
  674. $this->outputter->stdout($this->renderer->version());
  675. if ($exitCode !== false) {
  676. exit($exitCode);
  677. }
  678. }
  679. // }}}
  680. // findOption() {{{
  681. /**
  682. * Finds the option that matches the given short_name (ex: -v), long_name
  683. * (ex: --verbose) or name (ex: verbose).
  684. *
  685. * @param string $str The option identifier
  686. *
  687. * @return mixed A Console_CommandLine_Option instance or false
  688. */
  689. public function findOption($str)
  690. {
  691. $str = trim($str);
  692. if ($str === '') {
  693. return false;
  694. }
  695. $matches = array();
  696. foreach ($this->options as $opt) {
  697. if ($opt->short_name == $str || $opt->long_name == $str ||
  698. $opt->name == $str) {
  699. // exact match
  700. return $opt;
  701. }
  702. if (substr($opt->long_name, 0, strlen($str)) === $str) {
  703. // abbreviated long option
  704. $matches[] = $opt;
  705. }
  706. }
  707. if ($count = count($matches)) {
  708. if ($count > 1) {
  709. $matches_str = '';
  710. $padding = '';
  711. foreach ($matches as $opt) {
  712. $matches_str .= $padding . $opt->long_name;
  713. $padding = ', ';
  714. }
  715. throw Console_CommandLine_Exception::factory(
  716. 'OPTION_AMBIGUOUS',
  717. array('name' => $str, 'matches' => $matches_str),
  718. $this,
  719. $this->messages
  720. );
  721. }
  722. return $matches[0];
  723. }
  724. return false;
  725. }
  726. // }}}
  727. // registerAction() {{{
  728. /**
  729. * Registers a custom action for the parser, an example:
  730. *
  731. * <code>
  732. *
  733. * // in this example we create a "range" action:
  734. * // the user will be able to enter something like:
  735. * // $ <program> -r 1,5
  736. * // and in the result we will have:
  737. * // $result->options['range']: array(1, 5)
  738. *
  739. * require_once 'Console/CommandLine.php';
  740. * require_once 'Console/CommandLine/Action.php';
  741. *
  742. * class ActionRange extends Console_CommandLine_Action
  743. * {
  744. * public function execute($value=false, $params=array())
  745. * {
  746. * $range = explode(',', str_replace(' ', '', $value));
  747. * if (count($range) != 2) {
  748. * throw new Exception(sprintf(
  749. * 'Option "%s" must be 2 integers separated by a comma',
  750. * $this->option->name
  751. * ));
  752. * }
  753. * $this->setResult($range);
  754. * }
  755. * }
  756. * // then we can register our action
  757. * Console_CommandLine::registerAction('Range', 'ActionRange');
  758. * // and now our action is available !
  759. * $parser = new Console_CommandLine();
  760. * $parser->addOption('range', array(
  761. * 'short_name' => '-r',
  762. * 'long_name' => '--range',
  763. * 'action' => 'Range', // note our custom action
  764. * 'description' => 'A range of two integers separated by a comma'
  765. * ));
  766. * // etc...
  767. *
  768. * </code>
  769. *
  770. * @param string $name The name of the custom action
  771. * @param string $class The class name of the custom action
  772. *
  773. * @return void
  774. */
  775. public static function registerAction($name, $class)
  776. {
  777. if (!isset(self::$actions[$name])) {
  778. if (!class_exists($class)) {
  779. self::triggerError('action_class_does_not_exists',
  780. E_USER_ERROR,
  781. array('{$name}' => $name, '{$class}' => $class));
  782. }
  783. self::$actions[$name] = array($class, false);
  784. }
  785. }
  786. // }}}
  787. // triggerError() {{{
  788. /**
  789. * A wrapper for programming errors triggering.
  790. *
  791. * @param string $msgId Identifier of the message
  792. * @param int $level The php error level
  793. * @param array $params An array of search=>replaces entries
  794. *
  795. * @return void
  796. * @todo remove Console::triggerError() and use exceptions only
  797. */
  798. public static function triggerError($msgId, $level, $params=array())
  799. {
  800. if (isset(self::$errors[$msgId])) {
  801. $msg = str_replace(array_keys($params),
  802. array_values($params), self::$errors[$msgId]);
  803. trigger_error($msg, $level);
  804. } else {
  805. trigger_error('unknown error', $level);
  806. }
  807. }
  808. // }}}
  809. // parse() {{{
  810. /**
  811. * Parses the command line arguments and returns a
  812. * Console_CommandLine_Result instance.
  813. *
  814. * @param integer $userArgc Number of arguments (optional)
  815. * @param array $userArgv Array containing arguments (optional)
  816. *
  817. * @return Console_CommandLine_Result The result instance
  818. * @throws Exception on user errors
  819. */
  820. public function parse($userArgc=null, $userArgv=null)
  821. {
  822. $this->_lastopt = false;
  823. $this->_stopflag = false;
  824. $this->addBuiltinOptions();
  825. if ($userArgc !== null && $userArgv !== null) {
  826. $argc = $userArgc;
  827. $argv = $userArgv;
  828. } else {
  829. list($argc, $argv) = $this->getArgcArgv();
  830. }
  831. // build an empty result
  832. include_once 'Console/CommandLine/Result.php';
  833. $result = new Console_CommandLine_Result();
  834. if (!($this instanceof Console_CommandLine_Command)) {
  835. // remove script name if we're not in a subcommand
  836. array_shift($argv);
  837. $argc--;
  838. }
  839. // will contain arguments
  840. $args = array();
  841. foreach ($this->options as $name=>$option) {
  842. $result->options[$name] = $option->default;
  843. }
  844. // parse command line tokens
  845. while ($argc--) {
  846. $token = array_shift($argv);
  847. try {
  848. if ($cmd = $this->_getSubCommand($token)) {
  849. $result->command_name = $cmd->name;
  850. $result->command = $cmd->parse($argc, $argv);
  851. break;
  852. } else {
  853. $this->parseToken($token, $result, $args, $argc);
  854. }
  855. } catch (Exception $exc) {
  856. throw $exc;
  857. }
  858. }
  859. // Parse a null token to allow any undespatched actions to be despatched.
  860. $this->parseToken(null, $result, $args, 0);
  861. // Check if an invalid subcommand was specified. If there are
  862. // subcommands and no arguments, but an argument was provided, it is
  863. // an invalid subcommand.
  864. if ( count($this->commands) > 0
  865. && count($this->args) === 0
  866. && count($args) > 0
  867. ) {
  868. throw Console_CommandLine_Exception::factory(
  869. 'INVALID_SUBCOMMAND',
  870. array('command' => $args[0]),
  871. $this,
  872. $this->messages
  873. );
  874. }
  875. // if subcommand_required is set to true we must check that we have a
  876. // subcommand.
  877. if ( count($this->commands)
  878. && $this->subcommand_required
  879. && !$result->command_name
  880. ) {
  881. throw Console_CommandLine_Exception::factory(
  882. 'SUBCOMMAND_REQUIRED',
  883. array('commands' => implode(array_keys($this->commands), ', ')),
  884. $this,
  885. $this->messages
  886. );
  887. }
  888. // minimum argument number check
  889. $argnum = 0;
  890. foreach ($this->args as $name=>$arg) {
  891. if (!$arg->optional) {
  892. $argnum++;
  893. }
  894. }
  895. if (count($args) < $argnum) {
  896. throw Console_CommandLine_Exception::factory(
  897. 'ARGUMENT_REQUIRED',
  898. array('argnum' => $argnum, 'plural' => $argnum>1 ? 's': ''),
  899. $this,
  900. $this->messages
  901. );
  902. }
  903. // handle arguments
  904. $c = count($this->args);
  905. foreach ($this->args as $name=>$arg) {
  906. $c--;
  907. if ($arg->multiple) {
  908. $result->args[$name] = $c ? array_splice($args, 0, -$c) : $args;
  909. } else {
  910. $result->args[$name] = array_shift($args);
  911. }
  912. if (!$result->args[$name] && $arg->optional && $arg->default) {
  913. $result->args[$name] = $arg->default;
  914. }
  915. // check value is in argument choices
  916. if (!empty($this->args[$name]->choices)) {
  917. foreach ($result->args[$name] as $value) {
  918. if (!in_array($value, $arg->choices)) {
  919. throw Console_CommandLine_Exception::factory(
  920. 'ARGUMENT_VALUE_NOT_VALID',
  921. array(
  922. 'name' => $name,
  923. 'choices' => implode('", "', $arg->choices),
  924. 'value' => implode(' ', $result->args[$name]),
  925. ),
  926. $this,
  927. $arg->messages
  928. );
  929. }
  930. }
  931. }
  932. }
  933. // dispatch deferred options
  934. foreach ($this->_dispatchLater as $optArray) {
  935. $optArray[0]->dispatchAction($optArray[1], $optArray[2], $this);
  936. }
  937. return $result;
  938. }
  939. // }}}
  940. // parseToken() {{{
  941. /**
  942. * Parses the command line token and modifies *by reference* the $options
  943. * and $args arrays.
  944. *
  945. * @param string $token The command line token to parse
  946. * @param object $result The Console_CommandLine_Result instance
  947. * @param array &$args The argv array
  948. * @param int $argc Number of lasting args
  949. *
  950. * @return void
  951. * @access protected
  952. * @throws Exception on user errors
  953. */
  954. protected function parseToken($token, $result, &$args, $argc)
  955. {
  956. $last = $argc === 0;
  957. if (!$this->_stopflag && $this->_lastopt) {
  958. if (strlen($token) > ($this->avoid_reading_stdin ? 1 : 0) &&
  959. substr($token, 0, 1) == '-') {
  960. if ($this->_lastopt->argument_optional) {
  961. $this->_dispatchAction($this->_lastopt, '', $result);
  962. if ($this->_lastopt->action != 'StoreArray') {
  963. $this->_lastopt = false;
  964. }
  965. } else if (isset($result->options[$this->_lastopt->name])) {
  966. // case of an option that expect a list of args
  967. $this->_lastopt = false;
  968. } else {
  969. throw Console_CommandLine_Exception::factory(
  970. 'OPTION_VALUE_REQUIRED',
  971. array('name' => $this->_lastopt->name),
  972. $this,
  973. $this->messages
  974. );
  975. }
  976. } else {
  977. // when a StoreArray option is positioned last, the behavior
  978. // is to consider that if there's already an element in the
  979. // array, and the commandline expects one or more args, we
  980. // leave last tokens to arguments
  981. if ($this->_lastopt->action == 'StoreArray'
  982. && !empty($result->options[$this->_lastopt->name])
  983. && count($this->args) > ($argc + count($args))
  984. ) {
  985. if (!is_null($token)) {
  986. $args[] = $token;
  987. }
  988. return;
  989. }
  990. if (!is_null($token) || $this->_lastopt->action == 'Password') {
  991. $this->_dispatchAction($this->_lastopt, $token, $result);
  992. }
  993. if ($this->_lastopt->action != 'StoreArray') {
  994. $this->_lastopt = false;
  995. }
  996. return;
  997. }
  998. }
  999. if (!$this->_stopflag && substr($token, 0, 2) == '--') {
  1000. // a long option
  1001. $optkv = explode('=', $token, 2);
  1002. if (trim($optkv[0]) == '--') {
  1003. // the special argument "--" forces in all cases the end of
  1004. // option scanning.
  1005. $this->_stopflag = true;
  1006. return;
  1007. }
  1008. $opt = $this->findOption($optkv[0]);
  1009. if (!$opt) {
  1010. throw Console_CommandLine_Exception::factory(
  1011. 'OPTION_UNKNOWN',
  1012. array('name' => $optkv[0]),
  1013. $this,
  1014. $this->messages
  1015. );
  1016. }
  1017. $value = isset($optkv[1]) ? $optkv[1] : false;
  1018. if (!$opt->expectsArgument() && $value !== false) {
  1019. throw Console_CommandLine_Exception::factory(
  1020. 'OPTION_VALUE_UNEXPECTED',
  1021. array('name' => $opt->name, 'value' => $value),
  1022. $this,
  1023. $this->messages
  1024. );
  1025. }
  1026. if ($opt->expectsArgument() && $value === false) {
  1027. // maybe the long option argument is separated by a space, if
  1028. // this is the case it will be the next arg
  1029. if ($last && !$opt->argument_optional) {
  1030. throw Console_CommandLine_Exception::factory(
  1031. 'OPTION_VALUE_REQUIRED',
  1032. array('name' => $opt->name),
  1033. $this,
  1034. $this->messages
  1035. );
  1036. }
  1037. // we will have a value next time
  1038. $this->_lastopt = $opt;
  1039. return;
  1040. }
  1041. if ($opt->action == 'StoreArray') {
  1042. $this->_lastopt = $opt;
  1043. }
  1044. $this->_dispatchAction($opt, $value, $result);
  1045. } else if (!$this->_stopflag &&
  1046. strlen($token) > ($this->avoid_reading_stdin ? 1 : 0) &&
  1047. substr($token, 0, 1) == '-') {
  1048. // a short option
  1049. $optname = substr($token, 0, 2);
  1050. if ($optname == '-' && !$this->avoid_reading_stdin) {
  1051. // special case of "-": try to read stdin
  1052. $args[] = file_get_contents('php://stdin');
  1053. return;
  1054. }
  1055. $opt = $this->findOption($optname);
  1056. if (!$opt) {
  1057. throw Console_CommandLine_Exception::factory(
  1058. 'OPTION_UNKNOWN',
  1059. array('name' => $optname),
  1060. $this,
  1061. $this->messages
  1062. );
  1063. }
  1064. // parse other options or set the value
  1065. // in short: handle -f<value> and -f <value>
  1066. $next = substr($token, 2, 1);
  1067. // check if we must wait for a value
  1068. if (!$next) {
  1069. if ($opt->expectsArgument()) {
  1070. if ($last && !$opt->argument_optional) {
  1071. throw Console_CommandLine_Exception::factory(
  1072. 'OPTION_VALUE_REQUIRED',
  1073. array('name' => $opt->name),
  1074. $this,
  1075. $this->messages
  1076. );
  1077. }
  1078. // we will have a value next time
  1079. $this->_lastopt = $opt;
  1080. return;
  1081. }
  1082. $value = false;
  1083. } else {
  1084. if (!$opt->expectsArgument()) {
  1085. if ($nextopt = $this->findOption('-' . $next)) {
  1086. $this->_dispatchAction($opt, false, $result);
  1087. $this->parseToken('-' . substr($token, 2), $result,
  1088. $args, $last);
  1089. return;
  1090. } else {
  1091. throw Console_CommandLine_Exception::factory(
  1092. 'OPTION_UNKNOWN',
  1093. array('name' => $next),
  1094. $this,
  1095. $this->messages
  1096. );
  1097. }
  1098. }
  1099. if ($opt->action == 'StoreArray') {
  1100. $this->_lastopt = $opt;
  1101. }
  1102. $value = substr($token, 2);
  1103. }
  1104. $this->_dispatchAction($opt, $value, $result);
  1105. } else {
  1106. // We have an argument.
  1107. // if we are in POSIX compliant mode, we must set the stop flag to
  1108. // true in order to stop option parsing.
  1109. if (!$this->_stopflag && $this->force_posix) {
  1110. $this->_stopflag = true;
  1111. }
  1112. if (!is_null($token)) {
  1113. $args[] = $token;
  1114. }
  1115. }
  1116. }
  1117. // }}}
  1118. // addBuiltinOptions() {{{
  1119. /**
  1120. * Adds the builtin "Help" and "Version" options if needed.
  1121. *
  1122. * @return void
  1123. */
  1124. public function addBuiltinOptions()
  1125. {
  1126. if ($this->add_help_option) {
  1127. $helpOptionParams = array(
  1128. 'long_name' => '--help',
  1129. 'description' => 'show this help message and exit',
  1130. 'action' => 'Help'
  1131. );
  1132. if (!($option = $this->findOption('-h')) || $option->action == 'Help') {
  1133. // short name is available, take it
  1134. $helpOptionParams['short_name'] = '-h';
  1135. }
  1136. $this->addOption('help', $helpOptionParams);
  1137. }
  1138. if ($this->add_version_option && !empty($this->version)) {
  1139. $versionOptionParams = array(
  1140. 'long_name' => '--version',
  1141. 'description' => 'show the program version and exit',
  1142. 'action' => 'Version'
  1143. );
  1144. if (!$this->findOption('-v')) {
  1145. // short name is available, take it
  1146. $versionOptionParams['short_name'] = '-v';
  1147. }
  1148. $this->addOption('version', $versionOptionParams);
  1149. }
  1150. }
  1151. // }}}
  1152. // getArgcArgv() {{{
  1153. /**
  1154. * Tries to return an array containing argc and argv, or trigger an error
  1155. * if it fails to get them.
  1156. *
  1157. * @return array The argc/argv array
  1158. * @throws Console_CommandLine_Exception
  1159. */
  1160. protected function getArgcArgv()
  1161. {
  1162. if (php_sapi_name() != 'cli') {
  1163. // we have a web request
  1164. $argv = array($this->name);
  1165. if (isset($_REQUEST)) {
  1166. foreach ($_REQUEST as $key => $value) {
  1167. if (!is_array($value)) {
  1168. $value = array($value);
  1169. }
  1170. $opt = $this->findOption($key);
  1171. if ($opt instanceof Console_CommandLine_Option) {
  1172. // match a configured option
  1173. $argv[] = $opt->short_name ?
  1174. $opt->short_name : $opt->long_name;
  1175. foreach ($value as $v) {
  1176. if ($opt->expectsArgument()) {
  1177. $argv[] = isset($_REQUEST[$key]) ? urldecode($v) : $v;
  1178. } else if ($v == '0' || $v == 'false') {
  1179. array_pop($argv);
  1180. }
  1181. }
  1182. } else if (isset($this->args[$key])) {
  1183. // match a configured argument
  1184. foreach ($value as $v) {
  1185. $argv[] = isset($_REQUEST[$key]) ? urldecode($v) : $v;
  1186. }
  1187. }
  1188. }
  1189. }
  1190. return array(count($argv), $argv);
  1191. }
  1192. if (isset($argc) && isset($argv)) {
  1193. // case of register_argv_argc = 1
  1194. return array($argc, $argv);
  1195. }
  1196. if (isset($_SERVER['argc']) && isset($_SERVER['argv'])) {
  1197. return array($_SERVER['argc'], $_SERVER['argv']);
  1198. }
  1199. return array(0, array());
  1200. }
  1201. // }}}
  1202. // _dispatchAction() {{{
  1203. /**
  1204. * Dispatches the given option or store the option to dispatch it later.
  1205. *
  1206. * @param Console_CommandLine_Option $option The option instance
  1207. * @param string $token Command line token to parse
  1208. * @param Console_CommandLine_Result $result The result instance
  1209. *
  1210. * @return void
  1211. */
  1212. private function _dispatchAction($option, $token, $result)
  1213. {
  1214. if ($option->action == 'Password') {
  1215. $this->_dispatchLater[] = array($option, $token, $result);
  1216. } else {
  1217. $option->dispatchAction($token, $result, $this);
  1218. }
  1219. }
  1220. // }}}
  1221. // _getSubCommand() {{{
  1222. /**
  1223. * Tries to return the subcommand that matches the given token or returns
  1224. * false if no subcommand was found.
  1225. *
  1226. * @param string $token Current command line token
  1227. *
  1228. * @return mixed An instance of Console_CommandLine_Command or false
  1229. */
  1230. private function _getSubCommand($token)
  1231. {
  1232. foreach ($this->commands as $cmd) {
  1233. if ($cmd->name == $token || in_array($token, $cmd->aliases)) {
  1234. return $cmd;
  1235. }
  1236. }
  1237. return false;
  1238. }
  1239. // }}}
  1240. }