diff options
Diffstat (limited to 'vendor/symfony/console/Completion/CompletionInput.php')
-rw-r--r-- | vendor/symfony/console/Completion/CompletionInput.php | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/vendor/symfony/console/Completion/CompletionInput.php b/vendor/symfony/console/Completion/CompletionInput.php new file mode 100644 index 0000000..79c2f65 --- /dev/null +++ b/vendor/symfony/console/Completion/CompletionInput.php | |||
@@ -0,0 +1,248 @@ | |||
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 | |||
12 | namespace Symfony\Component\Console\Completion; | ||
13 | |||
14 | use Symfony\Component\Console\Exception\RuntimeException; | ||
15 | use Symfony\Component\Console\Input\ArgvInput; | ||
16 | use Symfony\Component\Console\Input\InputDefinition; | ||
17 | use Symfony\Component\Console\Input\InputOption; | ||
18 | |||
19 | /** | ||
20 | * An input specialized for shell completion. | ||
21 | * | ||
22 | * This input allows unfinished option names or values and exposes what kind of | ||
23 | * completion is expected. | ||
24 | * | ||
25 | * @author Wouter de Jong <wouter@wouterj.nl> | ||
26 | */ | ||
27 | final class CompletionInput extends ArgvInput | ||
28 | { | ||
29 | public const TYPE_ARGUMENT_VALUE = 'argument_value'; | ||
30 | public const TYPE_OPTION_VALUE = 'option_value'; | ||
31 | public const TYPE_OPTION_NAME = 'option_name'; | ||
32 | public const TYPE_NONE = 'none'; | ||
33 | |||
34 | private array $tokens; | ||
35 | private int $currentIndex; | ||
36 | private string $completionType; | ||
37 | private ?string $completionName = null; | ||
38 | private string $completionValue = ''; | ||
39 | |||
40 | /** | ||
41 | * Converts a terminal string into tokens. | ||
42 | * | ||
43 | * This is required for shell completions without COMP_WORDS support. | ||
44 | */ | ||
45 | public static function fromString(string $inputStr, int $currentIndex): self | ||
46 | { | ||
47 | preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens); | ||
48 | |||
49 | return self::fromTokens($tokens[0], $currentIndex); | ||
50 | } | ||
51 | |||
52 | /** | ||
53 | * Create an input based on an COMP_WORDS token list. | ||
54 | * | ||
55 | * @param string[] $tokens the set of split tokens (e.g. COMP_WORDS or argv) | ||
56 | * @param int $currentIndex the index of the cursor (e.g. COMP_CWORD) | ||
57 | */ | ||
58 | public static function fromTokens(array $tokens, int $currentIndex): self | ||
59 | { | ||
60 | $input = new self($tokens); | ||
61 | $input->tokens = $tokens; | ||
62 | $input->currentIndex = $currentIndex; | ||
63 | |||
64 | return $input; | ||
65 | } | ||
66 | |||
67 | public function bind(InputDefinition $definition): void | ||
68 | { | ||
69 | parent::bind($definition); | ||
70 | |||
71 | $relevantToken = $this->getRelevantToken(); | ||
72 | if ('-' === $relevantToken[0]) { | ||
73 | // the current token is an input option: complete either option name or option value | ||
74 | [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', '']; | ||
75 | |||
76 | $option = $this->getOptionFromToken($optionToken); | ||
77 | if (null === $option && !$this->isCursorFree()) { | ||
78 | $this->completionType = self::TYPE_OPTION_NAME; | ||
79 | $this->completionValue = $relevantToken; | ||
80 | |||
81 | return; | ||
82 | } | ||
83 | |||
84 | if ($option?->acceptValue()) { | ||
85 | $this->completionType = self::TYPE_OPTION_VALUE; | ||
86 | $this->completionName = $option->getName(); | ||
87 | $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : ''); | ||
88 | |||
89 | return; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | $previousToken = $this->tokens[$this->currentIndex - 1]; | ||
94 | if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) { | ||
95 | // check if previous option accepted a value | ||
96 | $previousOption = $this->getOptionFromToken($previousToken); | ||
97 | if ($previousOption?->acceptValue()) { | ||
98 | $this->completionType = self::TYPE_OPTION_VALUE; | ||
99 | $this->completionName = $previousOption->getName(); | ||
100 | $this->completionValue = $relevantToken; | ||
101 | |||
102 | return; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | // complete argument value | ||
107 | $this->completionType = self::TYPE_ARGUMENT_VALUE; | ||
108 | |||
109 | foreach ($this->definition->getArguments() as $argumentName => $argument) { | ||
110 | if (!isset($this->arguments[$argumentName])) { | ||
111 | break; | ||
112 | } | ||
113 | |||
114 | $argumentValue = $this->arguments[$argumentName]; | ||
115 | $this->completionName = $argumentName; | ||
116 | if (\is_array($argumentValue)) { | ||
117 | $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null; | ||
118 | } else { | ||
119 | $this->completionValue = $argumentValue; | ||
120 | } | ||
121 | } | ||
122 | |||
123 | if ($this->currentIndex >= \count($this->tokens)) { | ||
124 | if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { | ||
125 | $this->completionName = $argumentName; | ||
126 | $this->completionValue = ''; | ||
127 | } else { | ||
128 | // we've reached the end | ||
129 | $this->completionType = self::TYPE_NONE; | ||
130 | $this->completionName = null; | ||
131 | $this->completionValue = ''; | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | |||
136 | /** | ||
137 | * Returns the type of completion required. | ||
138 | * | ||
139 | * TYPE_ARGUMENT_VALUE when completing the value of an input argument | ||
140 | * TYPE_OPTION_VALUE when completing the value of an input option | ||
141 | * TYPE_OPTION_NAME when completing the name of an input option | ||
142 | * TYPE_NONE when nothing should be completed | ||
143 | * | ||
144 | * TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component. | ||
145 | * | ||
146 | * @return self::TYPE_* | ||
147 | */ | ||
148 | public function getCompletionType(): string | ||
149 | { | ||
150 | return $this->completionType; | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * The name of the input option or argument when completing a value. | ||
155 | * | ||
156 | * @return string|null returns null when completing an option name | ||
157 | */ | ||
158 | public function getCompletionName(): ?string | ||
159 | { | ||
160 | return $this->completionName; | ||
161 | } | ||
162 | |||
163 | /** | ||
164 | * The value already typed by the user (or empty string). | ||
165 | */ | ||
166 | public function getCompletionValue(): string | ||
167 | { | ||
168 | return $this->completionValue; | ||
169 | } | ||
170 | |||
171 | public function mustSuggestOptionValuesFor(string $optionName): bool | ||
172 | { | ||
173 | return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName(); | ||
174 | } | ||
175 | |||
176 | public function mustSuggestArgumentValuesFor(string $argumentName): bool | ||
177 | { | ||
178 | return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName(); | ||
179 | } | ||
180 | |||
181 | protected function parseToken(string $token, bool $parseOptions): bool | ||
182 | { | ||
183 | try { | ||
184 | return parent::parseToken($token, $parseOptions); | ||
185 | } catch (RuntimeException) { | ||
186 | // suppress errors, completed input is almost never valid | ||
187 | } | ||
188 | |||
189 | return $parseOptions; | ||
190 | } | ||
191 | |||
192 | private function getOptionFromToken(string $optionToken): ?InputOption | ||
193 | { | ||
194 | $optionName = ltrim($optionToken, '-'); | ||
195 | if (!$optionName) { | ||
196 | return null; | ||
197 | } | ||
198 | |||
199 | if ('-' === ($optionToken[1] ?? ' ')) { | ||
200 | // long option name | ||
201 | return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null; | ||
202 | } | ||
203 | |||
204 | // short option name | ||
205 | return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null; | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * The token of the cursor, or the last token if the cursor is at the end of the input. | ||
210 | */ | ||
211 | private function getRelevantToken(): string | ||
212 | { | ||
213 | return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex]; | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * Whether the cursor is "free" (i.e. at the end of the input preceded by a space). | ||
218 | */ | ||
219 | private function isCursorFree(): bool | ||
220 | { | ||
221 | $nrOfTokens = \count($this->tokens); | ||
222 | if ($this->currentIndex > $nrOfTokens) { | ||
223 | throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.'); | ||
224 | } | ||
225 | |||
226 | return $this->currentIndex >= $nrOfTokens; | ||
227 | } | ||
228 | |||
229 | public function __toString() | ||
230 | { | ||
231 | $str = ''; | ||
232 | foreach ($this->tokens as $i => $token) { | ||
233 | $str .= $token; | ||
234 | |||
235 | if ($this->currentIndex === $i) { | ||
236 | $str .= '|'; | ||
237 | } | ||
238 | |||
239 | $str .= ' '; | ||
240 | } | ||
241 | |||
242 | if ($this->currentIndex > $i) { | ||
243 | $str .= '|'; | ||
244 | } | ||
245 | |||
246 | return rtrim($str); | ||
247 | } | ||
248 | } | ||