diff --git a/src/Symfony/Component/Console/Command/HelpCommand.php b/src/Symfony/Component/Console/Command/HelpCommand.php index 9bcd7764d7b55..169767ebbd385 100644 --- a/src/Symfony/Component/Console/Command/HelpCommand.php +++ b/src/Symfony/Component/Console/Command/HelpCommand.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Console\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionInterface; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Descriptor\ApplicationDescription; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -22,7 +26,7 @@ * * @author Fabien Potencier */ -class HelpCommand extends Command +class HelpCommand extends Command implements CompletionInterface { private $command; @@ -80,4 +84,19 @@ protected function execute(InputInterface $input, OutputInterface $output) 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()); + } + } } diff --git a/src/Symfony/Component/Console/Command/ListCommand.php b/src/Symfony/Component/Console/Command/ListCommand.php index a192285128490..ee49be7bbe119 100644 --- a/src/Symfony/Component/Console/Command/ListCommand.php +++ b/src/Symfony/Component/Console/Command/ListCommand.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Console\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionInterface; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Descriptor\ApplicationDescription; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -22,7 +26,7 @@ * * @author Fabien Potencier */ -class ListCommand extends Command +class ListCommand extends Command implements CompletionInterface { /** * {@inheritdoc} @@ -74,4 +78,19 @@ protected function execute(InputInterface $input, OutputInterface $output) 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()); + } + } } diff --git a/src/Symfony/Component/Console/Helper/DescriptorHelper.php b/src/Symfony/Component/Console/Helper/DescriptorHelper.php index f2ad9db7a194c..af85e9c0a72e6 100644 --- a/src/Symfony/Component/Console/Helper/DescriptorHelper.php +++ b/src/Symfony/Component/Console/Helper/DescriptorHelper.php @@ -84,4 +84,9 @@ public function getName() { return 'descriptor'; } + + public function getFormats(): array + { + return array_keys($this->descriptors); + } } diff --git a/src/Symfony/Component/Console/Tester/CommandCompletionTester.php b/src/Symfony/Component/Console/Tester/CommandCompletionTester.php new file mode 100644 index 0000000000000..f274b9f28c3af --- /dev/null +++ b/src/Symfony/Component/Console/Tester/CommandCompletionTester.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionInterface; +use Symfony\Component\Console\Completion\CompletionSuggestions; + +/** + * Eases the testing of command completion. + * + * @author Jérôme Tamarelle + */ +class CommandCompletionTester +{ + private $command; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Create completion suggestions from input tokens. + */ + public function complete(array $input): array + { + if (!$this->command instanceof CompletionInterface) { + throw new \LogicException(sprintf('Command "%s" must implement "%s" to support completion.', \get_class($this->command), CompletionInput::class)); + } + + $currentIndex = \count($input); + if ('' === end($input)) { + array_pop($input); + } + array_unshift($input, $this->command->getName()); + + $completionInput = CompletionInput::fromTokens($input, $currentIndex); + $completionInput->bind($this->command->getDefinition()); + $suggestions = new CompletionSuggestions(); + + $this->command->complete($completionInput, $suggestions); + + $options = []; + foreach ($suggestions->getOptionSuggestions() as $option) { + $options[] = '--'.$option->getName(); + } + + return array_merge($options, $suggestions->getValueSuggestions()); + } +} diff --git a/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php b/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php index 4576170a980c6..bf0ab972061bc 100644 --- a/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/HelpCommandTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; class HelpCommandTest extends TestCase @@ -68,4 +69,35 @@ public function testExecuteForApplicationCommandWithXmlOption() $this->assertStringContainsString('list [--raw] [--format FORMAT] [--short] [--] [<namespace>]', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); $this->assertStringContainsString('getDisplay(), '->execute() returns an XML help text if --format=xml is passed'); } + + /** + * @dataProvider provideCompletionSuggestions + */ + public function testComplete(array $input, array $expectedSuggestions) + { + require_once realpath(__DIR__.'/../Fixtures/FooCommand.php'); + $application = new Application(); + $application->add(new \FooCommand()); + $tester = new CommandCompletionTester($application->get('help')); + $suggestions = $tester->complete($input, 2); + $this->assertSame($expectedSuggestions, $suggestions); + } + + public function provideCompletionSuggestions() + { + yield 'option --format' => [ + ['--format', ''], + ['txt', 'xml', 'json', 'md'], + ]; + + yield 'nothing' => [ + [''], + [], + ]; + + yield 'command_name' => [ + ['f'], + ['completion', 'help', 'list', 'foo:bar'], + ]; + } } diff --git a/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php b/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php index b9b9046edc454..0f78cf7ee3202 100644 --- a/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/ListCommandTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Tester\CommandCompletionTester; use Symfony\Component\Console\Tester\CommandTester; class ListCommandTest extends TestCase @@ -112,4 +113,35 @@ public function testExecuteListsCommandsOrderRaw() $this->assertEquals($output, trim($commandTester->getDisplay(true))); } + + /** + * @dataProvider provideCompletionSuggestions + */ + public function testComplete(array $input, array $expectedSuggestions) + { + require_once realpath(__DIR__.'/../Fixtures/FooCommand.php'); + $application = new Application(); + $application->add(new \FooCommand()); + $tester = new CommandCompletionTester($application->get('list')); + $suggestions = $tester->complete($input, 2); + $this->assertSame($expectedSuggestions, $suggestions); + } + + public function provideCompletionSuggestions() + { + yield 'option --format' => [ + ['--format', ''], + ['txt', 'xml', 'json', 'md'], + ]; + + yield 'namespace' => [ + [''], + ['_global', 'foo'], + ]; + + yield 'namespace started' => [ + ['f'], + ['_global', 'foo'], + ]; + } } diff --git a/src/Symfony/Component/Console/Tests/Helper/DescriptorHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/DescriptorHelperTest.php new file mode 100644 index 0000000000000..97bae77c51fcc --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Helper/DescriptorHelperTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Helper\DescriptorHelper; + +class DescriptorHelperTest extends TestCase +{ + public function testGetFormats() + { + $helper = new DescriptorHelper(); + $expectedFormats = [ + 'txt', + 'xml', + 'json', + 'md', + ]; + $this->assertSame($expectedFormats, $helper->getFormats()); + } +}