summaryrefslogtreecommitdiff
path: root/vendor/symfony/console/Input/ArgvInput.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/console/Input/ArgvInput.php')
-rw-r--r--vendor/symfony/console/Input/ArgvInput.php396
1 files changed, 396 insertions, 0 deletions
diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php
new file mode 100644
index 0000000..95703ba
--- /dev/null
+++ b/vendor/symfony/console/Input/ArgvInput.php
@@ -0,0 +1,396 @@
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Input;
13
14use Symfony\Component\Console\Exception\RuntimeException;
15
16/**
17 * ArgvInput represents an input coming from the CLI arguments.
18 *
19 * Usage:
20 *
21 * $input = new ArgvInput();
22 *
23 * By default, the `$_SERVER['argv']` array is used for the input values.
24 *
25 * This can be overridden by explicitly passing the input values in the constructor:
26 *
27 * $input = new ArgvInput($_SERVER['argv']);
28 *
29 * If you pass it yourself, don't forget that the first element of the array
30 * is the name of the running application.
31 *
32 * When passing an argument to the constructor, be sure that it respects
33 * the same rules as the argv one. It's almost always better to use the
34 * `StringInput` when you want to provide your own input.
35 *
36 * @author Fabien Potencier <fabien@symfony.com>
37 *
38 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
39 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
40 */
41class ArgvInput extends Input
42{
43 /** @var list<string> */
44 private array $tokens;
45 private array $parsed;
46
47 /** @param list<string>|null $argv */
48 public function __construct(?array $argv = null, ?InputDefinition $definition = null)
49 {
50 $argv ??= $_SERVER['argv'] ?? [];
51
52 // strip the application name
53 array_shift($argv);
54
55 $this->tokens = $argv;
56
57 parent::__construct($definition);
58 }
59
60 /** @param list<string> $tokens */
61 protected function setTokens(array $tokens): void
62 {
63 $this->tokens = $tokens;
64 }
65
66 protected function parse(): void
67 {
68 $parseOptions = true;
69 $this->parsed = $this->tokens;
70 while (null !== $token = array_shift($this->parsed)) {
71 $parseOptions = $this->parseToken($token, $parseOptions);
72 }
73 }
74
75 protected function parseToken(string $token, bool $parseOptions): bool
76 {
77 if ($parseOptions && '' == $token) {
78 $this->parseArgument($token);
79 } elseif ($parseOptions && '--' == $token) {
80 return false;
81 } elseif ($parseOptions && str_starts_with($token, '--')) {
82 $this->parseLongOption($token);
83 } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
84 $this->parseShortOption($token);
85 } else {
86 $this->parseArgument($token);
87 }
88
89 return $parseOptions;
90 }
91
92 /**
93 * Parses a short option.
94 */
95 private function parseShortOption(string $token): void
96 {
97 $name = substr($token, 1);
98
99 if (\strlen($name) > 1) {
100 if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
101 // an option with a value (with no space)
102 $this->addShortOption($name[0], substr($name, 1));
103 } else {
104 $this->parseShortOptionSet($name);
105 }
106 } else {
107 $this->addShortOption($name, null);
108 }
109 }
110
111 /**
112 * Parses a short option set.
113 *
114 * @throws RuntimeException When option given doesn't exist
115 */
116 private function parseShortOptionSet(string $name): void
117 {
118 $len = \strlen($name);
119 for ($i = 0; $i < $len; ++$i) {
120 if (!$this->definition->hasShortcut($name[$i])) {
121 $encoding = mb_detect_encoding($name, null, true);
122 throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
123 }
124
125 $option = $this->definition->getOptionForShortcut($name[$i]);
126 if ($option->acceptValue()) {
127 $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
128
129 break;
130 } else {
131 $this->addLongOption($option->getName(), null);
132 }
133 }
134 }
135
136 /**
137 * Parses a long option.
138 */
139 private function parseLongOption(string $token): void
140 {
141 $name = substr($token, 2);
142
143 if (false !== $pos = strpos($name, '=')) {
144 if ('' === $value = substr($name, $pos + 1)) {
145 array_unshift($this->parsed, $value);
146 }
147 $this->addLongOption(substr($name, 0, $pos), $value);
148 } else {
149 $this->addLongOption($name, null);
150 }
151 }
152
153 /**
154 * Parses an argument.
155 *
156 * @throws RuntimeException When too many arguments are given
157 */
158 private function parseArgument(string $token): void
159 {
160 $c = \count($this->arguments);
161
162 // if input is expecting another argument, add it
163 if ($this->definition->hasArgument($c)) {
164 $arg = $this->definition->getArgument($c);
165 $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
166
167 // if last argument isArray(), append token to last argument
168 } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
169 $arg = $this->definition->getArgument($c - 1);
170 $this->arguments[$arg->getName()][] = $token;
171
172 // unexpected argument
173 } else {
174 $all = $this->definition->getArguments();
175 $symfonyCommandName = null;
176 if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) {
177 $symfonyCommandName = $this->arguments['command'] ?? null;
178 unset($all[$key]);
179 }
180
181 if (\count($all)) {
182 if ($symfonyCommandName) {
183 $message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all)));
184 } else {
185 $message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)));
186 }
187 } elseif ($symfonyCommandName) {
188 $message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token);
189 } else {
190 $message = sprintf('No arguments expected, got "%s".', $token);
191 }
192
193 throw new RuntimeException($message);
194 }
195 }
196
197 /**
198 * Adds a short option value.
199 *
200 * @throws RuntimeException When option given doesn't exist
201 */
202 private function addShortOption(string $shortcut, mixed $value): void
203 {
204 if (!$this->definition->hasShortcut($shortcut)) {
205 throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
206 }
207
208 $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
209 }
210
211 /**
212 * Adds a long option value.
213 *
214 * @throws RuntimeException When option given doesn't exist
215 */
216 private function addLongOption(string $name, mixed $value): void
217 {
218 if (!$this->definition->hasOption($name)) {
219 if (!$this->definition->hasNegation($name)) {
220 throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
221 }
222
223 $optionName = $this->definition->negationToName($name);
224 if (null !== $value) {
225 throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
226 }
227 $this->options[$optionName] = false;
228
229 return;
230 }
231
232 $option = $this->definition->getOption($name);
233
234 if (null !== $value && !$option->acceptValue()) {
235 throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
236 }
237
238 if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
239 // if option accepts an optional or mandatory argument
240 // let's see if there is one provided
241 $next = array_shift($this->parsed);
242 if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
243 $value = $next;
244 } else {
245 array_unshift($this->parsed, $next);
246 }
247 }
248
249 if (null === $value) {
250 if ($option->isValueRequired()) {
251 throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
252 }
253
254 if (!$option->isArray() && !$option->isValueOptional()) {
255 $value = true;
256 }
257 }
258
259 if ($option->isArray()) {
260 $this->options[$name][] = $value;
261 } else {
262 $this->options[$name] = $value;
263 }
264 }
265
266 public function getFirstArgument(): ?string
267 {
268 $isOption = false;
269 foreach ($this->tokens as $i => $token) {
270 if ($token && '-' === $token[0]) {
271 if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) {
272 continue;
273 }
274
275 // If it's a long option, consider that everything after "--" is the option name.
276 // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
277 $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
278 if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
279 // noop
280 } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
281 $isOption = true;
282 }
283
284 continue;
285 }
286
287 if ($isOption) {
288 $isOption = false;
289 continue;
290 }
291
292 return $token;
293 }
294
295 return null;
296 }
297
298 public function hasParameterOption(string|array $values, bool $onlyParams = false): bool
299 {
300 $values = (array) $values;
301
302 foreach ($this->tokens as $token) {
303 if ($onlyParams && '--' === $token) {
304 return false;
305 }
306 foreach ($values as $value) {
307 // Options with values:
308 // For long options, test for '--option=' at beginning
309 // For short options, test for '-o' at beginning
310 $leading = str_starts_with($value, '--') ? $value.'=' : $value;
311 if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) {
312 return true;
313 }
314 }
315 }
316
317 return false;
318 }
319
320 public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed
321 {
322 $values = (array) $values;
323 $tokens = $this->tokens;
324
325 while (0 < \count($tokens)) {
326 $token = array_shift($tokens);
327 if ($onlyParams && '--' === $token) {
328 return $default;
329 }
330
331 foreach ($values as $value) {
332 if ($token === $value) {
333 return array_shift($tokens);
334 }
335 // Options with values:
336 // For long options, test for '--option=' at beginning
337 // For short options, test for '-o' at beginning
338 $leading = str_starts_with($value, '--') ? $value.'=' : $value;
339 if ('' !== $leading && str_starts_with($token, $leading)) {
340 return substr($token, \strlen($leading));
341 }
342 }
343 }
344
345 return $default;
346 }
347
348 /**
349 * Returns un-parsed and not validated tokens.
350 *
351 * @param bool $strip Whether to return the raw parameters (false) or the values after the command name (true)
352 *
353 * @return list<string>
354 */
355 public function getRawTokens(bool $strip = false): array
356 {
357 if (!$strip) {
358 return $this->tokens;
359 }
360
361 $parameters = [];
362 $keep = false;
363 foreach ($this->tokens as $value) {
364 if (!$keep && $value === $this->getFirstArgument()) {
365 $keep = true;
366
367 continue;
368 }
369 if ($keep) {
370 $parameters[] = $value;
371 }
372 }
373
374 return $parameters;
375 }
376
377 /**
378 * Returns a stringified representation of the args passed to the command.
379 */
380 public function __toString(): string
381 {
382 $tokens = array_map(function ($token) {
383 if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
384 return $match[1].$this->escapeToken($match[2]);
385 }
386
387 if ($token && '-' !== $token[0]) {
388 return $this->escapeToken($token);
389 }
390
391 return $token;
392 }, $this->tokens);
393
394 return implode(' ', $tokens);
395 }
396}