8000 [Console] Add completion values to input definition · symfony/symfony@5d70c9b · GitHub
[go: up one dir, main page]

Skip to content

Commit 5d70c9b

Browse files
committed
[Console] Add completion values to input definition
1 parent 44db49f commit 5d70c9b

12 files changed

+170
-62
lines changed

UPGRADE-6.1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Console
1111
-------
1212

1313
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
14+
* Add argument `$suggestedValues` to `Command::addArgument` and `Command::addOption`
15+
* Add argument `$suggestedValues` to `InputArgument` and `InputOption` constructors
1416

1517
Serializer
1618
----------

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add method `__toString()` to `InputInterface`
88
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
9+
* Add suggested values for arguments and options in input definition, for input completion
910

1011
6.0
1112
---

src/Symfony/Component/Console/Command/Command.php

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ public function run(InputInterface $input, OutputInterface $output): int
319319
*/
320320
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
321321
{
322+
$definition = $this->getDefinition();
323+
if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) {
324+
$suggestions->suggestValues($definition->getOption($input->getCompletionName())->getSuggestedValues($input));
325+
} elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) {
326+
$suggestions->suggestValues($definition->getArgument($input->getCompletionName())->getSuggestedValues($input));
327+
}
322328
}
323329

324330
/**
@@ -427,17 +433,22 @@ public function getNativeDefinition(): InputDefinition
427433
/**
428434
* Adds an argument.
429435
*
430-
* @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
431-
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
436+
* @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
437+
* @param $default The default value (for InputArgument::OPTIONAL mode only)
438+
* @param array|\Closure(CompletionInput):array $suggestedValues The values used for input completion
432439
*
433440
* @throws InvalidArgumentException When argument mode is not valid
434441
*
435442
* @return $this
436443
*/
437-
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null): static
444+
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, /*array|\Closure $suggestedValues = null*/): static
438445
{
439-
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
440-
$this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default));
446+
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
447+
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
448+
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
449+
}
450+
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
451+
$this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
441452

442453
return $this;
443454
}
@@ -448,15 +459,20 @@ public function addArgument(string $name, int $mode = null, string $description
448459
* @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
449460
* @param $mode The option mode: One of the InputOption::VALUE_* constants
450461
* @param $default The default value (must be null for InputOption::VALUE_NONE)
462+
* @param array|\Closure(CompletionInput):array $suggestedValues The values used for input completion
451463
*
452464
* @throws InvalidArgumentException If option mode is invalid or incompatible
453465
*
454466
* @return $this
455467
*/
456-
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null): static
468+
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, /*array|\Closure $suggestedValues = []*/): static
457469
{
458-
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
459-
$this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
470+
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
471+
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
472+
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
473+
}
474+
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
475+
$this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
460476

461477
return $this;
462478
}

src/Symfony/Component/Console/Command/DumpCompletionCommand.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
namespace Symfony\Component\Console\Command;
1313

1414
use Symfony\Component\Console\Attribute\AsCommand;
15-
use Symfony\Component\Console\Completion\CompletionInput;
16-
use Symfony\Component\Console\Completion\CompletionSuggestions;
1715
use Symfony\Component\Console\Input\InputArgument;
1816
use Symfony\Component\Console\Input\InputInterface;
1917
use Symfony\Component\Console\Input\InputOption;
@@ -39,12 +37,7 @@ final class DumpCompletionCommand extends Command
3937
*/
4038
protected static $defaultDescription = 'Dump the shell completion script';
4139

42-
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
43-
{
44-
if ($input->mustSuggestArgumentValuesFor('shell')) {
45-
$suggestions->suggestValues($this->getSupportedShells());
46-
}
47-
}
40+
private array $supportedShells;
4841

