Mercurial > hg > cmmr2012-drupal-site
comparison vendor/psy/psysh/src/Input/ShellInput.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of Psy Shell. | |
5 * | |
6 * (c) 2012-2018 Justin Hileman | |
7 * | |
8 * For the full copyright and license information, please view the LICENSE | |
9 * file that was distributed with this source code. | |
10 */ | |
11 | |
12 namespace Psy\Input; | |
13 | |
14 use Symfony\Component\Console\Input\InputDefinition; | |
15 use Symfony\Component\Console\Input\StringInput; | |
16 | |
17 /** | |
18 * A StringInput subclass specialized for code arguments. | |
19 */ | |
20 class ShellInput extends StringInput | |
21 { | |
22 private $hasCodeArgument = false; | |
23 | |
24 /** | |
25 * Unlike the parent implementation's tokens, this contains an array of | |
26 * token/rest pairs, so that code arguments can be handled while parsing. | |
27 */ | |
28 private $tokenPairs; | |
29 private $parsed; | |
30 | |
31 /** | |
32 * Constructor. | |
33 * | |
34 * @param string $input An array of parameters from the CLI (in the argv format) | |
35 */ | |
36 public function __construct($input) | |
37 { | |
38 parent::__construct($input); | |
39 | |
40 $this->tokenPairs = $this->tokenize($input); | |
41 } | |
42 | |
43 /** | |
44 * {@inheritdoc} | |
45 * | |
46 * @throws \InvalidArgumentException if $definition has CodeArgument before the final argument position | |
47 */ | |
48 public function bind(InputDefinition $definition) | |
49 { | |
50 $hasCodeArgument = false; | |
51 | |
52 if ($definition->getArgumentCount() > 0) { | |
53 $args = $definition->getArguments(); | |
54 $lastArg = array_pop($args); | |
55 foreach ($args as $arg) { | |
56 if ($arg instanceof CodeArgument) { | |
57 $msg = sprintf('Unexpected CodeArgument before the final position: %s', $arg->getName()); | |
58 throw new \InvalidArgumentException($msg); | |
59 } | |
60 } | |
61 | |
62 if ($lastArg instanceof CodeArgument) { | |
63 $hasCodeArgument = true; | |
64 } | |
65 } | |
66 | |
67 $this->hasCodeArgument = $hasCodeArgument; | |
68 | |
69 return parent::bind($definition); | |
70 } | |
71 | |
72 /** | |
73 * Tokenizes a string. | |
74 * | |
75 * The version of this on StringInput is good, but doesn't handle code | |
76 * arguments if they're at all complicated. This does :) | |
77 * | |
78 * @param string $input The input to tokenize | |
79 * | |
80 * @return array An array of token/rest pairs | |
81 * | |
82 * @throws \InvalidArgumentException When unable to parse input (should never happen) | |
83 */ | |
84 private function tokenize($input) | |
85 { | |
86 $tokens = []; | |
87 $length = strlen($input); | |
88 $cursor = 0; | |
89 while ($cursor < $length) { | |
90 if (preg_match('/\s+/A', $input, $match, null, $cursor)) { | |
91 } elseif (preg_match('/([^="\'\s]+?)(=?)(' . StringInput::REGEX_QUOTED_STRING . '+)/A', $input, $match, null, $cursor)) { | |
92 $tokens[] = [ | |
93 $match[1] . $match[2] . stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, strlen($match[3]) - 2))), | |
94 stripcslashes(substr($input, $cursor)), | |
95 ]; | |
96 } elseif (preg_match('/' . StringInput::REGEX_QUOTED_STRING . '/A', $input, $match, null, $cursor)) { | |
97 $tokens[] = [ | |
98 stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)), | |
99 stripcslashes(substr($input, $cursor)), | |
100 ]; | |
101 } elseif (preg_match('/' . StringInput::REGEX_STRING . '/A', $input, $match, null, $cursor)) { | |
102 $tokens[] = [ | |
103 stripcslashes($match[1]), | |
104 stripcslashes(substr($input, $cursor)), | |
105 ]; | |
106 } else { | |
107 // should never happen | |
108 // @codeCoverageIgnoreStart | |
109 throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); | |
110 // @codeCoverageIgnoreEnd | |
111 } | |
112 | |
113 $cursor += strlen($match[0]); | |
114 } | |
115 | |
116 return $tokens; | |
117 } | |
118 | |
119 /** | |
120 * Same as parent, but with some bonus handling for code arguments. | |
121 */ | |
122 protected function parse() | |
123 { | |
124 $parseOptions = true; | |
125 $this->parsed = $this->tokenPairs; | |
126 while (null !== $tokenPair = array_shift($this->parsed)) { | |
127 // token is what you'd expect. rest is the remainder of the input | |
128 // string, including token, and will be used if this is a code arg. | |
129 list($token, $rest) = $tokenPair; | |
130 | |
131 if ($parseOptions && '' === $token) { | |
132 $this->parseShellArgument($token, $rest); | |
133 } elseif ($parseOptions && '--' === $token) { | |
134 $parseOptions = false; | |
135 } elseif ($parseOptions && 0 === strpos($token, '--')) { | |
136 $this->parseLongOption($token); | |
137 } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { | |
138 $this->parseShortOption($token); | |
139 } else { | |
140 $this->parseShellArgument($token, $rest); | |
141 } | |
142 } | |
143 } | |
144 | |
145 /** | |
146 * Parses an argument, with bonus handling for code arguments. | |
147 * | |
148 * @param string $token The current token | |
149 * @param string $rest The remaining unparsed input, including the current token | |
150 * | |
151 * @throws \RuntimeException When too many arguments are given | |
152 */ | |
153 private function parseShellArgument($token, $rest) | |
154 { | |
155 $c = count($this->arguments); | |
156 | |
157 // if input is expecting another argument, add it | |
158 if ($this->definition->hasArgument($c)) { | |
159 $arg = $this->definition->getArgument($c); | |
160 | |
161 if ($arg instanceof CodeArgument) { | |
162 // When we find a code argument, we're done parsing. Add the | |
163 // remaining input to the current argument and call it a day. | |
164 $this->parsed = []; | |
165 $this->arguments[$arg->getName()] = $rest; | |
166 } else { | |
167 $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; | |
168 } | |
169 | |
170 return; | |
171 } | |
172 | |
173 // (copypasta) | |
174 // | |
175 // @codeCoverageIgnoreStart | |
176 | |
177 // if last argument isArray(), append token to last argument | |
178 if ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { | |
179 $arg = $this->definition->getArgument($c - 1); | |
180 $this->arguments[$arg->getName()][] = $token; | |
181 | |
182 return; | |
183 } | |
184 | |
185 // unexpected argument | |
186 $all = $this->definition->getArguments(); | |
187 if (count($all)) { | |
188 throw new \RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); | |
189 } | |
190 | |
191 throw new \RuntimeException(sprintf('No arguments expected, got "%s".', $token)); | |
192 // @codeCoverageIgnoreEnd | |
193 } | |
194 | |
195 // Everything below this is copypasta from ArgvInput private methods | |
196 // @codeCoverageIgnoreStart | |
197 | |
198 /** | |
199 * Parses a short option. | |
200 * | |
201 * @param string $token The current token | |
202 */ | |
203 private function parseShortOption($token) | |
204 { | |
205 $name = substr($token, 1); | |
206 | |
207 if (strlen($name) > 1) { | |
208 if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { | |
209 // an option with a value (with no space) | |
210 $this->addShortOption($name[0], substr($name, 1)); | |
211 } else { | |
212 $this->parseShortOptionSet($name); | |
213 } | |
214 } else { | |
215 $this->addShortOption($name, null); | |
216 } | |
217 } | |
218 | |
219 /** | |
220 * Parses a short option set. | |
221 * | |
222 * @param string $name The current token | |
223 * | |
224 * @throws \RuntimeException When option given doesn't exist | |
225 */ | |
226 private function parseShortOptionSet($name) | |
227 { | |
228 $len = strlen($name); | |
229 for ($i = 0; $i < $len; $i++) { | |
230 if (!$this->definition->hasShortcut($name[$i])) { | |
231 throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); | |
232 } | |
233 | |
234 $option = $this->definition->getOptionForShortcut($name[$i]); | |
235 if ($option->acceptValue()) { | |
236 $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); | |
237 | |
238 break; | |
239 } else { | |
240 $this->addLongOption($option->getName(), null); | |
241 } | |
242 } | |
243 } | |
244 | |
245 /** | |
246 * Parses a long option. | |
247 * | |
248 * @param string $token The current token | |
249 */ | |
250 private function parseLongOption($token) | |
251 { | |
252 $name = substr($token, 2); | |
253 | |
254 if (false !== $pos = strpos($name, '=')) { | |
255 if (0 === strlen($value = substr($name, $pos + 1))) { | |
256 // if no value after "=" then substr() returns "" since php7 only, false before | |
257 // see http://php.net/manual/fr/migration70.incompatible.php#119151 | |
258 if (PHP_VERSION_ID < 70000 && false === $value) { | |
259 $value = ''; | |
260 } | |
261 array_unshift($this->parsed, [$value, null]); | |
262 } | |
263 $this->addLongOption(substr($name, 0, $pos), $value); | |
264 } else { | |
265 $this->addLongOption($name, null); | |
266 } | |
267 } | |
268 | |
269 /** | |
270 * Adds a short option value. | |
271 * | |
272 * @param string $shortcut The short option key | |
273 * @param mixed $value The value for the option | |
274 * | |
275 * @throws \RuntimeException When option given doesn't exist | |
276 */ | |
277 private function addShortOption($shortcut, $value) | |
278 { | |
279 if (!$this->definition->hasShortcut($shortcut)) { | |
280 throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); | |
281 } | |
282 | |
283 $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); | |
284 } | |
285 | |
286 /** | |
287 * Adds a long option value. | |
288 * | |
289 * @param string $name The long option key | |
290 * @param mixed $value The value for the option | |
291 * | |
292 * @throws \RuntimeException When option given doesn't exist | |
293 */ | |
294 private function addLongOption($name, $value) | |
295 { | |
296 if (!$this->definition->hasOption($name)) { | |
297 throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); | |
298 } | |
299 | |
300 $option = $this->definition->getOption($name); | |
301 | |
302 if (null !== $value && !$option->acceptValue()) { | |
303 throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); | |
304 } | |
305 | |
306 if (in_array($value, ['', null], true) && $option->acceptValue() && count($this->parsed)) { | |
307 // if option accepts an optional or mandatory argument | |
308 // let's see if there is one provided | |
309 $next = array_shift($this->parsed); | |
310 $nextToken = $next[0]; | |
311 if ((isset($nextToken[0]) && '-' !== $nextToken[0]) || in_array($nextToken, ['', null], true)) { | |
312 $value = $nextToken; | |
313 } else { | |
314 array_unshift($this->parsed, $next); | |
315 } | |
316 } | |
317 | |
318 if (null === $value) { | |
319 if ($option->isValueRequired()) { | |
320 throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); | |
321 } | |
322 | |
323 if (!$option->isArray() && !$option->isValueOptional()) { | |
324 $value = true; | |
325 } | |
326 } | |
327 | |
328 if ($option->isArray()) { | |
329 $this->options[$name][] = $value; | |
330 } else { | |
331 $this->options[$name] = $value; | |
332 } | |
333 } | |
334 | |
335 // @codeCoverageIgnoreEnd | |
336 } |