From 08a526334f7c283ae88d22c78b3f8c2fc503f832 Mon Sep 17 00:00:00 2001 From: Stephen Holdaway Date: Mon, 7 Jan 2019 11:18:13 +1300 Subject: [PATCH] Prevent global/application options conflicting with CompletionCommand `CompletionCommand` does not need to know about custom application-level options in user code. This filters out options that aren't in the default set provided by the base `Application` class to prevent conflicts with option names while maintaining backwards compatibility. If user-code wants to use global options in a subclass of CompletionCommand, `filterApplicationOptions` can be overridden to append any desired options to the default list. Solves the issue mentioned in #86 --- src/CompletionCommand.php | 47 ++++++++++++++++++- .../BashCompletion/CompletionCommandTest.php | 41 ++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionCommandTest.php diff --git a/src/CompletionCommand.php b/src/CompletionCommand.php index 0a4a809..3b3e75a 100644 --- a/src/CompletionCommand.php +++ b/src/CompletionCommand.php @@ -10,7 +10,6 @@ class CompletionCommand extends SymfonyCommand { - /** * @var CompletionHandler */ @@ -49,6 +48,52 @@ public function getNativeDefinition() return $this->createDefinition(); } + /** + * Ignore user-defined global options + * + * Any global options defined by user-code are meaningless to this command. + * Options outside of the core defaults are ignored to avoid name and shortcut conflicts. + */ + public function mergeApplicationDefinition($mergeArgs = true) + { + // Get current application options + $appDefinition = $this->getApplication()->getDefinition(); + $originalOptions = $appDefinition->getOptions(); + + // Temporarily replace application options with a filtered list + $appDefinition->setOptions( + $this->filterApplicationOptions($originalOptions) + ); + + parent::mergeApplicationDefinition($mergeArgs); + + // Restore original application options + $appDefinition->setOptions($originalOptions); + } + + /** + * Reduce the passed list of options to the core defaults (if they exist) + * + * @param InputOption[] $appOptions + * @return InputOption[] + */ + protected function filterApplicationOptions(array $appOptions) + { + return array_filter($appOptions, function(InputOption $option) { + static $coreOptions = array( + 'help' => true, + 'quiet' => true, + 'verbose' => true, + 'version' => true, + 'ansi' => true, + 'no-ansi' => true, + 'no-interaction' => true, + ); + + return isset($coreOptions[$option->getName()]); + }); + } + protected function execute(InputInterface $input, OutputInterface $output) { $this->handler = new CompletionHandler($this->getApplication()); diff --git a/tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionCommandTest.php b/tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionCommandTest.php new file mode 100644 index 0000000..78b634d --- /dev/null +++ b/tests/Stecman/Component/Symfony/Console/BashCompletion/CompletionCommandTest.php @@ -0,0 +1,41 @@ +getDefinition()->addOption( + new InputOption('conflicting-shortcut', 'g', InputOption::VALUE_NONE) + ); + + // Conflicting option name + $app->getDefinition()->addOption( + new InputOption('program', null, InputOption::VALUE_REQUIRED) + ); + + $app->add(new CompletionCommand()); + + // Check completion command doesn't throw + $app->doRun(new StringInput('_completion -g --program foo'), new NullOutput()); + $app->doRun(new StringInput('_completion --help'), new NullOutput()); + $app->doRun(new StringInput('help _completion'), new NullOutput()); + + // Check default options are available + $app->doRun(new StringInput('_completion -V -vv --no-ansi --quiet'), new NullOutput()); + } +}