4942
protected function configure()
5043
{
@@ -82,7 +75,7 @@ protected function configure()
8275
<info>eval "$(${fullCommand} completion bash)"</>
8376
EOH
8477
)
85-
->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')
78+
->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', null, fn () => $this->getSupportedShells())
8679
->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
8780
;
8881
}
@@ -135,7 +128,7 @@ private function tailDebugLog(string $commandName, OutputInterface $output): voi
135128
*/
136129
private function getSupportedShells(): array
137130
{
138-
return array_map(function ($f) {
131+
return $this->supportedShells ??= array_map(function ($f) {
139132
return pathinfo($f, \PATHINFO_EXTENSION);
140133
}, glob(__DIR__.'/../Resources/completion.*'));
141134
}

src/Symfony/Component/Console/Command/HelpCommand.php

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
namespace Symfony\Component\Console\Command;
1313

14-
use Symfony\Component\Console\Completion\CompletionInput;
15-
use Symfony\Component\Console\Completion\CompletionSuggestions;
1614
use Symfony\Component\Console\Descriptor\ApplicationDescription;
1715
use Symfony\Component\Console\Helper\DescriptorHelper;
1816
use Symfony\Component\Console\Input\InputArgument;
@@ -39,8 +37,12 @@ protected function configure()
3937
$this
4038
->setName('help')
4139
->setDefinition([
42-
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
43-
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
40+
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', function () {
41+
return array_keys((new ApplicationDescription($this->getApplication()))->getCommands());
42+
}),
43+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
44+
return (new DescriptorHelper())->getFormats();
45+
}),
4446
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
4547
])
4648
->setDescription('Display help for a command')
@@ -81,19 +83,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8183

8284
return 0;
8385
}
84-
85-
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
86-
{
87-
if ($input->mustSuggestArgumentValuesFor('command_name')) {
88-
$descriptor = new ApplicationDescription($this->getApplication());
89-
$suggestions->suggestValues(array_keys($descriptor->getCommands()));
90-
91-
return;
92-
}
93-
94-
if ($input->mustSuggestOptionValuesFor('format')) {
95-
$helper = new DescriptorHelper();
96-
$suggestions->suggestValues($helper->getFormats());
97-
}
98-
}
9986
}

src/Symfony/Component/Console/Command/LazyCommand.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,28 @@ public function getNativeDefinition(): InputDefinition
108108
return $this->getCommand()->getNativeDefinition();
109109
}
110110

111-
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null): static
111+
/**
112+
* {@inheritdoc}
113+
*
114+
* @param array|\Closure(CompletionInput):array $suggestedValues The values used for input completion
115+
*/
116+
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, /*array|\Closure $suggestedValues = []*/): static
112117
{
113-
$this->getCommand()->addArgument($name, $mode, $description, $default);
118+
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
119+
$this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);
114120

115121
return $this;
116122
}
117123

118-
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null): static
124+
/**
125+
* {@inheritdoc}
126+
*
127+
* @param array|\Closure(CompletionInput):array $suggestedValues The values used for input completion
128+
*/
129+
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, /*array|\Closure $suggestedValues = []*/): static
119130
{
120-
$this->getCommand()->addOption($name, $shortcut, $mode, $description, $default);
131+
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
132+
$this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
121133

122134
return $this;
123135
}

src/Symfony/Component/Console/Command/ListCommand.php

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
namespace Symfony\Component\Console\Command;
1313

14-
use Symfony\Component\Console\Completion\CompletionInput;
15-
use Symfony\Component\Console\Completion\CompletionSuggestions;
1614
use Symfony\Component\Console\Descriptor\ApplicationDescription;
1715
use Symfony\Component\Console\Helper\DescriptorHelper;
1816
use Symfony\Component\Console\Input\InputArgument;
@@ -35,9 +33,13 @@ protected function configure()
3533
$this
3634
->setName('list')
3735
->setDefinition([
38-
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
36+
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, function () {
37+
return array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces());
38+
}),
3939
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
40-
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
40+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', function () {
41+
return (new DescriptorHelper())->getFormats();
42+
}),
4143
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
4244
])
4345
->setDescription('List commands')
@@ -77,19 +79,4 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7779

7880
return 0;
7981
}
80-
81-
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
82-
{
83-
if ($input->mustSuggestArgumentValuesFor('namespace')) {
84-
$descriptor = new ApplicationDescription($this->getApplication());
85-
$suggestions->suggestValues(array_keys($descriptor->getNamespaces()));
86-
87-
return;
88-
}
89-
90-
if ($input->mustSuggestOptionValuesFor('format')) {
91-
$helper = new DescriptorHelper();
92-
$suggestions->suggestValues($helper->getFormats());
93-
}
94-
}
9582
}

