Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 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 $tokens;
35
    private $currentIndex;
36
    private $completionType;
37
    private $completionName = null;
38
    private $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          $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
     * @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component
145
     */
146
    public function getCompletionType(): string
147
    {
148
        return $this->completionType;
149
    }
150
 
151
    /**
152
     * The name of the input option or argument when completing a value.
153
     *
154
     * @return string|null returns null when completing an option name
155
     */
156
    public function getCompletionName(): ?string
157
    {
158
        return $this->completionName;
159
    }
160
 
161
    /**
162
     * The value already typed by the user (or empty string).
163
     */
164
    public function getCompletionValue(): string
165
    {
166
        return $this->completionValue;
167
    }
168
 
169
    public function mustSuggestOptionValuesFor(string $optionName): bool
170
    {
171
        return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();
172
    }
173
 
174
    public function mustSuggestArgumentValuesFor(string $argumentName): bool
175
    {
176
        return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();
177
    }
178
 
179
    protected function parseToken(string $token, bool $parseOptions): bool
180
    {
181
        try {
182
            return parent::parseToken($token, $parseOptions);
183
        } catch (RuntimeException) {
184
            // suppress errors, completed input is almost never valid
185
        }
186
 
187
        return $parseOptions;
188
    }
189
 
190
    private function getOptionFromToken(string $optionToken): ?InputOption
191
    {
192
        $optionName = ltrim($optionToken, '-');
193
        if (!$optionName) {
194
            return null;
195
        }
196
 
197
        if ('-' === ($optionToken[1] ?? ' ')) {
198
            // long option name
199
            return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;
200
        }
201
 
202
        // short option name
203
        return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;
204
    }
205
 
206
    /**
207
     * The token of the cursor, or the last token if the cursor is at the end of the input.
208
     */
209
    private function getRelevantToken(): string
210
    {
211
        return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];
212
    }
213
 
214
    /**
215
     * Whether the cursor is "free" (i.e. at the end of the input preceded by a space).
216
     */
217
    private function isCursorFree(): bool
218
    {
219
        $nrOfTokens = \count($this->tokens);
220
        if ($this->currentIndex > $nrOfTokens) {
221
            throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');
222
        }
223
 
224
        return $this->currentIndex >= $nrOfTokens;
225
    }
226
 
227
    public function __toString()
228
    {
229
        $str = '';
230
        foreach ($this->tokens as $i => $token) {
231
            $str .= $token;
232
 
233
            if ($this->currentIndex === $i) {
234
                $str .= '|';
235
            }
236
 
237
            $str .= ' ';
238
        }
239
 
240
        if ($this->currentIndex > $i) {
241
            $str .= '|';
242
        }
243
 
244
        return rtrim($str);
245
    }
246
}