From aca619e130300953493b611d886b295452ce244d Mon Sep 17 00:00:00 2001 From: SofHad Date: Fri, 9 Jan 2015 21:44:39 +0100 Subject: [PATCH 1/5] 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.'); + } } From 6fff09c82a2008578386caba09fcd7c7af09db60 Mon Sep 17 00:00:00 2001 From: SofHad Date: Sat, 10 Jan 2015 23:24:24 +0100 Subject: [PATCH 2/5] Simplified questionnaire --- src/Composer/Command/UpdateCommand.php | 41 +++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 66144088d..23178e5b7 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -16,6 +16,7 @@ use Composer\Composer; use Composer\Installer; use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; +use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; @@ -188,47 +189,45 @@ EOT $helper = $this->getHelper('question'); $question = new Question( - 'Add package that should be updated:', + 'Enter package name: ', null ); - $confirmation = new ConfirmationQuestion( - 'Add more packages[yes|no] ? (no) ', - false - ); $packages = array(); + $output->writeln('NB: Empty package ends submission.'); // I couldn't find any better for now! + + $continue = true; 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(); + if (is_string($addedPackage)) { + if (!in_array($addedPackage, $packages)) { + $packages[] = $addedPackage; + if ($addedPackage === $vendorWildcard) { + $autocompleterValues = array(); + } else { + $autocompleterValues = array_diff($autocompleterValues, array($addedPackage)); + } } else { - $autocompleterValues = array_diff($autocompleterValues, array($addedPackage)); + $output->writeln(sprintf('The package "%s" was already added.', $package)); } } else { - $output->writeln(sprintf('The package "%s" was already added.', $package)); + $continue = false; } - } while ($helper->ask($input, $output, $confirmation)); + } while ($continue); $packages = array_filter($packages); if (!$packages) { throw new \InvalidArgumentException('You must enter minimum one package.'); } - $output->writeln(str_repeat('.', 40)); + $table = new Table($output); + $table->setHeaders(array('Selected packages')); foreach ((array)$packages as $package) { - $output->writeln(sprintf('- %s', $package)); + $table->addRow(array($package)); } - $output->writeln(str_repeat('.', 40)); + $table->render(); $continue = new ConfirmationQuestion( sprintf( From dd9e208bc795dfd200b4cb434c85a0b156fbf53f Mon Sep 17 00:00:00 2001 From: SofHad Date: Tue, 20 Jan 2015 00:12:14 +0100 Subject: [PATCH 3/5] update the logic --- src/Composer/Command/UpdateCommand.php | 43 ++++++-------------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 23178e5b7..95c1fa0be 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -80,11 +80,10 @@ 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'); + $packages = $input->getArgument('packages'); + if ($input->getOption('interactive')) { + $packages = $this->getPackagesInteractively($input, $output, $composer, $packages); + } if ($input->getOption('no-custom-installers')) { $output->writeln('You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.'); @@ -146,20 +145,10 @@ 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' - ); - } - + private function getPackagesInteractively(InputInterface $input, OutputInterface $output, Composer $composer, $packages) + { $packagesMap = $composer->getRepositoryManager() ->getLocalRepository()->getPackages(); - $config = $composer->getConfig(); $requiredPackageNames = array(); foreach ( @@ -174,26 +163,18 @@ EOT foreach ($packagesMap as $package) { $InstalledPackageNames[] = $package->getPrettyName(); } - $names = array_unique( + + $autocompleterValues = 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( 'Enter package name: ', null ); - $packages = array(); + $packages = is_array($packages) ? $packages : array(); $output->writeln('NB: Empty package ends submission.'); // I couldn't find any better for now! $continue = true; @@ -204,11 +185,7 @@ EOT if (is_string($addedPackage)) { if (!in_array($addedPackage, $packages)) { $packages[] = $addedPackage; - if ($addedPackage === $vendorWildcard) { - $autocompleterValues = array(); - } else { - $autocompleterValues = array_diff($autocompleterValues, array($addedPackage)); - } + $autocompleterValues = array_diff($autocompleterValues, array($addedPackage)); } else { $output->writeln(sprintf('The package "%s" was already added.', $package)); } From 1a82ecad910dce377909ea1428b4ba34038469d0 Mon Sep 17 00:00:00 2001 From: SofHad Date: Fri, 30 Jan 2015 10:13:54 +0100 Subject: [PATCH 4/5] Refactor the loop + add private method isInteractive --- src/Composer/Command/UpdateCommand.php | 41 +++++++++++++++----------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 95c1fa0be..2234ed4cf 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -79,17 +79,20 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $composer = $this->getComposer(true, $input->getOption('no-plugins')); - $packages = $input->getArgument('packages'); - if ($input->getOption('interactive')) { - $packages = $this->getPackagesInteractively($input, $output, $composer, $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')); + + $packages = $input->getArgument('packages'); + + if ($this->isInteractive($input)) { + $packages = $this->getPackagesInteractively($input, $output, $composer, $packages); + } + + $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress')); $io = $this->getIO(); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); @@ -177,22 +180,21 @@ EOT $packages = is_array($packages) ? $packages : array(); $output->writeln('NB: Empty package ends submission.'); // I couldn't find any better for now! - $continue = true; do { $question->setAutocompleterValues($autocompleterValues); $addedPackage = $helper->ask($input, $output, $question); - if (is_string($addedPackage)) { - if (!in_array($addedPackage, $packages)) { - $packages[] = $addedPackage; - $autocompleterValues = array_diff($autocompleterValues, array($addedPackage)); - } else { - $output->writeln(sprintf('The package "%s" was already added.', $package)); - } - } else { - $continue = false; + if (!is_string($addedPackage)) { + break; } - } while ($continue); + + if (!in_array($addedPackage, $packages)) { + $packages[] = $addedPackage; + $autocompleterValues = array_diff($autocompleterValues, array($addedPackage)); + } else { + $output->writeln(sprintf('The package "%s" was already added.', $package)); + } + } while (true); $packages = array_filter($packages); if (!$packages) { @@ -220,4 +222,9 @@ EOT throw new \RuntimeException('Installation aborted.'); } + + private function isInteractive($input) + { + return $input->getOption('interactive'); + } } From f9777bc70ae65c205892be455d2e39290b467965 Mon Sep 17 00:00:00 2001 From: SofHad Date: Fri, 30 Jan 2015 11:53:42 +0100 Subject: [PATCH 5/5] Check if input is interactive --- src/Composer/Command/UpdateCommand.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 2234ed4cf..edb4dd937 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -88,7 +88,7 @@ EOT $packages = $input->getArgument('packages'); - if ($this->isInteractive($input)) { + if ($input->getOption('interactive')) { $packages = $this->getPackagesInteractively($input, $output, $composer, $packages); } @@ -150,6 +150,10 @@ EOT private function getPackagesInteractively(InputInterface $input, OutputInterface $output, Composer $composer, $packages) { + if (!$input->isInteractive()) { + throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.'); + } + $packagesMap = $composer->getRepositoryManager() ->getLocalRepository()->getPackages(); @@ -222,9 +226,4 @@ EOT throw new \RuntimeException('Installation aborted.'); } - - private function isInteractive($input) - { - return $input->getOption('interactive'); - } }