src/Symfony/Component/Console/Input/InputArgument.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Console\Input;
1313

14+
use Symfony\Component\Console\Completion\CompletionInput;
1415
use Symfony\Component\Console\Exception\InvalidArgumentException;
1516
use Symfony\Component\Console\Exception\LogicException;
1617

@@ -28,17 +29,19 @@ class InputArgument
2829
private string $name;
2930
private int $mode;
3031
private string|int|bool|array|null|float $default;
32+
private array|\Closure $suggestedValues;
3133
private string $description;
3234

3335
/**
3436
* @param string $name The argument name
3537
* @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
3638
* @param string $description A description text
3739
* @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only)
40+
* @param array|\Closure(CompletionInput):array $suggestedValues The values used for input completion
3841
*
3942
* @throws InvalidArgumentException When argument mode is not valid
4043
*/
41-
public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null)
44+
public function __construct(string $name, int $mode = null, string $description = '', string|bool|int|float|array $default = null, \Closure|array $suggestedValues = [])
4245
{
4346
if (null === $mode) {
4447
$mode = self::OPTIONAL;
@@ -49,6 +52,7 @@ public function __construct(string $name, int $mode = null, string $description
4952
$this->name = $name;
5053
$this->mode = $mode;
5154
$this->description = $description;
55+
$this->suggestedValues = $suggestedValues;
5256

5357
$this->setDefault($default);
5458
}
@@ -111,6 +115,19 @@ public function getDefault(): string|bool|int|float|array|null
111115
return $this->default;
112116
}
113117

118+
/**
119+
* Returns suggested values for input completion.
120+
*/
121+
public function getSuggestedValues(CompletionInput $input): array
122+
{
123+
$values = $this->suggestedValues;
124+
if ($values instanceof \Closure && !\is_array($values = $values($input))) {
125+
throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
126+
}
127+
128+
return $values;
129+
}
130+
114131
/**
115132
* Returns the description text.
116133
*/

src/Symfony/Component/Console/Input/InputOption.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Console\Input;
1313

14+
use Symfony\Component\Console\Completion\CompletionInput;
1415
use Symfony\Component\Console\Exception\InvalidArgumentException;
1516
use Symfony\Component\Console\Exception\LogicException;
1617

@@ -50,16 +51,18 @@ class InputOption
5051
private string|array|null $shortcut;
5152
private int $mode;
5253
private string|int|bool|array|null|float $default;
54+
private array|\Closure $suggestedValues;
5355
private string $description;
5456

5557
/**
5658
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
5759
* @param int|null $mode The option mode: One of the VALUE_* constants
5860
* @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE)
61+
* @param array|\Closure(CompletionInput):array $suggestedValues The values used for input completion
5962
*
6063
* @throws InvalidArgumentException If option mode is invalid or incompatible
6164
*/
62-
public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null)
65+
public function __construct(string $name, string|array $shortcut = null, int $mode = null, string $description = '', string|bool|int|float|array $default = null, array|\Closure $suggestedValues = [])
6366
{
6467
if (str_starts_with($name, '--')) {
6568
$name = substr($name, 2);
@@ -96,7 +99,11 @@ public function __construct(string $name, string|array $shortcut = null, int $mo
9699
$this->shortcut = $shortcut;
97100
$this->mode = $mode;
98101
$this->description = $description;
102+
$this->suggestedValues = $suggestedValues;
99103

104+
if ($suggestedValues && !$this->acceptValue()) {
105+
throw new LogicException('Cannot set suggested values if the option does not accept a value.');
106+
}
100107
if ($this->isArray() && !$this->acceptValue()) {
101108
throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
102109
}
@@ -193,6 +200,19 @@ public function getDefault(): string|bool|int|float|array|null
193200
return $this->default;
194201
}
195202

203+
/**
204+
* Returns suggested values for input completion.
205+
*/
206+
public function getSuggestedValues(CompletionInput $input): array
207+
{
208+
$values = $this->suggestedValues;
209+
if ($values instanceof \Closure && !\is_array($values = $values($input))) {
210+
throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
211+
}
212+
213+
return $values;
214+
}
215+
196216
/**
197217
* Returns the description text.
198218
*/

0 commit comments

Comments
 (0)
0