From 829d5d1bf60b2efeb0887b7436873becc71a45eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 17 May 2022 22:50:08 +0200 Subject: [PATCH 1/5] Complete negatable options --- Completion/Output/BashCompletionOutput.php | 3 ++ Tests/Command/CompleteCommandTest.php | 4 +- .../Output/BashCompletionOutputTest.php | 33 ++++++++++++ .../Output/CompletionOutputTestCase.php | 51 +++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 Tests/Completion/Output/BashCompletionOutputTest.php create mode 100644 Tests/Completion/Output/CompletionOutputTestCase.php diff --git a/Completion/Output/BashCompletionOutput.php b/Completion/Output/BashCompletionOutput.php index 8d5ffa6b6..c6f76eb8f 100644 --- a/Completion/Output/BashCompletionOutput.php +++ b/Completion/Output/BashCompletionOutput.php @@ -24,6 +24,9 @@ public function write(CompletionSuggestions $suggestions, OutputInterface $outpu $values = $suggestions->getValueSuggestions(); foreach ($suggestions->getOptionSuggestions() as $option) { $values[] = '--'.$option->getName(); + if ($option->isNegatable()) { + $values[] = '--no-'.$option->getName(); + } } $output->writeln(implode("\n", $values)); } diff --git a/Tests/Command/CompleteCommandTest.php b/Tests/Command/CompleteCommandTest.php index f3ffe7b50..642d5c69a 100644 --- a/Tests/Command/CompleteCommandTest.php +++ b/Tests/Command/CompleteCommandTest.php @@ -119,9 +119,9 @@ public function testCompleteCommandInputDefinition(array $input, array $suggesti public function provideCompleteCommandInputDefinitionInputs() { - yield 'definition' => [['bin/console', 'hello', '-'], ['--help', '--quiet', '--verbose', '--version', '--ansi', '--no-interaction']]; + yield 'definition' => [['bin/console', 'hello', '-'], ['--help', '--quiet', '--verbose', '--version', '--ansi', '--no-ansi', '--no-interaction']]; yield 'custom' => [['bin/console', 'hello'], ['Fabien', 'Robin', 'Wouter']]; - yield 'definition-aliased' => [['bin/console', 'ahoy', '-'], ['--help', '--quiet', '--verbose', '--version', '--ansi', '--no-interaction']]; + yield 'definition-aliased' => [['bin/console', 'ahoy', '-'], ['--help', '--quiet', '--verbose', '--version', '--ansi', '--no-ansi', '--no-interaction']]; yield 'custom-aliased' => [['bin/console', 'ahoy'], ['Fabien', 'Robin', 'Wouter']]; } diff --git a/Tests/Completion/Output/BashCompletionOutputTest.php b/Tests/Completion/Output/BashCompletionOutputTest.php new file mode 100644 index 000000000..84ec56acc --- /dev/null +++ b/Tests/Completion/Output/BashCompletionOutputTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Completion\Output; + +use Symfony\Component\Console\Completion\Output\BashCompletionOutput; +use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; + +class BashCompletionOutputTest extends CompletionOutputTestCase +{ + public function getCompletionOutput(): CompletionOutputInterface + { + return new BashCompletionOutput(); + } + + public function getExpectedOptionsOutput(): string + { + return "--option1\n--negatable\n--no-negatable".\PHP_EOL; + } + + public function getExpectedValuesOutput(): string + { + return "Green\nRed\nYellow".\PHP_EOL; + } +} diff --git a/Tests/Completion/Output/CompletionOutputTestCase.php b/Tests/Completion/Output/CompletionOutputTestCase.php new file mode 100644 index 000000000..c4551e5b6 --- /dev/null +++ b/Tests/Completion/Output/CompletionOutputTestCase.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Completion\Output; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Completion\Output\CompletionOutputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\StreamOutput; + +abstract class CompletionOutputTestCase extends TestCase +{ + abstract public function getCompletionOutput(): CompletionOutputInterface; + + abstract public function getExpectedOptionsOutput(): string; + + abstract public function getExpectedValuesOutput(): string; + + public function testOptionsOutput() + { + $options = [ + new InputOption('option1', 'o', InputOption::VALUE_NONE), + new InputOption('negatable', null, InputOption::VALUE_NEGATABLE), + ]; + $suggestions = new CompletionSuggestions(); + $suggestions->suggestOptions($options); + $stream = fopen('php://memory', 'rw+'); + $this->getCompletionOutput()->write($suggestions, new StreamOutput($stream)); + fseek($stream, 0); + $this->assertEquals($this->getExpectedOptionsOutput(), stream_get_contents($stream)); + } + + public function testValuesOutput() + { + $suggestions = new CompletionSuggestions(); + $suggestions->suggestValues(['Green', 'Red', 'Yellow']); + $stream = fopen('php://memory', 'rw+'); + $this->getCompletionOutput()->write($suggestions, new StreamOutput($stream)); + fseek($stream, 0); + $this->assertEquals($this->getExpectedValuesOutput(), stream_get_contents($stream)); + } +} From 40663b49fbddbe72db81ec8a84233e8c31c5c6d1 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Fri, 3 Jun 2022 14:46:29 +0200 Subject: [PATCH 2/5] [Console] Escape % in command name & description from getDefault*() --- DependencyInjection/AddConsoleCommandPass.php | 4 +-- .../AddConsoleCommandPassTest.php | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php index 743e306d0..2c9ebe668 100644 --- a/DependencyInjection/AddConsoleCommandPass.php +++ b/DependencyInjection/AddConsoleCommandPass.php @@ -67,7 +67,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $aliases = $class::getDefaultName(); + $aliases = str_replace('%', '%%', $class::getDefaultName()); } $aliases = explode('|', $aliases ?? ''); @@ -124,7 +124,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $description = $class::getDefaultDescription(); + $description = str_replace('%', '%%', $class::getDefaultDescription()); } if ($description) { diff --git a/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/Tests/DependencyInjection/AddConsoleCommandPassTest.php index aa92c76f1..eec4eff43 100644 --- a/Tests/DependencyInjection/AddConsoleCommandPassTest.php +++ b/Tests/DependencyInjection/AddConsoleCommandPassTest.php @@ -153,6 +153,33 @@ public function testProcessFallsBackToDefaultDescription() $this->assertSame(1 + $initCounter, DescribedCommand::$initCounter); } + public function testEscapesDefaultFromPhp() + { + $container = new ContainerBuilder(); + $container + ->register('to-escape', EscapedDefaultsFromPhpCommand::class) + ->addTag('console.command') + ; + + $pass = new AddConsoleCommandPass(); + $pass->process($container); + + $commandLoader = $container->getDefinition('console.command_loader'); + $commandLocator = $container->getDefinition((string) $commandLoader->getArgument(0)); + + $this->assertSame(ContainerCommandLoader::class, $commandLoader->getClass()); + $this->assertSame(['%%cmd%%' => 'to-escape', '%%cmdalias%%' => 'to-escape'], $commandLoader->getArgument(1)); + $this->assertEquals([['to-escape' => new ServiceClosureArgument(new Reference('.to-escape.lazy'))]], $commandLocator->getArguments()); + $this->assertSame([], $container->getParameter('console.command.ids')); + + $command = $container->get('console.command_loader')->get('%%cmd%%'); + + $this->assertInstanceOf(LazyCommand::class, $command); + $this->assertSame('%cmd%', $command->getName()); + $this->assertSame(['%cmdalias%'], $command->getAliases()); + $this->assertSame('Creates a 80% discount', $command->getDescription()); + } + public function testProcessThrowAnExceptionIfTheServiceIsAbstract() { $this->expectException(\InvalidArgumentException::class); @@ -286,6 +313,12 @@ class NamedCommand extends Command protected static $defaultName = 'default'; } +class EscapedDefaultsFromPhpCommand extends Command +{ + protected static $defaultName = '%cmd%|%cmdalias%'; + protected static $defaultDescription = 'Creates a 80% discount'; +} + class DescribedCommand extends Command { public static $initCounter = 0; From 948192497e2bbbb697571a7590ee63d041b21e72 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Mon, 6 Jun 2022 14:28:06 +0200 Subject: [PATCH 3/5] [Console] Prevent PHP 8.1 str_replace deprec on null Prevents: > Deprecated: str_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated on `getDefaultName()` returning `null` --- DependencyInjection/AddConsoleCommandPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php index 2c9ebe668..c55c5db4e 100644 --- a/DependencyInjection/AddConsoleCommandPass.php +++ b/DependencyInjection/AddConsoleCommandPass.php @@ -67,7 +67,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $aliases = str_replace('%', '%%', $class::getDefaultName()); + $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); } $aliases = explode('|', $aliases ?? ''); From 56f40c96d6619cd87717a066f7ce65054bfd38b1 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 7 Jun 2022 04:10:23 +0200 Subject: [PATCH 4/5] [Console] Fix deprecation when description is null str_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated --- DependencyInjection/AddConsoleCommandPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DependencyInjection/AddConsoleCommandPass.php b/DependencyInjection/AddConsoleCommandPass.php index c55c5db4e..1fbb212e7 100644 --- a/DependencyInjection/AddConsoleCommandPass.php +++ b/DependencyInjection/AddConsoleCommandPass.php @@ -124,7 +124,7 @@ public function process(ContainerBuilder $container) if (!$r->isSubclassOf(Command::class)) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class)); } - $description = str_replace('%', '%%', $class::getDefaultDescription()); + $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); } if ($description) { From 6187424023fbffcd757789aeb517c9161b1eabee Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 8 Jun 2022 15:49:57 +0200 Subject: [PATCH 5/5] [Console] Fix tests --- Tests/DependencyInjection/AddConsoleCommandPassTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/DependencyInjection/AddConsoleCommandPassTest.php b/Tests/DependencyInjection/AddConsoleCommandPassTest.php index 7b4ff08b0..ddda0f472 100644 --- a/Tests/DependencyInjection/AddConsoleCommandPassTest.php +++ b/Tests/DependencyInjection/AddConsoleCommandPassTest.php @@ -314,10 +314,9 @@ class NamedCommand extends Command { } +#[AsCommand(name: '%cmd%|%cmdalias%', description: 'Creates a 80% discount')] class EscapedDefaultsFromPhpCommand extends Command { - protected static $defaultName = '%cmd%|%cmdalias%'; - protected static $defaultDescription = 'Creates a 80% discount'; } #[AsCommand(name: '|cmdname|cmdalias', description: 'Just testing')]