GetOption.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <?php
  2. /**
  3. * Hoa
  4. *
  5. *
  6. * @license
  7. *
  8. * New BSD License
  9. *
  10. * Copyright © 2007-2017, Hoa community. All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are met:
  14. * * Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. * * Redistributions in binary form must reproduce the above copyright
  17. * notice, this list of conditions and the following disclaimer in the
  18. * documentation and/or other materials provided with the distribution.
  19. * * Neither the name of the Hoa nor the names of its contributors may be
  20. * used to endorse or promote products derived from this software without
  21. * specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
  27. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. */
  35. namespace Hoa\Console;
  36. use Hoa\Ustring;
  37. /**
  38. * Class \Hoa\Console\GetOption.
  39. *
  40. * This class is complementary to the \Hoa\Console\Parser class.
  41. * This class manages the options profile for a command, i.e. argument,
  42. * interactivity, option name etc.
  43. * And, of course, it proposes the getOption method, that allow user to loop
  44. * easily the command options/arguments.
  45. *
  46. * @copyright Copyright © 2007-2017 Hoa community
  47. * @license New BSD License
  48. */
  49. class GetOption
  50. {
  51. /**
  52. * Argument: no argument is needed.
  53. *
  54. * @const int
  55. */
  56. const NO_ARGUMENT = 0;
  57. /**
  58. * Argument: required.
  59. *
  60. * @const int
  61. */
  62. const REQUIRED_ARGUMENT = 1;
  63. /**
  64. * Argument: optional.
  65. *
  66. * @const int
  67. */
  68. const OPTIONAL_ARGUMENT = 2;
  69. /**
  70. * Option bucket: name.
  71. *
  72. * @const int
  73. */
  74. const OPTION_NAME = 0;
  75. /**
  76. * Option bucket: has argument.
  77. *
  78. * @const int
  79. */
  80. const OPTION_HAS_ARG = 1;
  81. /**
  82. * Option bucket: value.
  83. *
  84. * @const int
  85. */
  86. const OPTION_VAL = 2;
  87. /**
  88. * Describe the command options (or switches).
  89. * An option is describing like this:
  90. * name, has_arg, val
  91. * (In C, we got the flag data before val, but it does not have sens here).
  92. *
  93. * The name is the option name and the long option value.
  94. * The has_arg is a constant: NO_ARGUMENT, REQUIRED_ARGUMENT, and
  95. * OPTIONAL_ARGUMENT.
  96. * The val is the short option value.
  97. *
  98. * @var array
  99. */
  100. protected $_options = [];
  101. /**
  102. * Parser.
  103. *
  104. * @var \Hoa\Console\Parser
  105. */
  106. protected $_parser = null;
  107. /**
  108. * The pipette contains all the short value of options.
  109. *
  110. * @var array
  111. */
  112. protected $_pipette = [];
  113. /**
  114. * Prepare the pipette.
  115. *
  116. * @param array $options The option definition.
  117. * @param \Hoa\Console\Parser $parser The parser.
  118. * @throws \Hoa\Console\Exception
  119. */
  120. public function __construct(array $options, Parser $parser)
  121. {
  122. $this->_options = $options;
  123. $this->_parser = $parser;
  124. if (empty($options)) {
  125. $this->_pipette[null] = null;
  126. return;
  127. }
  128. $names = [];
  129. foreach ($options as $i => $option) {
  130. if (isset($option[self::OPTION_NAME])) {
  131. $names[$option[self::OPTION_NAME]] = $i;
  132. }
  133. if (isset($option[self::OPTION_VAL])) {
  134. $names[$option[self::OPTION_VAL]] = $i;
  135. }
  136. }
  137. $_names = array_keys($names);
  138. $switches = $parser->getSwitches();
  139. foreach ($switches as $name => $value) {
  140. if (false === in_array($name, $_names)) {
  141. if (1 === strlen($name)) {
  142. $this->_pipette[] = ['__ambiguous', [
  143. 'solutions' => [],
  144. 'value' => $value,
  145. 'option' => $name
  146. ]];
  147. continue;
  148. }
  149. $haystack = implode(';', $_names);
  150. $differences = (int) ceil(strlen($name) / 3);
  151. $searched = Ustring\Search::approximated(
  152. $haystack,
  153. $name,
  154. $differences
  155. );
  156. $solutions = [];
  157. foreach ($searched as $s) {
  158. $h = substr($haystack, $s['i'], $s['l']);
  159. if (false !== strpos($h, ';') ||
  160. false !== in_array($h, array_keys($switches)) ||
  161. false === in_array($h, $_names)) {
  162. continue;
  163. }
  164. $solutions[] = $h;
  165. }
  166. if (empty($solutions)) {
  167. continue;
  168. }
  169. $this->_pipette[] = ['__ambiguous', [
  170. 'solutions' => $solutions,
  171. 'value' => $value,
  172. 'option' => $name
  173. ]];
  174. continue;
  175. }
  176. $option = $options[$names[$name]];
  177. $argument = $option[self::OPTION_HAS_ARG];
  178. if (self::NO_ARGUMENT === $argument) {
  179. if (!is_bool($value)) {
  180. $parser->transferSwitchToInput($name, $value);
  181. }
  182. } elseif (self::REQUIRED_ARGUMENT === $argument && !is_string($value)) {
  183. throw new Exception(
  184. 'The argument %s requires a value (it is not a switch).',
  185. 0,
  186. $name
  187. );
  188. }
  189. $this->_pipette[] = [$option[self::OPTION_VAL], $value];
  190. }
  191. $this->_pipette[null] = null;
  192. reset($this->_pipette);
  193. return;
  194. }
  195. /**
  196. * Get option from the pipette.
  197. *
  198. * @param string &$optionValue Place a variable that will receive the
  199. * value of the current option.
  200. * @param string $short Short options to scan (in a single
  201. * string). If $short = null, all short
  202. * options will be selected.
  203. * @return mixed
  204. */
  205. public function getOption(&$optionValue, $short = null)
  206. {
  207. static $first = true;
  208. $optionValue = null;
  209. if (true === $this->isPipetteEmpty() && true === $first) {
  210. $first = false;
  211. return false;
  212. }
  213. $k = key($this->_pipette);
  214. $c = current($this->_pipette);
  215. $key = $c[0];
  216. $value = $c[1];
  217. if (null == $k && null === $c) {
  218. reset($this->_pipette);
  219. $first = true;
  220. return false;
  221. }
  222. $allow = [];
  223. if (null === $short) {
  224. foreach ($this->_options as $option) {
  225. $allow[] = $option[self::OPTION_VAL];
  226. }
  227. } else {
  228. $allow = str_split($short);
  229. }
  230. if (!in_array($key, $allow) && '__ambiguous' != $key) {
  231. return false;
  232. }
  233. $optionValue = $value;
  234. $return = $key;
  235. next($this->_pipette);
  236. return $return;
  237. }
  238. /**
  239. * Check if the pipette is empty.
  240. *
  241. * @return bool
  242. */
  243. public function isPipetteEmpty()
  244. {
  245. return count($this->_pipette) == 1;
  246. }
  247. /**
  248. * Resolve option ambiguity. Please see the special pipette entry
  249. * “ambiguous” in the self::__construct method.
  250. * For a smarter resolving, you could use the console kit (please, see
  251. * Hoa\Console\Dispatcher\Kit).
  252. *
  253. * @param array $solutions Solutions.
  254. * @return void
  255. * @throws \Hoa\Console\Exception
  256. */
  257. public function resolveOptionAmbiguity(array $solutions)
  258. {
  259. if (!isset($solutions['solutions']) ||
  260. !isset($solutions['value']) ||
  261. !isset($solutions['option'])) {
  262. throw new Exception(
  263. 'Cannot resolve option ambiguity because the given solution ' .
  264. 'seems to be corruped.',
  265. 1
  266. );
  267. }
  268. $choices = $solutions['solutions'];
  269. if (1 > count($choices)) {
  270. throw new Exception(
  271. 'Cannot resolve ambiguity, fix your typo in the option %s :-).',
  272. 2,
  273. $solutions['option']
  274. );
  275. }
  276. $theSolution = $choices[0];
  277. foreach ($this->_options as $option) {
  278. if ($theSolution == $option[self::OPTION_NAME] ||
  279. $theSolution == $option[self::OPTION_VAL]) {
  280. $argument = $option[self::OPTION_HAS_ARG];
  281. $value = $solutions['value'];
  282. if (self::NO_ARGUMENT === $argument) {
  283. if (!is_bool($value)) {
  284. $this->_parser->transferSwitchToInput($theSolution, $value);
  285. }
  286. } elseif (self::REQUIRED_ARGUMENT === $argument &&
  287. !is_string($value)) {
  288. throw new Exception(
  289. 'The argument %s requires a value (it is not a switch).',
  290. 3,
  291. $theSolution
  292. );
  293. }
  294. unset($this->_pipette[null]);
  295. $this->_pipette[] = [$option[self::OPTION_VAL], $value];
  296. $this->_pipette[null] = null;
  297. return;
  298. }
  299. }
  300. return;
  301. }
  302. }