From 091027ac8c361e98d94f3c889d39a17fffaaa3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 16 Mar 2022 19:29:12 +0100 Subject: [PATCH] Implements Option/ArgumentInput::complete() for openess --- .../Component/Console/Command/Command.php | 4 +- .../Component/Console/Input/InputArgument.php | 19 ++++++--- .../Component/Console/Input/InputOption.php | 30 ++++++++----- .../Console/Tests/Command/CommandTest.php | 5 ++- .../Console/Tests/Input/InputArgumentTest.php | 30 +++++++++---- .../Console/Tests/Input/InputOptionTest.php | 42 ++++++++++++------- 6 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/Symfony/Component/Console/Command/Command.php b/src/Symfony/Component/Console/Command/Command.php index b2aa834631517..2428d5a639615 100644 --- a/src/Symfony/Component/Console/Command/Command.php +++ b/src/Symfony/Component/Console/Command/Command.php @@ -321,9 +321,9 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti { $definition = $this->getDefinition(); if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) { - $suggestions->suggestValues($definition->getOption($input->getCompletionName())->getSuggestedValues($input)); + $definition->getOption($input->getCompletionName())->complete($input, $suggestions); } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) { - $suggestions->suggestValues($definition->getArgument($input->getCompletionName())->getSuggestedValues($input)); + $definition->getArgument($input->getCompletionName())->complete($input, $suggestions); } } diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php index 957ff197522d1..940eaa79448f3 100644 --- a/src/Symfony/Component/Console/Input/InputArgument.php +++ b/src/Symfony/Component/Console/Input/InputArgument.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\Input; use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; @@ -115,17 +116,25 @@ public function getDefault(): string|bool|int|float|array|null return $this->default; } + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; + } + /** - * Returns suggested values for input completion. + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() */ - public function getSuggestedValues(CompletionInput $input): array + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { $values = $this->suggestedValues; if ($values instanceof \Closure && !\is_array($values = $values($input))) { - throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); + throw new LogicException(sprintf('Closure for argument "%s" must return an array or null. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); } - - return $values; } /** diff --git a/src/Symfony/Component/Console/Input/InputOption.php b/src/Symfony/Component/Console/Input/InputOption.php index 54346b688ebbe..9f6d9ff5e100e 100644 --- a/src/Symfony/Component/Console/Input/InputOption.php +++ b/src/Symfony/Component/Console/Input/InputOption.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Console\Input; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\LogicException; @@ -201,24 +203,32 @@ public function getDefault(): string|bool|int|float|array|null } /** - * Returns suggested values for input completion. + * Returns the description text. */ - public function getSuggestedValues(CompletionInput $input): array + public function getDescription(): string { - $values = $this->suggestedValues; - if ($values instanceof \Closure && !\is_array($values = $values($input))) { - throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values))); - } + return $this->description; + } - return $values; + public function hasCompletion(): bool + { + return [] !== $this->suggestedValues; } /** - * Returns the description text. + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() */ - public function getDescription(): string + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { - return $this->description; + $values = $this->suggestedValues; + if ($values instanceof \Closure && !\is_array($values = $values($input))) { + throw new LogicException(sprintf('Closure for option "%s" must return an array or null. Got "%s".', $this->name, get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } } /** diff --git a/src/Symfony/Component/Console/Tests/Command/CommandTest.php b/src/Symfony/Component/Console/Tests/Command/CommandTest.php index 17658f0be6808..bc0200ed85f32 100644 --- a/src/Symfony/Component/Console/Tests/Command/CommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/CommandTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Input\InputArgument; @@ -96,7 +97,7 @@ public function testAddArgumentFull() $argument = $command->getDefinition()->getArgument('foo'); $this->assertSame('Description', $argument->getDescription()); $this->assertSame('default', $argument->getDefault()); - $this->assertSame(['a', 'b'], $argument->getSuggestedValues(new CompletionInput())); + $this->assertTrue($argument->hasCompletion()); } public function testAddOption() @@ -115,7 +116,7 @@ public function testAddOptionFull() $this->assertSame('f', $option->getShortcut()); $this->assertSame('Description', $option->getDescription()); $this->assertSame('default', $option->getDefault()); - $this->assertSame(['a', 'b'], $option->getSuggestedValues(new CompletionInput())); + $this->assertTrue($option->hasCompletion()); } public function testSetHidden() diff --git a/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php b/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php index 93744abfb6921..68814ffb7205d 100644 --- a/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php +++ b/src/Symfony/Component/Console/Tests/Input/InputArgumentTest.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputArgument; @@ -98,22 +100,32 @@ public function testSetDefaultWithArrayArgument() $argument->setDefault('default'); } - public function testGetSuggestedValues() + public function testCompleteArray() { $values = ['foo', 'bar']; - $option = new InputArgument('foo', null, '', null, $values); - $this->assertSame($values, $option->getSuggestedValues(new CompletionInput())); + $argument = new InputArgument('foo', null, '', null, $values); + $this->assertTrue($argument->hasCompletion()); + $suggestions = new CompletionSuggestions(); + $argument->complete(new CompletionInput(), $suggestions); + $this->assertSame($values, array_map(fn (Suggestion $suggestion) => $suggestion->getValue(), $suggestions->getValueSuggestions())); + } - $option = new InputArgument('foo', null, '', null, fn (CompletionInput $input): array => $values); - $this->assertSame($values, $option->getSuggestedValues(new CompletionInput())); + public function testCompleteClosure() + { + $values = ['foo', 'bar']; + $argument = new InputArgument('foo', null, '', null, fn (CompletionInput $input): array => $values); + $this->assertTrue($argument->hasCompletion()); + $suggestions = new CompletionSuggestions(); + $argument->complete(new CompletionInput(), $suggestions); + $this->assertSame($values, array_map(fn (Suggestion $suggestion) => $suggestion->getValue(), $suggestions->getValueSuggestions())); } - public function testGetSuggestedValuesClosureReturnIncorrectType() + public function testCompleteClosureReturnIncorrectType() { $this->expectException(LogicException::class); - $this->expectExceptionMessage('Closure for argument "foo" must return an array. Got "string".'); + $this->expectExceptionMessage('Closure for argument "foo" must return an array or null. Got "string".'); - $option = new InputArgument('foo', InputArgument::OPTIONAL, '', null, fn (CompletionInput $input) => 'invalid'); - $option->getSuggestedValues(new CompletionInput()); + $argument = new InputArgument('foo', InputArgument::OPTIONAL, '', null, fn (CompletionInput $input) => 'invalid'); + $argument->complete(new CompletionInput(), new CompletionSuggestions()); } } diff --git a/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php b/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php index 3af6aaceecdcf..74901a6b768e2 100644 --- a/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php +++ b/src/Symfony/Component/Console/Tests/Input/InputOptionTest.php @@ -13,7 +13,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Suggestion; use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; class InputOptionTest extends TestCase @@ -197,31 +200,40 @@ public function testEquals() $this->assertFalse($option->equals($option2)); } - public function testGetSuggestedValues() + public function testSuggestedValuesErrorIfNoValue() { - $values = ['foo', 'bar']; - $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', null, $values); - $this->assertSame($values, $option->getSuggestedValues(new CompletionInput())); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot set suggested values if the option does not accept a value.'); - $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', null, fn (CompletionInput $input): array => $values); - $this->assertSame($values, $option->getSuggestedValues(new CompletionInput())); + new InputOption('foo', null, InputOption::VALUE_NONE, '', null, ['foo']); } - public function testGetSuggestedValuesClosureReturnIncorrectType() + public function testCompleteArray() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Closure for option "foo" must return an array. Got "string".'); + $values = ['foo', 'bar']; + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', null, $values); + $this->assertTrue($option->hasCompletion()); + $suggestions = new CompletionSuggestions(); + $option->complete(new CompletionInput(), $suggestions); + $this->assertSame($values, array_map(fn (Suggestion $suggestion) => $suggestion->getValue(), $suggestions->getValueSuggestions())); + } - $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', null, fn (CompletionInput $input) => 'invalid'); - $option->getSuggestedValues(new CompletionInput()); + public function testCompleteClosure() + { + $values = ['foo', 'bar']; + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', null, fn (CompletionInput $input): array => $values); + $this->assertTrue($option->hasCompletion()); + $suggestions = new CompletionSuggestions(); + $option->complete(new CompletionInput(), $suggestions); + $this->assertSame($values, array_map(fn (Suggestion $suggestion) => $suggestion->getValue(), $suggestions->getValueSuggestions())); } - public function testSuggestedValuesErrorIfNoValue() + public function testCompleteClosureReturnIncorrectType() { $this->expectException(LogicException::class); - $this->expectExceptionMessage('Cannot set suggested values if the option does not accept a value.'); + $this->expectExceptionMessage('Closure for option "foo" must return an array or null. Got "string".'); - $option = new InputOption('foo', null, InputOption::VALUE_NONE, '', null, ['foo']); - $option->getSuggestedValues(new CompletionInput()); + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', null, fn (CompletionInput $input) => 'invalid'); + $option->complete(new CompletionInput(), new CompletionSuggestions()); } }