Skip to content

Commit

Permalink
[Console] Add completion values to input definition
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Jan 7, 2022
1 parent 098ff62 commit 8650876
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 43 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
---

* Add method `__toString()` to `InputInterface`
* Add completion values for arguments and options in input definition

6.0
---
Expand Down
51 changes: 51 additions & 0 deletions src/Symfony/Component/Console/Command/Command.php
Expand Up @@ -303,6 +303,27 @@ public function run(InputInterface $input, OutputInterface $output): int
*/
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
$definition = $this->getDefinition();
$values = null;
if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) {
$values = $definition->getOption($input->getCompletionName())->getValues();
} elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) {
$values = $definition->getArgument($input->getCompletionName())->getValues();
}
if (null === $values) {
return;
}
if ($values instanceof \Closure) {
$values = $values($input);
if (null === $values) {
return;
}
if (!is_iterable($values)) {
throw new LogicException(sprintf('Callable for %s %s must return an iterable or null. Got "%s".', $input->getCompletionType(), $input->getCompletionName(), get_debug_type($values)));
}
}

$suggestions->suggestValues(is_array($values) ? $values : iterator_to_array($values));
}

/**
Expand Down Expand Up @@ -426,6 +447,21 @@ public function addArgument(string $name, int $mode = null, string $description
return $this;
}

/**
* Set values for input completion.
*
* @throws InvalidArgumentException If the argument is not defined
*
* @return $this
*/
public function setArgumentValues(string $name, \Closure|iterable|null $values): static
{
$this->definition->getArgument($name)->setValues($values);
$this->fullDefinition?->getArgument($name)->setValues($values);

return $this;
}

/**
* Adds an option.
*
Expand All @@ -445,6 +481,21 @@ public function addOption(string $name, string|array $shortcut = null, int $mode
return $this;
}

/**
* Set values for input completion.
*
* @throws InvalidArgumentException If the option is not defined
*
* @return $this
*/
public function setOptionValues(string $name, \Closure|iterable|null $values): static
{
$this->definition->getOption($name)->setValues($values);
$this->fullDefinition?->getOption($name)->setValues($values);

return $this;
}

/**
* Sets the name of the command.
*
Expand Down
10 changes: 3 additions & 7 deletions src/Symfony/Component/Console/Command/DumpCompletionCommand.php
Expand Up @@ -30,12 +30,7 @@ final class DumpCompletionCommand extends Command
protected static $defaultName = 'completion';
protected static $defaultDescription = 'Dump the shell completion script';

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('shell')) {
$suggestions->suggestValues($this->getSupportedShells());
}
}
private array $supportedShells;

protected function configure()
{
Expand Down Expand Up @@ -74,6 +69,7 @@ protected function configure()
EOH
)
->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given')
->setArgumentValues('shell', $this->getSupportedShells())
->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
;
}
Expand Down Expand Up @@ -126,7 +122,7 @@ private function tailDebugLog(string $commandName, OutputInterface $output): voi
*/
private function getSupportedShells(): array
{
return array_map(function ($f) {
return $this->supportedShells ?? $this->supportedShells = array_map(function ($f) {
return pathinfo($f, \PATHINFO_EXTENSION);
}, glob(__DIR__.'/../Resources/completion.*'));
}
Expand Down
23 changes: 6 additions & 17 deletions src/Symfony/Component/Console/Command/HelpCommand.php
Expand Up @@ -39,8 +39,12 @@ protected function configure()
$this
->setName('help')
->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', function () {
return array_keys((new ApplicationDescription($this->getApplication()))->getCommands());
}),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
return (new DescriptorHelper())->getFormats();
}),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Display help for a command')
Expand Down Expand Up @@ -81,19 +85,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int

return 0;
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('command_name')) {
$descriptor = new ApplicationDescription($this->getApplication());
$suggestions->suggestValues(array_keys($descriptor->getCommands()));

return;
}

if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
}
}
}
23 changes: 6 additions & 17 deletions src/Symfony/Component/Console/Command/ListCommand.php
Expand Up @@ -35,9 +35,13 @@ protected function configure()
$this
->setName('list')
->setDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () {
return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces());
}),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
return (new DescriptorHelper())->getFormats();
}),
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
])
->setDescription('List commands')
Expand Down Expand Up @@ -77,19 +81,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int

return 0;
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('namespace')) {
$descriptor = new ApplicationDescription($this->getApplication());
$suggestions->suggestValues(array_keys($descriptor->getNamespaces()));

return;
}

if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
}
}
}
17 changes: 16 additions & 1 deletion src/Symfony/Component/Console/Input/InputArgument.php
Expand Up @@ -28,6 +28,7 @@ class InputArgument
private string $name;
private int $mode;
private string|int|bool|array|null|float $default;
private \Closure|iterable|null $values;
private string $description;

/**
Expand All @@ -38,7 +39,7 @@ class InputArgument
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null)
public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null, \Closure|iterable|null $suggestions = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
Expand All @@ -51,6 +52,7 @@ public function __construct(string $name, int $mode = null, string $description
$this->description = $description;

$this->setDefault($default);
$this->setValues($suggestions);
}

/**
Expand Down Expand Up @@ -111,6 +113,19 @@ public function getDefault(): string|bool|int|float|array|null
return $this->default;
}

public function setValues(Callable|iterable|null $values = null)
{
$this->values = $values;
}

/**
* Returns suggestion values for input completion.
*/
public function getValues(): \Closure|iterable|null
{
return $this->values;
}

/**
* Returns the description text.
*/
Expand Down
21 changes: 20 additions & 1 deletion src/Symfony/Component/Console/Input/InputOption.php
Expand Up @@ -50,6 +50,7 @@ class InputOption
private string|array|null $shortcut;
private int $mode;
private string|int|bool|array|null|float $default;
private \Closure|iterable|null $values;
private string $description;

/**
Expand All @@ -59,7 +60,7 @@ class InputOption
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null)
public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null, \Closure|iterable|null $values = null)
{
if (str_starts_with($name, '--')) {
$name = substr($name, 2);
Expand Down Expand Up @@ -105,6 +106,7 @@ public function __construct(string $name, string|array $shortcut = null, int $mo
}

$this->setDefault($default);
$this->setValues($values);
}

/**
Expand Down Expand Up @@ -193,6 +195,23 @@ public function getDefault(): string|bool|int|float|array|null
return $this->default;
}

public function setValues(\Closure|iterable|null $values = null)
{
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $values) {
throw new LogicException('Cannot set a completion when using InputOption::VALUE_NONE mode.');
}

$this->values = is_callable($values) ? \Closure::fromCallable($values) : $values;
}

/**
* Returns suggestions for input completion.
*/
public function getValues(): Callable|iterable|null
{
return $this->values;
}

/**
* Returns the description text.
*/
Expand Down

0 comments on commit 8650876

Please sign in to comment.