PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Aura/Cli/Getopt.php

https://bitbucket.org/harikt/aura.cli
PHP | 452 lines | 183 code | 52 blank | 217 comment | 42 complexity | 62e0bcf798147d83d8c623e5840306c5 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. *
  4. * This file is part of the Aura project for PHP.
  5. *
  6. * @package Aura.Cli
  7. *
  8. * @license http://opensource.org/licenses/bsd-license.php BSD
  9. *
  10. */
  11. namespace Aura\Cli;
  12. /**
  13. *
  14. * Retrieves and validates command-line options and parameter values.
  15. *
  16. * @package Aura.Cli
  17. *
  18. */
  19. class Getopt
  20. {
  21. /**
  22. *
  23. * If an option is passed that is not defined, throw an exception.
  24. *
  25. * @const bool
  26. *
  27. */
  28. const STRICT = true;
  29. /**
  30. *
  31. * Do not throw exceptions when undefined options are passed.
  32. *
  33. * @const bool
  34. *
  35. */
  36. const NON_STRICT = false;
  37. /**
  38. *
  39. * A factory to create Option objects.
  40. *
  41. * @var OptionFactory
  42. *
  43. */
  44. protected $option_factory;
  45. /**
  46. *
  47. * Definitions for recognized options.
  48. *
  49. * @var array
  50. *
  51. */
  52. protected $options = [];
  53. /**
  54. *
  55. * Remaining non-option params after loading option values.
  56. *
  57. * @var array
  58. *
  59. */
  60. protected $params = [];
  61. /**
  62. *
  63. * The incoming arguments, typically from $_SERVER['argv'].
  64. *
  65. * @param array
  66. *
  67. */
  68. protected $argv = [];
  69. /**
  70. *
  71. * Constructor.
  72. *
  73. * @param OptionFactory $option_factory A factory for Option objects.
  74. *
  75. */
  76. public function __construct(OptionFactory $option_factory)
  77. {
  78. $this->option_factory = $option_factory;
  79. }
  80. /**
  81. *
  82. * Make Option values available as magic readonly properties.
  83. *
  84. * @param string $key The option name.
  85. *
  86. * @return mixed The option value.
  87. *
  88. */
  89. public function __get($key)
  90. {
  91. $option = $this->getOption($key);
  92. if ($option) {
  93. return $option->getValue();
  94. }
  95. }
  96. /**
  97. *
  98. * Initializes the instance with option definitions.
  99. *
  100. * @param array $opts An array of key-value pairs where the key is the
  101. * option name and the value is the option spec.
  102. *
  103. * @param bool $strict Initialize in strict (true) or non-strict (false)
  104. * mode?
  105. *
  106. * @return void
  107. *
  108. */
  109. public function init(array $opts, $strict = self::STRICT)
  110. {
  111. if ($this->options) {
  112. throw new Exception('Already initialized.');
  113. }
  114. foreach ($opts as $name => $spec) {
  115. if (! is_array($spec)) {
  116. throw new \UnexpectedValueException;
  117. }
  118. $spec['name'] = $name;
  119. $this->options[$name] = $this->option_factory->newInstance($spec);
  120. }
  121. $this->strict = $strict;
  122. }
  123. /**
  124. *
  125. * Returns all the Option definition objects.
  126. *
  127. * @return array An array of Option objects.
  128. *
  129. */
  130. public function getOptions()
  131. {
  132. return $this->options;
  133. }
  134. /**
  135. *
  136. * Returns a single Option definition object by its property name.
  137. *
  138. * @param string $prop The property name of the option.
  139. *
  140. * @return Option
  141. *
  142. */
  143. public function getOption($prop)
  144. {
  145. if (array_key_exists($prop, $this->options)) {
  146. return $this->options[$prop];
  147. }
  148. if ($this->strict) {
  149. throw new Exception\OptionNotDefined($prop);
  150. }
  151. }
  152. /**
  153. *
  154. * Returns an array of all Option names and their values.
  155. *
  156. * @return array
  157. *
  158. */
  159. public function getOptionValues()
  160. {
  161. $vals = [];
  162. foreach ($this->getOptions() as $name => $option) {
  163. $vals[$name] = $option->getValue();
  164. }
  165. return $vals;
  166. }
  167. /**
  168. *
  169. * Returns the value of a single Option by name.
  170. *
  171. * @param string $name The option name to get a value for.
  172. *
  173. * @return mixed
  174. *
  175. */
  176. public function getOptionValue($name)
  177. {
  178. $option = $this->getOption($name);
  179. if ($option) {
  180. return $option->getValue();
  181. }
  182. }
  183. /**
  184. *
  185. * Returns an array of all numeric parameters.
  186. *
  187. * @return array
  188. *
  189. */
  190. public function getParams()
  191. {
  192. return $this->params;
  193. }
  194. /**
  195. *
  196. * Returns a single Option definition object by its long-format name.
  197. *
  198. * @param string $long The long-format name of the option.
  199. *
  200. * @return Option
  201. *
  202. */
  203. public function getLongOption($long)
  204. {
  205. foreach ($this->options as $option) {
  206. if ($option->getLong() == $long) {
  207. return $option;
  208. }
  209. }
  210. if ($this->strict) {
  211. throw new Exception\OptionNotDefined("--$long");
  212. }
  213. }
  214. /**
  215. *
  216. * Returns a single Option definition object by its short-format name.
  217. *
  218. * @param string $char The long-format name of the option.
  219. *
  220. * @return Option
  221. *
  222. */
  223. public function getShortOption($char)
  224. {
  225. foreach ($this->options as $option) {
  226. if ($option->getShort() == $char) {
  227. return $option;
  228. }
  229. }
  230. if ($this->strict) {
  231. throw new Exception\OptionNotDefined("-$char");
  232. }
  233. }
  234. /**
  235. *
  236. * Loads Option values from an argument array, placing option values
  237. * in the defined Option objects and placing non-option params in a
  238. * `$params` variable.
  239. *
  240. * @param array $argv An argument array, typically from $_SERVER['argv'].
  241. *
  242. * @return void
  243. *
  244. */
  245. public function load(array $argv)
  246. {
  247. // hold onto the argv source
  248. $this->argv = $argv;
  249. // remaining non-option params
  250. $params = [];
  251. // flag to say when we've reached the end of options
  252. $done = false;
  253. // shift each element from the top of the $argv source
  254. while ($this->argv) {
  255. // get the next argument
  256. $arg = array_shift($this->argv);
  257. // after a plain double-dash, all values are numeric (not options)
  258. if ($arg == '--') {
  259. $done = true;
  260. continue;
  261. }
  262. // if we're reached the end of options, just add to the params
  263. if ($done) {
  264. $this->params[] = $arg;
  265. continue;
  266. }
  267. // long option, short option, or numeric param?
  268. if (substr($arg, 0, 2) == '--') {
  269. $this->loadLong($arg);
  270. } elseif (substr($arg, 0, 1) == '-') {
  271. $this->loadShort($arg);
  272. } else {
  273. $this->params[] = $arg;
  274. }
  275. }
  276. }
  277. /**
  278. *
  279. * Parses a long-form option.
  280. *
  281. * @param string $spec The `$argv` element, e.g. "--foo" or "--bar=baz".
  282. *
  283. * @return void
  284. *
  285. */
  286. protected function loadLong($spec)
  287. {
  288. // take the leading "--" off the specification
  289. $spec = substr($spec, 2);
  290. // split the spec into name and value
  291. $pos = strpos($spec, '=');
  292. if ($pos === false) {
  293. $name = $spec;
  294. $value = null;
  295. } else {
  296. $name = substr($spec, 0, $pos);
  297. $value = substr($spec, $pos + 1);
  298. }
  299. // get the option object
  300. $option = $this->getLongOption($name);
  301. if (! $option) {
  302. return;
  303. }
  304. // if param is required but not present, blow up
  305. if ($option->isParamRequired() && $value === null) {
  306. throw new Exception\OptionParamRequired;
  307. }
  308. // if params are rejected and one is present, blow up
  309. if ($option->isParamRejected() && $value !== null) {
  310. throw new Exception\OptionParamRejected;
  311. }
  312. // if param is optional but not present, set to true
  313. if ($option->isParamOptional() && $value === null) {
  314. $option->setValue(true);
  315. } else {
  316. $option->setValue($value);
  317. }
  318. }
  319. /**
  320. *
  321. * Parses a short-form option (or cluster of options).
  322. *
  323. * @param string $spec The `$argv` element, e.g. "-f" or "-fbz".
  324. *
  325. * @return void
  326. *
  327. */
  328. protected function loadShort($spec)
  329. {
  330. // if we have a string like "-abcd", process as a cluster
  331. if (strlen($spec) > 2) {
  332. return $this->loadShortCluster($spec);
  333. }
  334. // get the option character (after the first "-")
  335. $char = substr($spec, 1);
  336. // get the option object
  337. $option = $this->getShortOption($char);
  338. if (! $option) {
  339. return;
  340. }
  341. // if the option does not need a param, flag as true and move on
  342. if ($option->isParamRejected()) {
  343. $option->setValue(true);
  344. return;
  345. }
  346. // the option was defined as needing a param (required or optional).
  347. // peek at the next element from $argv ...
  348. $value = reset($this->argv);
  349. // ... and see if it's a param. can be empty, too, which indicates
  350. // then end of the arguments.
  351. $is_param = ! empty($value) && substr($value, 0, 1) != '-';
  352. if (! $is_param && $option->isParamOptional()) {
  353. // the next value is not a param, but a param is optional,
  354. // so flag the option as true and move on.
  355. $option->setValue(true);
  356. return;
  357. }
  358. if (! $is_param && $option->isParamRequired()) {
  359. // the next value is not a param, but a param is required,
  360. // so blow up.
  361. throw new Exception\OptionParamRequired;
  362. }
  363. // at this point, the value is a param, and it's optional or required.
  364. // pull it out of the arguments for real ...
  365. $value = array_shift($this->argv);
  366. // ... and set it.
  367. $option->setValue($value);
  368. }
  369. /**
  370. *
  371. * Parses a cluster of short options.
  372. *
  373. * @param string $spec The short-option cluster (e.g. "-abcd").
  374. *
  375. * @return void
  376. *
  377. */
  378. protected function loadShortCluster($spec)
  379. {
  380. // drop the leading dash
  381. $spec = substr($spec, 1);
  382. // loop through each character in the cluster
  383. $k = strlen($spec);
  384. for ($i = 0; $i < $k; $i ++) {
  385. // get the right character from the cluster
  386. $char = $spec[$i];
  387. // get the option object
  388. $option = $this->getShortOption($char);
  389. if (! $option) {
  390. continue;
  391. }
  392. // can't process params in a cluster
  393. if ($option->isParamRequired()) {
  394. throw new Exception\OptionParamRequired;
  395. }
  396. // otherwise, set the value as a flag
  397. $option->setValue(true);
  398. }
  399. }
  400. }