Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/** This file is part of the Symfony package.** (c) Fabien Potencier <fabien@symfony.com>** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace Symfony\Component\Console\Completion;use Symfony\Component\Console\Exception\RuntimeException;use Symfony\Component\Console\Input\ArgvInput;use Symfony\Component\Console\Input\InputDefinition;use Symfony\Component\Console\Input\InputOption;/*** An input specialized for shell completion.** This input allows unfinished option names or values and exposes what kind of* completion is expected.** @author Wouter de Jong <wouter@wouterj.nl>*/final class CompletionInput extends ArgvInput{public const TYPE_ARGUMENT_VALUE = 'argument_value';public const TYPE_OPTION_VALUE = 'option_value';public const TYPE_OPTION_NAME = 'option_name';public const TYPE_NONE = 'none';private $tokens;private $currentIndex;private $completionType;private $completionName = null;private $completionValue = '';/*** Converts a terminal string into tokens.** This is required for shell completions without COMP_WORDS support.*/public static function fromString(string $inputStr, int $currentIndex): self{preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens);return self::fromTokens($tokens[0], $currentIndex);}/*** Create an input based on an COMP_WORDS token list.** @param string[] $tokens the set of split tokens (e.g. COMP_WORDS or argv)* @param $currentIndex the index of the cursor (e.g. COMP_CWORD)*/public static function fromTokens(array $tokens, int $currentIndex): self{$input = new self($tokens);$input->tokens = $tokens;$input->currentIndex = $currentIndex;return $input;}public function bind(InputDefinition $definition): void{parent::bind($definition);$relevantToken = $this->getRelevantToken();if ('-' === $relevantToken[0]) {// the current token is an input option: complete either option name or option value[$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', ''];$option = $this->getOptionFromToken($optionToken);if (null === $option && !$this->isCursorFree()) {$this->completionType = self::TYPE_OPTION_NAME;$this->completionValue = $relevantToken;return;}if ($option?->acceptValue()) {$this->completionType = self::TYPE_OPTION_VALUE;$this->completionName = $option->getName();$this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');return;}}$previousToken = $this->tokens[$this->currentIndex - 1];if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {// check if previous option accepted a value$previousOption = $this->getOptionFromToken($previousToken);if ($previousOption?->acceptValue()) {$this->completionType = self::TYPE_OPTION_VALUE;$this->completionName = $previousOption->getName();$this->completionValue = $relevantToken;return;}}// complete argument value$this->completionType = self::TYPE_ARGUMENT_VALUE;foreach ($this->definition->getArguments() as $argumentName => $argument) {if (!isset($this->arguments[$argumentName])) {break;}$argumentValue = $this->arguments[$argumentName];$this->completionName = $argumentName;if (\is_array($argumentValue)) {$this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;} else {$this->completionValue = $argumentValue;}}if ($this->currentIndex >= \count($this->tokens)) {if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) {$this->completionName = $argumentName;$this->completionValue = '';} else {// we've reached the end$this->completionType = self::TYPE_NONE;$this->completionName = null;$this->completionValue = '';}}}/*** Returns the type of completion required.** TYPE_ARGUMENT_VALUE when completing the value of an input argument* TYPE_OPTION_VALUE when completing the value of an input option* TYPE_OPTION_NAME when completing the name of an input option* TYPE_NONE when nothing should be completed** @return string One of self::TYPE_* constants. TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component*/public function getCompletionType(): string{return $this->completionType;}/*** The name of the input option or argument when completing a value.** @return string|null returns null when completing an option name*/public function getCompletionName(): ?string{return $this->completionName;}/*** The value already typed by the user (or empty string).*/public function getCompletionValue(): string{return $this->completionValue;}public function mustSuggestOptionValuesFor(string $optionName): bool{return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();}public function mustSuggestArgumentValuesFor(string $argumentName): bool{return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();}protected function parseToken(string $token, bool $parseOptions): bool{try {return parent::parseToken($token, $parseOptions);} catch (RuntimeException) {// suppress errors, completed input is almost never valid}return $parseOptions;}private function getOptionFromToken(string $optionToken): ?InputOption{$optionName = ltrim($optionToken, '-');if (!$optionName) {return null;}if ('-' === ($optionToken[1] ?? ' ')) {// long option namereturn $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;}// short option namereturn $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;}/*** The token of the cursor, or the last token if the cursor is at the end of the input.*/private function getRelevantToken(): string{return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];}/*** Whether the cursor is "free" (i.e. at the end of the input preceded by a space).*/private function isCursorFree(): bool{$nrOfTokens = \count($this->tokens);if ($this->currentIndex > $nrOfTokens) {throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');}return $this->currentIndex >= $nrOfTokens;}public function __toString(){$str = '';foreach ($this->tokens as $i => $token) {$str .= $token;if ($this->currentIndex === $i) {$str .= '|';}$str .= ' ';}if ($this->currentIndex > $i) {$str .= '|';}return rtrim($str);}}