From ed7d8219addbc9d1abe7ae6224a035d57530dea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 14 Apr 2022 22:24:33 +0200 Subject: [PATCH] Backport bash completion suggestion definition from symfony/console 6.1 --- src/Composer/Command/ArchiveCommand.php | 21 +-- src/Composer/Command/BaseCommand.php | 124 ++++-------------- src/Composer/Command/CompletionTrait.php | 102 ++++++++++++++ src/Composer/Command/CreateProjectCommand.php | 15 +-- src/Composer/Command/DependsCommand.php | 13 +- src/Composer/Command/ExecCommand.php | 23 ++-- src/Composer/Command/FundCommand.php | 4 +- src/Composer/Command/GlobalCommand.php | 2 +- src/Composer/Command/HomeCommand.php | 13 +- src/Composer/Command/InitCommand.php | 17 +-- src/Composer/Command/InstallCommand.php | 15 +-- src/Composer/Command/OutdatedCommand.php | 15 +-- src/Composer/Command/ProhibitsCommand.php | 13 +- src/Composer/Command/ReinstallCommand.php | 15 +-- src/Composer/Command/RemoveCommand.php | 13 +- src/Composer/Command/RequireCommand.php | 16 +-- src/Composer/Command/RunScriptCommand.php | 17 +-- src/Composer/Command/ScriptAliasCommand.php | 4 +- src/Composer/Command/SearchCommand.php | 15 +-- src/Composer/Command/SelfUpdateCommand.php | 4 +- src/Composer/Command/ShowCommand.php | 25 +--- src/Composer/Command/StatusCommand.php | 2 +- src/Composer/Command/SuggestsCommand.php | 13 +- src/Composer/Command/UpdateCommand.php | 15 +-- src/Composer/Console/Input/InputArgument.php | 61 +++++++++ src/Composer/Console/Input/InputOption.php | 65 +++++++++ src/Composer/Factory.php | 14 +- .../Package/Version/VersionGuesser.php | 3 +- src/Composer/Util/HttpDownloader.php | 38 ++---- src/Composer/Util/Platform.php | 8 ++ 30 files changed, 376 insertions(+), 329 deletions(-) create mode 100644 src/Composer/Command/CompletionTrait.php create mode 100644 src/Composer/Console/Input/InputArgument.php create mode 100644 src/Composer/Console/Input/InputOption.php diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index 2629db7e9..1114ed029 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -27,11 +27,9 @@ use Composer\Util\Filesystem; use Composer\Util\Loop; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** @@ -41,16 +39,9 @@ use Symfony\Component\Console\Output\OutputInterface; */ class ArchiveCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($this->completeAvailablePackage($input, $suggestions)) { - return; - } + use CompletionTrait; - if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['tar', 'tar.gz', 'tar.bz2', 'zip']); - } - } + private const FORMATS = ['tar', 'tar.gz', 'tar.bz2', 'zip']; /** * @return void @@ -61,9 +52,9 @@ class ArchiveCommand extends BaseCommand ->setName('archive') ->setDescription('Creates an archive of this composer package.') ->setDefinition(array( - new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project'), + new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project', null, $this->suggestAvailablePackage()), new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'), - new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)', null, self::FORMATS), new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'), new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.' .' Note that the format will be appended.'), diff --git a/src/Composer/Command/BaseCommand.php b/src/Composer/Command/BaseCommand.php index dc88c5e17..7ab897d5d 100644 --- a/src/Composer/Command/BaseCommand.php +++ b/src/Composer/Command/BaseCommand.php @@ -15,21 +15,16 @@ namespace Composer\Command; use Composer\Composer; use Composer\Config; use Composer\Console\Application; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; use Composer\Factory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory; use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface; use Composer\IO\IOInterface; use Composer\IO\NullIO; -use Composer\Package\Package; -use Composer\Package\PackageInterface; use Composer\Plugin\PreCommandRunEvent; use Composer\Package\Version\VersionParser; use Composer\Plugin\PluginEvents; -use Composer\Repository\CompositeRepository; -use Composer\Repository\InstalledRepository; -use Composer\Repository\PlatformRepository; -use Composer\Repository\RepositoryInterface; -use Composer\Repository\RootPackageRepository; use Composer\Util\Platform; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; @@ -191,6 +186,30 @@ abstract class BaseCommand extends Command $this->io = $io; } + /** + * @inheritdoc + * + * Backport suggested values definition from symfony/console 6.1+ + */ + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + $definition = $this->getDefinition(); + $name = (string) $input->getCompletionName(); + if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() + && $definition->hasOption($name) + && ($option = $definition->getOption($name)) instanceof InputOption + ) { + $option->complete($input, $suggestions); + } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() + && $definition->hasArgument($name) + && ($argument = $definition->getArgument($name)) instanceof InputArgument + ) { + $argument->complete($input, $suggestions); + } else { + parent::complete($input, $suggestions); + } + } + /** * @inheritDoc * @@ -326,96 +345,7 @@ abstract class BaseCommand extends Command } /** - * Suggestion values for "prefer-install" option - */ - protected function completePreferInstall(CompletionInput $input, CompletionSuggestions $suggestions): bool - { - if ($input->mustSuggestOptionValuesFor('prefer-install')) { - $suggestions->suggestValues(['dist', 'source', 'auto']); - - return true; - } - - return false; - } - - /** - * Suggest package names from installed ones. - */ - protected function completeInstalledPackage(CompletionInput $input, CompletionSuggestions $suggestions): bool - { - if (!$input->mustSuggestArgumentValuesFor('packages') && - !$input->mustSuggestArgumentValuesFor('package') && - !$input->mustSuggestOptionValuesFor('ignore') - ) { - return false; - } - - $composer = $this->getComposer(); - $installedRepos = [new RootPackageRepository(clone $composer->getPackage())]; - - $locker = $composer->getLocker(); - if ($locker->isLocked()) { - $installedRepos[] = $locker->getLockedRepository(true); - } else { - $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); - } - - $installedRepo = new InstalledRepository($installedRepos); - $suggestions->suggestValues(array_map(function (PackageInterface $package) { - return $package->getName(); - }, $installedRepo->getPackages())); - - return true; - } - - /** - * Suggest package names available on all configured repositories. - */ - protected function completeAvailablePackage(CompletionInput $input, CompletionSuggestions $suggestions): bool - { - if (!$input->mustSuggestArgumentValuesFor('packages') && - !$input->mustSuggestArgumentValuesFor('package') && - !$input->mustSuggestOptionValuesFor('require') && - !$input->mustSuggestOptionValuesFor('require-dev') - ) { - return false; - } - - $composer = $this->getComposer(); - $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); - - $packages = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME); - - foreach (array_slice($packages, 0, 150) as $package) { - $suggestions->suggestValue($package['name']); - } - - return true; - } - - /** - * Suggests ext- packages from the ones available on the currently-running PHP - */ - protected function completePlatformPackage(CompletionInput $input, CompletionSuggestions $suggestions): bool - { - if (!$input->mustSuggestOptionValuesFor('require') && - !$input->mustSuggestOptionValuesFor('require-dev') && - !str_starts_with($input->getCompletionValue(), 'ext-') - ) { - return false; - } - - $repos = new PlatformRepository([], $this->getComposer()->getConfig()->get('platform') ?? []); - $suggestions->suggestValues(array_map(function (PackageInterface $package) { - return $package->getName(); - }, $repos->getPackages())); - - return true; - } - - /** - * @param array $requirements + * @param array $requirements * * @return array */ diff --git a/src/Composer/Command/CompletionTrait.php b/src/Composer/Command/CompletionTrait.php new file mode 100644 index 000000000..c16c3a8f5 --- /dev/null +++ b/src/Composer/Command/CompletionTrait.php @@ -0,0 +1,102 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Composer; +use Composer\Package\PackageInterface; +use Composer\Repository\CompositeRepository; +use Composer\Repository\InstalledRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\RepositoryInterface; +use Composer\Repository\RootPackageRepository; +use Symfony\Component\Console\Completion\CompletionInput; + +/** + * Adds completion to arguments and options. + * + * @internal + */ +trait CompletionTrait +{ + /** + * @see BaseCommand::requireComposer() + */ + abstract public function requireComposer(bool $disablePlugins = null, bool $disableScripts = null): Composer; + + /** + * Suggestion values for "prefer-install" option + */ + private function suggestPreferInstall(): array + { + return ['dist', 'source', 'auto']; + } + + /** + * Suggest package names from installed. + */ + private function suggestInstalledPackage(): \Closure + { + return function (): array { + $composer = $this->requireComposer(); + $installedRepos = [new RootPackageRepository(clone $composer->getPackage())]; + + $locker = $composer->getLocker(); + if ($locker->isLocked()) { + $installedRepos[] = $locker->getLockedRepository(true); + } else { + $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository(); + } + + $installedRepo = new InstalledRepository($installedRepos); + + return array_map(function (PackageInterface $package) { + return $package->getName(); + }, $installedRepo->getPackages()); + }; + } + + /** + * Suggest package names available on all configured repositories. + * @todo rework to list packages from cache + */ + private function suggestAvailablePackage(): \Closure + { + return function (CompletionInput $input) { + $composer = $this->requireComposer(); + $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); + + $packages = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME); + + return array_column(array_slice($packages, 0, 150), 'name'); + }; + } + + /** + * Suggest package names available on all configured repositories or + * ext- packages from the ones available on the currently-running PHP + */ + private function suggestAvailablePackageOrExtension(): \Closure + { + return function (CompletionInput $input) { + if (!str_starts_with($input->getCompletionValue(), 'ext-')) { + return $this->suggestAvailablePackage()($input); + } + + $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform') ?? []); + + return array_map(function (PackageInterface $package) { + return $package->getName(); + }, $repos->getPackages()); + }; + } +} diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 341caaaa8..23356c267 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -33,11 +33,9 @@ use Composer\Repository\InstalledArrayRepository; use Composer\Repository\RepositorySet; use Composer\Script\ScriptEvents; use Composer\Util\Silencer; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; use Composer\Json\JsonFile; @@ -57,10 +55,7 @@ use Composer\Package\Version\VersionParser; */ class CreateProjectCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completeAvailablePackage($input, $suggestions) || $this->completePreferInstall($input, $suggestions); - } + use CompletionTrait; /** * @var SuggestedPackagesReporter @@ -76,13 +71,13 @@ class CreateProjectCommand extends BaseCommand ->setName('create-project') ->setDescription('Creates new project from a package into given directory.') ->setDefinition(array( - new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed'), + new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed', null, $this->suggestAvailablePackage()), new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'), new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), - new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories to look the package up, either by URL or using JSON arrays'), new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'), new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.'), diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index d5c65ed46..53ba7115f 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -12,22 +12,17 @@ namespace Composer\Command; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; /** * @author Niels Keurentjes */ class DependsCommand extends BaseDependencyCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completeInstalledPackage($input, $suggestions); - } + use CompletionTrait; /** * Configure command metadata. @@ -41,7 +36,7 @@ class DependsCommand extends BaseDependencyCommand ->setAliases(array('why')) ->setDescription('Shows which packages cause the given package to be installed.') ->setDefinition(array( - new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'), + new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage()), new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), )) diff --git a/src/Composer/Command/ExecCommand.php b/src/Composer/Command/ExecCommand.php index 1afdb6e8d..49df0a832 100644 --- a/src/Composer/Command/ExecCommand.php +++ b/src/Composer/Command/ExecCommand.php @@ -12,25 +12,16 @@ namespace Composer\Command; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputArgument; /** * @author Davey Shafik */ class ExecCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($input->mustSuggestArgumentValuesFor('binary')) { - $suggestions->suggestValues($this->getBinaries(false)); - } - } - /** * @return void */ @@ -41,7 +32,9 @@ class ExecCommand extends BaseCommand ->setDescription('Executes a vendored binary/script.') ->setDefinition(array( new InputOption('list', 'l', InputOption::VALUE_NONE), - new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit'), + new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit', null, function () { + return $this->getBinaries(false); + }), new InputArgument( 'args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, @@ -61,9 +54,9 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { $composer = $this->requireComposer(); - if ($input->getOption('list') || !$input->getArgument('binary')) { + if ($input->getOption('list') || null === $input->getArgument('binary')) { $bins = $this->getBinaries(true); - if (count($bins) > 0) { + if ([] === $bins) { $binDir = $composer->getConfig()->get('bin-dir'); throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)"); @@ -107,7 +100,7 @@ EOT private function getBinaries(bool $forDisplay): array { - $composer = $this->getComposer(); + $composer = $this->requireComposer(); $binDir = $composer->getConfig()->get('bin-dir'); $bins = glob($binDir . '/*'); $localBins = $composer->getPackage()->getBinaries(); diff --git a/src/Composer/Command/FundCommand.php b/src/Composer/Command/FundCommand.php index 057fea5d6..15960d294 100644 --- a/src/Composer/Command/FundCommand.php +++ b/src/Composer/Command/FundCommand.php @@ -21,7 +21,7 @@ use Composer\Repository\CompositeRepository; use Composer\Semver\Constraint\MatchAllConstraint; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** @@ -38,7 +38,7 @@ class FundCommand extends BaseCommand $this->setName('fund') ->setDescription('Discover how to help fund the maintenance of your dependencies.') ->setDefinition(array( - new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['text', 'json']), )) ; } diff --git a/src/Composer/Command/GlobalCommand.php b/src/Composer/Command/GlobalCommand.php index ea9b496e3..d74bf2b14 100644 --- a/src/Composer/Command/GlobalCommand.php +++ b/src/Composer/Command/GlobalCommand.php @@ -20,7 +20,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; diff --git a/src/Composer/Command/HomeCommand.php b/src/Composer/Command/HomeCommand.php index db715617c..5e792af63 100644 --- a/src/Composer/Command/HomeCommand.php +++ b/src/Composer/Command/HomeCommand.php @@ -18,10 +18,8 @@ use Composer\Repository\RootPackageRepository; use Composer\Repository\RepositoryFactory; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -30,10 +28,7 @@ use Symfony\Component\Console\Output\OutputInterface; */ class HomeCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completeInstalledPackage($input, $suggestions); - } + use CompletionTrait; /** * @inheritDoc @@ -47,7 +42,7 @@ class HomeCommand extends BaseCommand ->setAliases(array('home')) ->setDescription('Opens the package\'s repository URL or homepage in your browser.') ->setDefinition(array( - new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.'), + new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.', null, $this->suggestInstalledPackage()), new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'), new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'), )) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 8af6719c4..6677da101 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -23,11 +23,9 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryFactory; use Composer\Util\Filesystem; use Composer\Util\Silencer; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; @@ -39,19 +37,12 @@ use Symfony\Component\Console\Helper\FormatterHelper; */ class InitCommand extends BaseCommand { + use CompletionTrait; use PackageDiscoveryTrait; /** @var array */ private $gitConfig; - /** @var RepositorySet[] */ - private $repositorySets; - - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completeAvailablePackage($input, $suggestions); - } - /** * @inheritDoc * @@ -68,8 +59,8 @@ class InitCommand extends BaseCommand new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'), new InputOption('type', null, InputOption::VALUE_OPTIONAL, 'Type of package (e.g. library, project, metapackage, composer-plugin)'), new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'), - new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), - new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), + new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackage()), + new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackage()), new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::$stabilities)).')'), new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'), new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'), diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index cbc51042d..35c7f664e 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -16,11 +16,9 @@ use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Util\HttpDownloader; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** @@ -31,10 +29,7 @@ use Symfony\Component\Console\Output\OutputInterface; */ class InstallCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completePreferInstall($input, $suggestions) || $this->completeInstalledPackage($input, $suggestions); - } + use CompletionTrait; /** * @return void @@ -48,7 +43,7 @@ class InstallCommand extends BaseCommand ->setDefinition(array( new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), - new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), @@ -63,7 +58,7 @@ class InstallCommand extends BaseCommand new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.', null, $this->suggestInstalledPackage()), )) ->setHelp( <<completeInstalledPackage($input, $suggestions); - } + use CompletionTrait; /** * @return void @@ -39,7 +34,7 @@ class OutdatedCommand extends BaseCommand ->setName('outdated') ->setDescription('Shows a list of installed packages that have updates available, including their latest version.') ->setDefinition(array( - new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'), + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage()), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'), new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'), new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'), @@ -48,7 +43,7 @@ class OutdatedCommand extends BaseCommand new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --outdated option.'), new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), - new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage()), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), diff --git a/src/Composer/Command/ProhibitsCommand.php b/src/Composer/Command/ProhibitsCommand.php index 45908ec40..d536a3141 100644 --- a/src/Composer/Command/ProhibitsCommand.php +++ b/src/Composer/Command/ProhibitsCommand.php @@ -12,22 +12,17 @@ namespace Composer\Command; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; /** * @author Niels Keurentjes */ class ProhibitsCommand extends BaseDependencyCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completeAvailablePackage($input, $suggestions); - } + use CompletionTrait; /** * Configure command metadata. @@ -41,7 +36,7 @@ class ProhibitsCommand extends BaseDependencyCommand ->setAliases(array('why-not')) ->setDescription('Shows which packages prevent the given package from being installed.') ->setDefinition(array( - new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'), + new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestAvailablePackage()), new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::REQUIRED, 'Version constraint, which version you expected to be installed'), new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'), new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'), diff --git a/src/Composer/Command/ReinstallCommand.php b/src/Composer/Command/ReinstallCommand.php index c845fc3f5..40a376134 100644 --- a/src/Composer/Command/ReinstallCommand.php +++ b/src/Composer/Command/ReinstallCommand.php @@ -22,11 +22,9 @@ use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Script\ScriptEvents; use Composer\Util\Platform; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** @@ -34,10 +32,7 @@ use Symfony\Component\Console\Output\OutputInterface; */ class ReinstallCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completeInstalledPackage($input, $suggestions) || $this->completePreferInstall($input, $suggestions); - } + use CompletionTrait; /** * @return void @@ -50,7 +45,7 @@ class ReinstallCommand extends BaseCommand ->setDefinition(array( new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), - new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'), @@ -59,7 +54,7 @@ class ReinstallCommand extends BaseCommand new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'), - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'List of package names to reinstall, can include a wildcard (*) to match any substring.'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage()), )) ->setHelp( <<completeInstalledPackage($input, $suggestions); - } + use CompletionTrait; /** * @return void @@ -48,7 +43,7 @@ class RemoveCommand extends BaseCommand ->setName('remove') ->setDescription('Removes a package from the require or require-dev.') ->setDefinition(array( - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.', null, $this->suggestInstalledPackage()), new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index ff8d39976..d8fead4f9 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -14,11 +14,9 @@ namespace Composer\Command; use Composer\DependencyResolver\Request; use Composer\Util\Filesystem; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Factory; use Composer\Installer; @@ -41,6 +39,7 @@ use Composer\Util\Silencer; */ class RequireCommand extends BaseCommand { + use CompletionTrait; use PackageDiscoveryTrait; /** @var bool */ @@ -60,11 +59,6 @@ class RequireCommand extends BaseCommand /** @var bool */ private $dependencyResolutionCompleted = false; - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completePlatformPackage($input, $suggestions) || $this->completeAvailablePackage($input, $suggestions) || $this->completePreferInstall($input, $suggestions); - } - /** * @return void */ @@ -74,12 +68,12 @@ class RequireCommand extends BaseCommand ->setName('require') ->setDescription('Adds required packages to your composer.json and installs them.') ->setDefinition(array( - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageOrExtension()), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), - new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), diff --git a/src/Composer/Command/RunScriptCommand.php b/src/Composer/Command/RunScriptCommand.php index 2b035cab2..cdc262b1a 100644 --- a/src/Composer/Command/RunScriptCommand.php +++ b/src/Composer/Command/RunScriptCommand.php @@ -16,11 +16,9 @@ use Composer\Script\Event as ScriptEvent; use Composer\Script\ScriptEvents; use Composer\Util\ProcessExecutor; use Composer\Util\Platform; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** @@ -46,13 +44,6 @@ class RunScriptCommand extends BaseCommand ScriptEvents::POST_AUTOLOAD_DUMP, ); - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($input->mustSuggestArgumentValuesFor('script')) { - $suggestions->suggestValues(array_keys($this->getComposer()->getPackage()->getScripts())); - } - } - /** * @return void */ @@ -63,7 +54,9 @@ class RunScriptCommand extends BaseCommand ->setAliases(array('run')) ->setDescription('Runs the scripts defined in composer.json.') ->setDefinition(array( - new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.'), + new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.', null, function () { + return array_keys($this->requireComposer()->getPackage()->getScripts()); + }), new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''), new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'), diff --git a/src/Composer/Command/ScriptAliasCommand.php b/src/Composer/Command/ScriptAliasCommand.php index 4c34368ad..603ea1059 100644 --- a/src/Composer/Command/ScriptAliasCommand.php +++ b/src/Composer/Command/ScriptAliasCommand.php @@ -13,8 +13,8 @@ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; /** diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index f7dfa2001..32ceaa9bc 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -14,12 +14,10 @@ namespace Composer\Command; use Composer\Factory; use Composer\Json\JsonFile; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; @@ -32,13 +30,6 @@ use Composer\Plugin\PluginEvents; */ class SearchCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['json', 'text']); - } - } - /** * @return void */ @@ -51,7 +42,7 @@ class SearchCommand extends BaseCommand new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in package names'), new InputOption('only-vendor', 'O', InputOption::VALUE_NONE, 'Search only for vendor / organization names, returns only "vendor" as result'), new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'), - new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'), )) ->setHelp( diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 5e025b486..f8291f133 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -24,8 +24,8 @@ use Composer\IO\IOInterface; use Composer\Downloader\FilesystemException; use Composer\Downloader\TransportException; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputOption; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index c128295c4..dcce4f16a 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -40,13 +40,11 @@ use Composer\Semver\Constraint\ConstraintInterface; use Composer\Semver\Semver; use Composer\Spdx\SpdxLicenses; use Composer\Util\PackageInfo; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** @@ -57,6 +55,8 @@ use Symfony\Component\Console\Output\OutputInterface; */ class ShowCommand extends BaseCommand { + use CompletionTrait; + /** @var VersionParser */ protected $versionParser; /** @var string[] */ @@ -65,17 +65,6 @@ class ShowCommand extends BaseCommand /** @var ?RepositorySet */ private $repositorySet; - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - if ($this->completeInstalledPackage($input, $suggestions)) { - return; - } - - if ($input->mustSuggestOptionValuesFor('format')) { - $suggestions->suggestValues(['json', 'text']); - } - } - /** * @return void */ @@ -86,7 +75,7 @@ class ShowCommand extends BaseCommand ->setAliases(array('info')) ->setDescription('Shows information about packages.') ->setDefinition(array( - new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'), + new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage()), new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'), new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'), new InputOption('locked', null, InputOption::VALUE_NONE, 'List all locked packages'), @@ -99,12 +88,12 @@ class ShowCommand extends BaseCommand new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'), new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'), new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'), - new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'), + new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage()), new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'), new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --outdated option.'), new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'), new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'), - new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'), + new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'), new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'), new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'), diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index c749294d9..0f8e2b530 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -13,7 +13,7 @@ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Composer\Downloader\ChangeReportInterface; use Composer\Downloader\DvcsDownloaderInterface; diff --git a/src/Composer/Command/SuggestsCommand.php b/src/Composer/Command/SuggestsCommand.php index 173337691..0dd1653b4 100644 --- a/src/Composer/Command/SuggestsCommand.php +++ b/src/Composer/Command/SuggestsCommand.php @@ -16,19 +16,14 @@ use Composer\Repository\PlatformRepository; use Composer\Repository\RootPackageRepository; use Composer\Repository\InstalledRepository; use Composer\Installer\SuggestedPackagesReporter; -use Symfony\Component\Console\Completion\CompletionInput; -use Symfony\Component\Console\Completion\CompletionSuggestions; -use Symfony\Component\Console\Input\InputArgument; +use Composer\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; +use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class SuggestsCommand extends BaseCommand { - public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void - { - $this->completeInstalledPackage($input, $suggestions); - } + use CompletionTrait; /** * @return void @@ -44,7 +39,7 @@ class SuggestsCommand extends BaseCommand new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'), new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'), - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.', null, $this->suggestInstalledPackage()), )) ->setHelp( <<completeInstalledPackage($input, $suggestions) || $this->completePreferInstall($input, $suggestions); - } + use CompletionTrait; /** * @return void @@ -54,11 +49,11 @@ class UpdateCommand extends BaseCommand ->setAliases(array('u', 'upgrade')) ->setDescription('Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file.') ->setDefinition(array( - new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.'), + new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.', null, $this->suggestInstalledPackage()), new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'), - new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'), + new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()), new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'), new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'), new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'), diff --git a/src/Composer/Console/Input/InputArgument.php b/src/Composer/Console/Input/InputArgument.php new file mode 100644 index 000000000..88fe62b3c --- /dev/null +++ b/src/Composer/Console/Input/InputArgument.php @@ -0,0 +1,61 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console\Input; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputArgument as BaseInputArgument; + +/** + * Backport suggested values definition from symfony/console 6.1+ + * + * @author Jérôme Tamarelle + * + * @internal + */ +class InputArgument extends BaseInputArgument +{ + /** + * @var string[]|\Closure + */ + private $suggestedValues; + + /** + * @inheritdoc + * + * @param string[]|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + */ + public function __construct(string $name, int $mode = null, string $description = '', $default = null, $suggestedValues = []) + { + parent::__construct($name, $mode, $description, $default, $suggestedValues); + + $this->suggestedValues = $suggestedValues; + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + 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 option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } +} diff --git a/src/Composer/Console/Input/InputOption.php b/src/Composer/Console/Input/InputOption.php new file mode 100644 index 000000000..0d4ca2722 --- /dev/null +++ b/src/Composer/Console/Input/InputOption.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Console\Input; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputOption as BaseInputOption; + +/** + * Backport suggested values definition from symfony/console 6.1+ + * + * @author Jérôme Tamarelle + * + * @internal + */ +class InputOption extends BaseInputOption +{ + /** + * @var string[]|\Closure + */ + private $suggestedValues; + + /** + * @inheritdoc + * + * @param string[]|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion + */ + public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null, $suggestedValues = []) + { + parent::__construct($name, $shortcut, $mode, $description, $default, $suggestedValues); + + $this->suggestedValues = $suggestedValues; + + if ($suggestedValues && !$this->acceptValue()) { + throw new LogicException('Cannot set suggested values if the option does not accept a value.'); + } + } + + /** + * Adds suggestions to $suggestions for the current completion input. + * + * @see Command::complete() + */ + 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->getName(), get_debug_type($values))); + } + if ($values) { + $suggestions->suggestValues($values); + } + } +} diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 93253b0b3..af468ab80 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -309,12 +309,14 @@ class Factory throw new \InvalidArgumentException($message.PHP_EOL.$instructions); } - try { - $file->validateSchema(JsonFile::LAX_SCHEMA); - } catch (JsonValidationException $e) { - $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); - $message = $e->getMessage() . ':' . PHP_EOL . $errors; - throw new JsonValidationException($message); + if (!Platform::isInputCompletionProcess()) { + try { + $file->validateSchema(JsonFile::LAX_SCHEMA); + } catch (JsonValidationException $e) { + $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors()); + $message = $e->getMessage() . ':' . PHP_EOL . $errors; + throw new JsonValidationException($message); + } } $localConfig = $file->read(); diff --git a/src/Composer/Package/Version/VersionGuesser.php b/src/Composer/Package/Version/VersionGuesser.php index 96384b128..d768f3247 100644 --- a/src/Composer/Package/Version/VersionGuesser.php +++ b/src/Composer/Package/Version/VersionGuesser.php @@ -19,6 +19,7 @@ use Composer\IO\NullIO; use Composer\Semver\VersionParser as SemverVersionParser; use Composer\Util\Git as GitUtil; use Composer\Util\HttpDownloader; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Composer\Util\Svn as SvnUtil; use React\Promise\CancellablePromiseInterface; @@ -76,7 +77,7 @@ class VersionGuesser // bypass version guessing in bash completions as it takes time to create // new processes and the root version is usually not that important - if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] === '_complete') { + if (Platform::isInputCompletionProcess()) { return null; } diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index 4fb6b9f76..581e17226 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -44,12 +44,8 @@ class HttpDownloader private $config; /** @var array */ private $jobs = array(); - /** @var bool */ - private $disableTls; /** @var mixed[] */ private $options = array(); - /** @var mixed[]|null */ - private $tlsDefaultOptions = null; /** @var int */ private $runningJobs = 0; /** @var int */ @@ -77,19 +73,22 @@ class HttpDownloader $this->disabled = (bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK'); - if ($disableTls === true) { - // make sure the tlsDefaultOptions are not loaded later - $this->tlsDefaultOptions = []; + // Setup TLS options + // The cafile option can be set via config.json + if ($disableTls === false) { + $this->options = StreamContextFactory::getTlsDefaults($options, $io); } - $this->disableTls = $disableTls; - $this->options = $options; + // handle the other externally set options normally. + $this->options = array_replace_recursive($this->options, $options); $this->config = $config; if (self::isCurlEnabled()) { $this->curl = new CurlDownloader($io, $config, $options, $disableTls); } + $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls); + if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_HTTP'))) { $this->maxJobs = max(1, min(50, (int) $maxJobs)); } @@ -172,13 +171,7 @@ class HttpDownloader */ public function getOptions() { - if ($this->tlsDefaultOptions === null) { - // Setup TLS options - // The cafile option can be set via config.json - $this->tlsDefaultOptions = StreamContextFactory::getTlsDefaults($this->options, $this->io); - } - - return array_replace_recursive($this->tlsDefaultOptions, $this->options); + return $this->options; } /** @@ -198,7 +191,7 @@ class HttpDownloader */ private function addJob(array $request, bool $sync = false): array { - $request['options'] = array_replace_recursive($this->getOptions(), $request['options']); + $request['options'] = array_replace_recursive($this->options, $request['options']); /** @var Job */ $job = array( @@ -218,6 +211,8 @@ class HttpDownloader $this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2])); } + $rfs = $this->rfs; + if ($this->canUseCurl($job)) { $resolver = function ($resolve, $reject) use (&$job): void { $job['status'] = HttpDownloader::STATUS_QUEUED; @@ -290,15 +285,6 @@ class HttpDownloader return array($job, $promise); } - private function getRFS(): RemoteFilesystem - { - if (null === $this->rfs) { - $this->rfs = new RemoteFilesystem($this->io, $this->config, $this->options, $this->disableTls); - } - - return $this->rfs; - } - /** * @param int $id * @return void diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index be37ba621..83fe71bd4 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -225,6 +225,14 @@ class Platform return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } + /** + * @return bool Whether the current command is for bash completion + */ + public static function isInputCompletionProcess(): bool + { + return '_complete' === ($_SERVER['argv'][1] ?? null); + } + /** * @return void */