/vendor/sebastian/cli-parser/src/Parser.php

https://gitlab.com/madwanz64/laravel · PHP · 204 lines · 135 code · 40 blank · 29 comment · 34 complexity · d2400fc3cc1671c8a59d8024326591e8 MD5 · raw file

  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of sebastian/cli-parser.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\CliParser;
  11. use function array_map;
  12. use function array_merge;
  13. use function array_shift;
  14. use function array_slice;
  15. use function assert;
  16. use function count;
  17. use function current;
  18. use function explode;
  19. use function is_array;
  20. use function is_int;
  21. use function is_string;
  22. use function key;
  23. use function next;
  24. use function preg_replace;
  25. use function reset;
  26. use function sort;
  27. use function strlen;
  28. use function strpos;
  29. use function strstr;
  30. use function substr;
  31. final class Parser
  32. {
  33. /**
  34. * @psalm-param list<string> $argv
  35. * @psalm-param list<string> $longOptions
  36. *
  37. * @throws AmbiguousOptionException
  38. * @throws RequiredOptionArgumentMissingException
  39. * @throws OptionDoesNotAllowArgumentException
  40. * @throws UnknownOptionException
  41. */
  42. public function parse(array $argv, string $shortOptions, array $longOptions = null): array
  43. {
  44. if (empty($argv)) {
  45. return [[], []];
  46. }
  47. $options = [];
  48. $nonOptions = [];
  49. if ($longOptions) {
  50. sort($longOptions);
  51. }
  52. if (isset($argv[0][0]) && $argv[0][0] !== '-') {
  53. array_shift($argv);
  54. }
  55. reset($argv);
  56. $argv = array_map('trim', $argv);
  57. while (false !== $arg = current($argv)) {
  58. $i = key($argv);
  59. assert(is_int($i));
  60. next($argv);
  61. if ($arg === '') {
  62. continue;
  63. }
  64. if ($arg === '--') {
  65. $nonOptions = array_merge($nonOptions, array_slice($argv, $i + 1));
  66. break;
  67. }
  68. if ($arg[0] !== '-' || (strlen($arg) > 1 && $arg[1] === '-' && !$longOptions)) {
  69. $nonOptions[] = $arg;
  70. continue;
  71. }
  72. if (strlen($arg) > 1 && $arg[1] === '-' && is_array($longOptions)) {
  73. $this->parseLongOption(
  74. substr($arg, 2),
  75. $longOptions,
  76. $options,
  77. $argv
  78. );
  79. } else {
  80. $this->parseShortOption(
  81. substr($arg, 1),
  82. $shortOptions,
  83. $options,
  84. $argv
  85. );
  86. }
  87. }
  88. return [$options, $nonOptions];
  89. }
  90. /**
  91. * @throws RequiredOptionArgumentMissingException
  92. */
  93. private function parseShortOption(string $arg, string $shortOptions, array &$opts, array &$args): void
  94. {
  95. $argLength = strlen($arg);
  96. for ($i = 0; $i < $argLength; $i++) {
  97. $option = $arg[$i];
  98. $optionArgument = null;
  99. if ($arg[$i] === ':' || ($spec = strstr($shortOptions, $option)) === false) {
  100. throw new UnknownOptionException('-' . $option);
  101. }
  102. assert(is_string($spec));
  103. if (strlen($spec) > 1 && $spec[1] === ':') {
  104. if ($i + 1 < $argLength) {
  105. $opts[] = [$option, substr($arg, $i + 1)];
  106. break;
  107. }
  108. if (!(strlen($spec) > 2 && $spec[2] === ':')) {
  109. $optionArgument = current($args);
  110. if (!$optionArgument) {
  111. throw new RequiredOptionArgumentMissingException('-' . $option);
  112. }
  113. assert(is_string($optionArgument));
  114. next($args);
  115. }
  116. }
  117. $opts[] = [$option, $optionArgument];
  118. }
  119. }
  120. /**
  121. * @psalm-param list<string> $longOptions
  122. *
  123. * @throws AmbiguousOptionException
  124. * @throws RequiredOptionArgumentMissingException
  125. * @throws OptionDoesNotAllowArgumentException
  126. * @throws UnknownOptionException
  127. */
  128. private function parseLongOption(string $arg, array $longOptions, array &$opts, array &$args): void
  129. {
  130. $count = count($longOptions);
  131. $list = explode('=', $arg);
  132. $option = $list[0];
  133. $optionArgument = null;
  134. if (count($list) > 1) {
  135. $optionArgument = $list[1];
  136. }
  137. $optionLength = strlen($option);
  138. foreach ($longOptions as $i => $longOption) {
  139. $opt_start = substr($longOption, 0, $optionLength);
  140. if ($opt_start !== $option) {
  141. continue;
  142. }
  143. $opt_rest = substr($longOption, $optionLength);
  144. if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && strpos($longOptions[$i + 1], $option) === 0) {
  145. throw new AmbiguousOptionException('--' . $option);
  146. }
  147. if (substr($longOption, -1) === '=') {
  148. /* @noinspection StrlenInEmptyStringCheckContextInspection */
  149. if (substr($longOption, -2) !== '==' && !strlen((string) $optionArgument)) {
  150. if (false === $optionArgument = current($args)) {
  151. throw new RequiredOptionArgumentMissingException('--' . $option);
  152. }
  153. next($args);
  154. }
  155. } elseif ($optionArgument) {
  156. throw new OptionDoesNotAllowArgumentException('--' . $option);
  157. }
  158. $fullOption = '--' . preg_replace('/={1,2}$/', '', $longOption);
  159. $opts[] = [$fullOption, $optionArgument];
  160. return;
  161. }
  162. throw new UnknownOptionException('--' . $option);
  163. }
  164. }