From aca619e130300953493b611d886b295452ce244d Mon Sep 17 00:00:00 2001 From: SofHad Date: Fri, 9 Jan 2015 21:44:39 +0100 Subject: [PATCH] Interactive interface with autocompletion --- src/Composer/Command/UpdateCommand.php | 115 ++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 10fa26987..66144088d 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -12,6 +12,7 @@ namespace Composer\Command; +use Composer\Composer; use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; @@ -19,6 +20,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; /** * @author Jordi Boggiano @@ -50,6 +53,7 @@ class UpdateCommand extends Command new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'), new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'), new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'), + new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'interactive interface with autocompletion that can help to select the packages to update.'), )) ->setHelp(<<update command reads the composer.json file from the @@ -74,13 +78,18 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { + $composer = $this->getComposer(true, $input->getOption('no-plugins')); + $packages = $input->getOption('interactive') ? $this->getPackagesInteractively( + $input, + $output, + $composer + ) : $input->getArgument('packages'); + if ($input->getOption('no-custom-installers')) { $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); $input->setOption('no-plugins', true); } - $composer = $this->getComposer(true, $input->getOption('no-plugins')); - $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); @@ -122,7 +131,7 @@ EOT ->setRunScripts(!$input->getOption('no-scripts')) ->setOptimizeAutoloader($optimize) ->setUpdate(true) - ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages')) + ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $packages) ->setWhitelistDependencies($input->getOption('with-dependencies')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) ->setPreferStable($input->getOption('prefer-stable')) @@ -135,4 +144,104 @@ EOT return $install->run(); } + + private function getPackagesInteractively( + InputInterface $input, + OutputInterface $output, + Composer $composer + ) { + if ($input->getArgument('packages')) { + throw new \InvalidArgumentException( + 'The option --interactive does not require an argument' + ); + } + + $packagesMap = $composer->getRepositoryManager() + ->getLocalRepository()->getPackages(); + $config = $composer->getConfig(); + + $requiredPackageNames = array(); + foreach ( + array_merge( + $composer->getPackage()->getRequires(), + $composer->getPackage()->getDevRequires() + ) as $require) { + $requiredPackageNames[] = $require->getTarget(); + } + + $InstalledPackageNames = array(); + foreach ($packagesMap as $package) { + $InstalledPackageNames[] = $package->getPrettyName(); + } + $names = array_unique( + array_merge($InstalledPackageNames, $requiredPackageNames) + ); + + $vendorDir = $config->get('vendor-dir', true); + $vendorWildcard = sprintf('%s/*', $vendorDir); + + $autocompleterValues = array($vendorWildcard); + foreach ($names as $name) { + $autocompleterValues[] = $name; + $autocompleterValues[] = sprintf('%s/%s', $vendorDir, $name); + } + + $helper = $this->getHelper('question'); + $question = new Question( + 'Add package that should be updated:', + null + ); + $confirmation = new ConfirmationQuestion( + 'Add more packages[yes|no] ? (no) ', + false + ); + + $packages = array(); + do { + $question->setAutocompleterValues($autocompleterValues); + $addedPackage = $helper->ask($input, $output, $question); + + if (!is_string($addedPackage) || empty($addedPackage)) { + $output->writeln('Invalid package.'); + + continue; + } + + if (!in_array($addedPackage, $packages)) { + $packages[] = $addedPackage; + if ($addedPackage === $vendorWildcard) { + $autocompleterValues = array(); + } else { + $autocompleterValues = array_diff($autocompleterValues, array($addedPackage)); + } + } else { + $output->writeln(sprintf('The package "%s" was already added.', $package)); + } + } while ($helper->ask($input, $output, $confirmation)); + + $packages = array_filter($packages); + if (!$packages) { + throw new \InvalidArgumentException('You must enter minimum one package.'); + } + + $output->writeln(str_repeat('.', 40)); + foreach ((array)$packages as $package) { + $output->writeln(sprintf('- %s', $package)); + } + $output->writeln(str_repeat('.', 40)); + + $continue = new ConfirmationQuestion( + sprintf( + 'Would you like to continue and update the above package%s [yes|no] ? (yes) ', + 1 === count($packages) ? '' : 's' + ), + true + ); + + if ($helper->ask($input, $output, $continue)) { + return $packages; + } + + throw new \RuntimeException('Installation aborted.'); + } }