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