PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/htdocs/solar/1.1.1/source/solar/Solar/Getopt.php

http://github.com/pmjones/php-framework-benchmarks
PHP | 647 lines | 247 code | 65 blank | 335 comment | 62 complexity | 9e93698e68c37d4ac4c861aa03c656ef MD5 | raw file
Possible License(s): LGPL-3.0, Apache-2.0, BSD-3-Clause, ISC, AGPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. *
  4. * Retrieves and validates command-line options and parameter values.
  5. *
  6. * @category Solar
  7. *
  8. * @package Solar_Getopt Command-line option parsing.
  9. *
  10. * @author Clay Loveless <clay@killersoft.com>
  11. *
  12. * @author Paul M. Jones <pmjones@solarphp.com>
  13. *
  14. * @license http://opensource.org/licenses/bsd-license.php BSD
  15. *
  16. * @version $Id: Getopt.php 4384 2010-02-14 16:56:47Z pmjones $
  17. *
  18. */
  19. class Solar_Getopt extends Solar_Base
  20. {
  21. /**
  22. *
  23. * Default configuration values.
  24. *
  25. * @config string filter_class The data-filter class to use when
  26. * validating and sanitizing parameter values.
  27. *
  28. * @config bool strict In strict mode, throw an exception when an
  29. * unknown option is passed into getopt.
  30. *
  31. * @var array
  32. *
  33. */
  34. protected $_Solar_Getopt = array(
  35. 'filter_class' => 'Solar_Filter',
  36. 'strict' => true,
  37. );
  38. /**
  39. *
  40. * The array of acceptable options.
  41. *
  42. * The `$options` array contains all options accepted by the
  43. * application, including their types, default values, descriptions,
  44. * requirements, and validation callbacks.
  45. *
  46. * In general, you should not try to set $options yourself;
  47. * instead, use [[Solar_Getopt::setOption()]] and/or
  48. * [[Solar_Getopt::setOptions()]].
  49. *
  50. * @var array
  51. *
  52. */
  53. public $options = array();
  54. /**
  55. *
  56. * Default option settings.
  57. *
  58. * `long`
  59. * : (string) The long-form of the option name (e.g., "--foo-bar" would
  60. * be "foo-bar").
  61. *
  62. * `short`
  63. * : (string) The short-form of the option, if any (e.g., "-f" would be
  64. * "f").
  65. *
  66. * `descr`
  67. * : (string) A description of the option (used in "help" output).
  68. *
  69. * `param`
  70. * : (string) When the option is present, does it take a parameter? If so,
  71. * the param can be "r[eq[uired]]" every time, or be "[o[pt[ional]]". If empty, no
  72. * parameter for the option will be recognized (the option's value will be
  73. * boolean true when the option is present). Default is null;
  74. * recognizes `o`', `opt`, `optional`, `r`, `req`, and `required`.
  75. *
  76. * `value`
  77. * : (mixed) The default value for the option parameter, if any. This way,
  78. * options not specified in the arguments can have a default value.
  79. *
  80. * `require`
  81. * : (bool) At validation time, the option must have a non-blank value
  82. * of some sort.
  83. *
  84. * `filters`
  85. * : (array) An array of filters to apply to the parameter value. This can
  86. * be a single filter (`array('validateInt')`), or a series of filters
  87. * (`array('validateInt', array('validateRange', -10, +10)`).
  88. *
  89. * @var array
  90. *
  91. */
  92. protected $_default = array(
  93. 'long' => null,
  94. 'short' => null,
  95. 'param' => null,
  96. 'value' => null,
  97. 'descr' => null,
  98. 'require' => false,
  99. 'filters' => array(),
  100. );
  101. /**
  102. *
  103. * The arguments passed in from the command line.
  104. *
  105. * @var array
  106. *
  107. * @see populate()
  108. *
  109. */
  110. protected $_argv;
  111. /**
  112. *
  113. * List of names for invalid option values, and error messages.
  114. *
  115. * @var array
  116. *
  117. */
  118. protected $_invalid = array();
  119. /**
  120. *
  121. * Option values parsed from the arguments, as well as remaining (numeric)
  122. * arguments.
  123. *
  124. * @var array
  125. *
  126. */
  127. protected $_values;
  128. /**
  129. *
  130. * Post-construction tasks to complete object construction.
  131. *
  132. * @return void
  133. *
  134. */
  135. protected function _postConstruct()
  136. {
  137. parent::_postConstruct();
  138. // get the current request environment
  139. $this->_request = Solar_Registry::get('request');
  140. // set up the data-filter class
  141. $this->_filter = Solar::factory($this->_config['filter_class']);
  142. }
  143. // -----------------------------------------------------------------
  144. //
  145. // Option-management methods
  146. //
  147. // -----------------------------------------------------------------
  148. /**
  149. *
  150. * Sets one option for recognition.
  151. *
  152. * @param string $name The option name to set or add; overrides
  153. * $info['short'] if 1 character long, otherwise overrides $info['long'].
  154. *
  155. * @param array $info Option information using the same keys
  156. * as [[Solar_Getopt::$_default]].
  157. *
  158. * @return void
  159. *
  160. */
  161. public function setOption($name, $info)
  162. {
  163. // prepare the option info
  164. $info = array_merge($this->_default, $info);
  165. // override the short- or long-form of the option
  166. if (strlen($name) == 1) {
  167. $info['short'] = $name;
  168. } else {
  169. // convert underscores to dashes for the *cli*
  170. $info['long'] = str_replace('_', '-', $name);
  171. }
  172. // normalize the "param" setting
  173. $param = strtolower($info['param']);
  174. if ($param == 'r' || substr($param, 0, 3) == 'req') {
  175. $info['param'] = 'required';
  176. } elseif ($param == 'o' || substr($param, 0, 3) == 'opt') {
  177. $info['param'] = 'optional';
  178. } else {
  179. $info['param'] = null;
  180. }
  181. // convert dashes to underscores for the *key*
  182. $name = str_replace('-', '_', $name);
  183. // forcibly cast each of the keys in the options array
  184. $this->options[$name] = array(
  185. 'long' => $info['long'],
  186. 'short' => substr($info['short'], 0, 1),
  187. 'param' => $info['param'],
  188. 'value' => $info['value'],
  189. 'descr' => $info['descr'],
  190. 'require' => (bool) $info['require'],
  191. 'filters' => array(),
  192. 'present' => false, // present in the cli command?
  193. );
  194. // retain and fix any filters for the option value
  195. if ($info['filters']) {
  196. // make sure filters are an array
  197. settype($info['filters'], 'array');
  198. // make sure that strings are converted to arrays so that
  199. // validate() works properly.
  200. foreach ($info['filters'] as $key => $list) {
  201. if (is_string($list)) {
  202. $info['filters'][$key] = array($list);
  203. }
  204. }
  205. }
  206. }
  207. /**
  208. *
  209. * Sets multiple acceptable options. Appends if they do not exist.
  210. *
  211. * @param array $list Argument information as array(name => info), where
  212. * each info value is an array like Solar_Getopt::$_default.
  213. *
  214. * @return void
  215. *
  216. */
  217. public function setOptions($list)
  218. {
  219. if (! empty($list)) {
  220. foreach ($list as $name => $info) {
  221. $this->setOption($name, $info);
  222. }
  223. }
  224. }
  225. /**
  226. *
  227. * Populates the options with values from $argv.
  228. *
  229. * For a given option on the command line, these values will result:
  230. *
  231. * `--foo-bar`
  232. * : `'foo_bar' => true`
  233. *
  234. * `--foo-bar=baz`
  235. * : `'foo_bar' => 'baz'`
  236. *
  237. * `--foo-bar="baz dib zim"`
  238. * : `'foo_bar' => 'baz dib zim'`
  239. *
  240. * `-s`
  241. * : `'s' => true`
  242. *
  243. * `-s dib`
  244. * : `'s' => 'dib'`
  245. *
  246. * `-s "dib zim gir"`
  247. * : `'s' => 'dib zim gir'`
  248. *
  249. * Short-option clusters are parsed as well, so that `-fbz` will result
  250. * in `array('f' => true, 'b' => true, 'z' => true)`. Note that you
  251. * cannot pass parameters to an option in a cluster.
  252. *
  253. * If an option is not defined, an exception will be thrown.
  254. *
  255. * Options values are stored under the option key name, not the short-
  256. * or long-format version of the option. For example, an option named
  257. * 'foo-bar' with a short-form of 'f' will be stored under 'foo-bar'.
  258. * This helps deconflict between long- and short-form aliases.
  259. *
  260. * @param array $argv The argument values passed on the command line. If
  261. * empty, will use $_SERVER['argv'] after shifting off its first element.
  262. *
  263. * @return void
  264. *
  265. */
  266. public function populate($argv = null)
  267. {
  268. // get the command-line arguments
  269. if ($argv === null) {
  270. $argv = $this->_request->argv();
  271. array_shift($argv);
  272. } else {
  273. $argv = (array) $argv;
  274. }
  275. // hold onto the argv source
  276. $this->_argv = $argv;
  277. // reset values to defaults
  278. $this->_values = array();
  279. foreach ($this->options as $name => $info) {
  280. $this->_values[$name] = $info['value'];
  281. }
  282. // flag to say when we've reached the end of options
  283. $done = false;
  284. // shift each element from the top of the $argv source
  285. while (true) {
  286. // get the next argument
  287. $arg = array_shift($this->_argv);
  288. if ($arg === null) {
  289. // no more args, we're done
  290. break;
  291. }
  292. // after a plain double-dash, all values are numeric (not options)
  293. if ($arg == '--') {
  294. $done = true;
  295. continue;
  296. }
  297. // if we're reached the end of options, just add to the numeric
  298. // values.
  299. if ($done) {
  300. $this->_values[] = $arg;
  301. continue;
  302. }
  303. // long, short, or numeric?
  304. if (substr($arg, 0, 2) == '--') {
  305. // long
  306. $this->_values = array_merge(
  307. $this->_values,
  308. (array) $this->_parseLong($arg)
  309. );
  310. } elseif (substr($arg, 0, 1) == '-') {
  311. // short
  312. $this->_values = array_merge(
  313. $this->_values,
  314. (array) $this->_parseShort($arg)
  315. );
  316. } else {
  317. // numeric
  318. $this->_values[] = $arg;
  319. }
  320. }
  321. }
  322. /**
  323. *
  324. * Applies validation and sanitizing filters to the option values.
  325. *
  326. * @return bool True if all values are valid, false if not.
  327. *
  328. */
  329. public function validate()
  330. {
  331. // reset previous invalidations
  332. $this->_invalid = array();
  333. // reset the filter chain so we can rebuild it
  334. $this->_filter->resetChain();
  335. // build the filter chain and requires
  336. foreach ($this->options as $name => $info) {
  337. if ($info['present'] && $info['param'] == 'required') {
  338. $info['filters'][] = 'validateNotBlank';
  339. }
  340. $this->_filter->addChainFilters($name, $info['filters']);
  341. $this->_filter->setChainRequire($name, $info['require']);
  342. }
  343. // apply the filter chain to the option values
  344. $status = $this->_filter->applyChain($this->_values);
  345. // retain any invalidation messages
  346. $invalid = $this->_filter->getChainInvalid();
  347. foreach ((array) $invalid as $key => $val) {
  348. $this->_invalid[$key] = $val;
  349. }
  350. // done
  351. return $status;
  352. }
  353. /**
  354. *
  355. * Returns a list of invalid options and their error messages (if any).
  356. *
  357. * @return array
  358. *
  359. */
  360. public function getInvalid()
  361. {
  362. return $this->_invalid;
  363. }
  364. /**
  365. *
  366. * Returns the populated option values.
  367. *
  368. * @return array
  369. *
  370. */
  371. public function values()
  372. {
  373. return $this->_values;
  374. }
  375. /**
  376. *
  377. * Parse a long-form option.
  378. *
  379. * @param string $arg The $argv element, e.g. "--foo" or "--bar=baz".
  380. *
  381. * @return array An associative array where the key is the option name and
  382. * the value is the option value.
  383. *
  384. */
  385. protected function _parseLong($arg)
  386. {
  387. // strip the leading "--"
  388. $arg = substr($arg, 2);
  389. // find the first = sign
  390. $eqpos = strpos($arg, '=');
  391. // get the key for name lookup
  392. if ($eqpos === false) {
  393. $key = $arg;
  394. $value = null;
  395. } else {
  396. $key = substr($arg, 0, $eqpos);
  397. $value = substr($arg, $eqpos+1);
  398. }
  399. // is this a recognized option?
  400. $name = $this->_getOptionName('long', $key);
  401. if (! $name) {
  402. return;
  403. }
  404. // the option is present
  405. $this->options[$name]['present'] = true;
  406. // was a value specified with equals?
  407. if ($eqpos !== false) {
  408. // parse the value for the option param
  409. return $this->_parseParam($name, $value);
  410. }
  411. // value was not specified with equals;
  412. // is a param needed at all?
  413. $info = $this->options[$name];
  414. if (! $info['param']) {
  415. // defined as not-needing a param, treat as a flag.
  416. return array($name => true);
  417. }
  418. // the option was defined as needing a param (required or optional),
  419. // but there was no equals-sign. this means we need to look at the
  420. // next element for a possible param value.
  421. //
  422. // get the next element from $argv to see if it's a param.
  423. $value = array_shift($this->_argv);
  424. // make sure the element not an option itself.
  425. if (substr($value, 0, 1) == '-') {
  426. // the next element is an option, not a param.
  427. // this means no param is present.
  428. // put the element back into $argv.
  429. array_unshift($this->_argv, $value);
  430. // was the missing param required?
  431. if ($info['param'] == 'required') {
  432. // required but not present
  433. return array($name => null);
  434. } else {
  435. // optional but not present, treat as a flag
  436. return array($name => true);
  437. }
  438. }
  439. // parse the parameter for a required or optional value
  440. return $this->_parseParam($name, $value);
  441. }
  442. /**
  443. *
  444. * Parse the parameter value for a named option.
  445. *
  446. * @param string $name The option name.
  447. *
  448. * @param string $value The parameter.
  449. *
  450. * @return array An associative array where the option name is the key,
  451. * and the parsed parameter is the value.
  452. *
  453. */
  454. protected function _parseParam($name, $value)
  455. {
  456. // get info about the option
  457. $info = $this->options[$name];
  458. // is the value blank?
  459. if (trim($value) == '') {
  460. // value is blank. was it required for the option?
  461. if ($info['param'] == 'required') {
  462. // required but blank.
  463. return array($name => null);
  464. } else {
  465. // optional but blank, treat as a flag.
  466. return array($name => true);
  467. }
  468. }
  469. // param was present and not blank.
  470. return array($name => $value);
  471. }
  472. /**
  473. *
  474. * Parse a short-form option (or cluster of options).
  475. *
  476. * @param string $arg The $argv element, e.g. "-f" or "-fbz".
  477. *
  478. * @param bool $cluster This option is part of a cluster.
  479. *
  480. * @return array An associative array where the key is the option name and
  481. * the value is the option value.
  482. *
  483. */
  484. protected function _parseShort($arg, $cluster = false)
  485. {
  486. // strip the leading "-"
  487. $arg = substr($arg, 1);
  488. // re-process as a cluster?
  489. if (strlen($arg) > 1) {
  490. $data = array();
  491. foreach (str_split($arg) as $key) {
  492. $data = array_merge(
  493. $data,
  494. (array) $this->_parseShort("-$key", true)
  495. );
  496. }
  497. return $data;
  498. }
  499. // is the option defined?
  500. $name = $this->_getOptionName('short', $arg);
  501. if (! $name) {
  502. // not defined
  503. return;
  504. } else {
  505. // keep the option info
  506. $info = $this->options[$name];
  507. }
  508. // the option is present
  509. $this->options[$name]['present'] = true;
  510. // are we processing as part of a cluster?
  511. if ($cluster) {
  512. // is a param required for the option?
  513. if ($info['param'] == 'required') {
  514. // can't get params when in a cluster.
  515. return array($name => null);
  516. } else {
  517. // param was optional or not needed, treat as a flag.
  518. return array($name => true);
  519. }
  520. }
  521. // not processing as part of a cluster.
  522. // does the option need a param?
  523. if (! $info['param']) {
  524. // defined as not-needing a param, treat as a flag.
  525. return array($name => true);
  526. }
  527. // the option was defined as needing a param (required or optional).
  528. // get the next element from $argv to see if it's a param.
  529. $value = array_shift($this->_argv);
  530. // make sure the element not an option itself.
  531. if (substr($value, 0, 1) == '-') {
  532. // the next element is an option, not a param.
  533. // this means no param is present.
  534. // put the element back into $argv.
  535. array_unshift($this->_argv, $value);
  536. // was the missing param required?
  537. if ($info['param'] == 'required') {
  538. // required but not present
  539. return array($name => null);
  540. } else {
  541. // optional but not present, treat as a flag
  542. return array($name => true);
  543. }
  544. }
  545. // parse the parameter for a required or optional value
  546. return $this->_parseParam($name, $value);
  547. }
  548. /**
  549. *
  550. * Gets an option name from its short or long format.
  551. *
  552. * @param string $type Look in the 'long' or 'short' key for option names.
  553. *
  554. * @param string $spec The long or short format of the option name.
  555. *
  556. * @return string
  557. *
  558. */
  559. protected function _getOptionName($type, $spec)
  560. {
  561. foreach ($this->options as $name => $info) {
  562. if ($info[$type] == $spec) {
  563. return $name;
  564. }
  565. }
  566. // if not in strict mode, we can let this go
  567. if (! $this->_config['strict']) {
  568. return;
  569. }
  570. // not found, blow up
  571. if ($type == 'short') {
  572. $spec = "-$spec";
  573. } else {
  574. $spec = "--$spec";
  575. }
  576. throw $this->_exception('ERR_UNKNOWN_OPTION', array(
  577. 'type' => $type,
  578. 'name' => $spec,
  579. 'options' => $this->options,
  580. ));
  581. }
  582. }