Add completion on commands
parent
766943c767
commit
fe6be142b1
|
@ -27,6 +27,8 @@ 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 Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -39,6 +41,17 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class ArchiveCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($this->completeAvailablePackage($input, $suggestions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(['tar', 'tar.gz', 'tar.bz2', 'zip']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -20,10 +20,19 @@ 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;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Helper\TableSeparator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
@ -317,7 +326,96 @@ abstract class BaseCommand extends Command
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $requirements
|
||||
* 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<string, string> $requirements
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
|
|
|
@ -33,6 +33,8 @@ 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 Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -55,6 +57,11 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var SuggestedPackagesReporter
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
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;
|
||||
|
@ -22,6 +24,11 @@ use Symfony\Component\Console\Input\InputOption;
|
|||
*/
|
||||
class DependsCommand extends BaseDependencyCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeInstalledPackage($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure command metadata.
|
||||
*
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
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 Symfony\Component\Console\Output\OutputInterface;
|
||||
|
@ -22,6 +24,13 @@ use Symfony\Component\Console\Input\InputArgument;
|
|||
*/
|
||||
class ExecCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('binary')) {
|
||||
$suggestions->suggestValues($this->getBinaries(false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
@ -52,14 +61,11 @@ EOT
|
|||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$composer = $this->requireComposer();
|
||||
if ($input->getOption('list') || !$input->getArgument('binary')) {
|
||||
$bins = $this->getBinaries(true);
|
||||
if (count($bins) > 0) {
|
||||
$binDir = $composer->getConfig()->get('bin-dir');
|
||||
if ($input->getOption('list') || null === $input->getArgument('binary')) {
|
||||
$bins = glob($binDir . '/*');
|
||||
$bins = array_merge($bins, array_map(function ($e) {
|
||||
return "$e (local)";
|
||||
}, $composer->getPackage()->getBinaries()));
|
||||
|
||||
if (!$bins) {
|
||||
throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)");
|
||||
}
|
||||
|
||||
|
@ -70,13 +76,6 @@ EOT
|
|||
);
|
||||
|
||||
foreach ($bins as $bin) {
|
||||
// skip .bat copies
|
||||
if (isset($previousBin) && $bin === $previousBin.'.bat') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$previousBin = $bin;
|
||||
$bin = basename($bin);
|
||||
$this->getIO()->write(
|
||||
<<<EOT
|
||||
<info>- $bin</info>
|
||||
|
@ -105,4 +104,30 @@ EOT
|
|||
|
||||
return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args'));
|
||||
}
|
||||
|
||||
private function getBinaries(bool $forDisplay): array
|
||||
{
|
||||
$composer = $this->getComposer();
|
||||
$binDir = $composer->getConfig()->get('bin-dir');
|
||||
$bins = glob($binDir . '/*');
|
||||
$localBins = $composer->getPackage()->getBinaries();
|
||||
if ($forDisplay) {
|
||||
$localBins = array_map(function ($e) {
|
||||
return "$e (local)";
|
||||
}, $localBins);
|
||||
}
|
||||
|
||||
$binaries = [];
|
||||
foreach (array_merge($bins, $localBins) as $bin) {
|
||||
// skip .bat copies
|
||||
if (isset($previousBin) && $bin === $previousBin.'.bat') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$previousBin = $bin;
|
||||
$binaries[] = basename($bin);
|
||||
}
|
||||
|
||||
return $binaries;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ use Composer\Factory;
|
|||
use Composer\Pcre\Preg;
|
||||
use Composer\Util\Filesystem;
|
||||
use Composer\Util\Platform;
|
||||
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 Symfony\Component\Console\Input\StringInput;
|
||||
|
@ -26,6 +29,16 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class GlobalCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$application = $this->getApplication();
|
||||
if ($input->mustSuggestArgumentValuesFor('command-name')) {
|
||||
$suggestions->suggestValues(array_filter(array_map(function (Command $command) {
|
||||
return $command->isHidden() ? null : $command->getName();
|
||||
}, $application->all())));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -18,6 +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 Symfony\Component\Console\Input\InputInterface;
|
||||
|
@ -28,6 +30,11 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class HomeCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeInstalledPackage($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
|
|
|
@ -23,6 +23,8 @@ 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;
|
||||
|
@ -42,6 +44,14 @@ class InitCommand extends BaseCommand
|
|||
/** @var array<string, string> */
|
||||
private $gitConfig;
|
||||
|
||||
/** @var RepositorySet[] */
|
||||
private $repositorySets;
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeAvailablePackage($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
|
|
|
@ -16,6 +16,8 @@ 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;
|
||||
|
@ -29,6 +31,11 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
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\InputArgument;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
|
@ -23,6 +25,11 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
*/
|
||||
class OutdatedCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeInstalledPackage($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
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;
|
||||
|
@ -22,6 +24,11 @@ use Symfony\Component\Console\Input\InputOption;
|
|||
*/
|
||||
class ProhibitsCommand extends BaseDependencyCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeAvailablePackage($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure command metadata.
|
||||
*
|
||||
|
|
|
@ -22,6 +22,8 @@ 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;
|
||||
|
@ -32,6 +34,11 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -20,6 +20,8 @@ use Composer\Plugin\CommandEvent;
|
|||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Factory;
|
||||
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;
|
||||
|
@ -32,6 +34,11 @@ use Composer\Package\BasePackage;
|
|||
*/
|
||||
class RemoveCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeInstalledPackage($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,8 @@ 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;
|
||||
|
@ -58,6 +60,11 @@ 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
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,8 @@ 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;
|
||||
|
@ -44,6 +46,13 @@ 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
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,8 @@ 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;
|
||||
|
@ -30,6 +32,13 @@ 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
|
||||
*/
|
||||
|
|
|
@ -20,7 +20,6 @@ use Composer\Package\BasePackage;
|
|||
use Composer\Package\CompletePackageInterface;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Package\Version\VersionSelector;
|
||||
|
@ -41,6 +40,8 @@ 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;
|
||||
|
@ -64,6 +65,17 @@ 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
|
||||
*/
|
||||
|
|
|
@ -16,6 +16,8 @@ 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 Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -23,6 +25,11 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
|
||||
class SuggestsCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeInstalledPackage($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,8 @@ use Composer\Package\Version\VersionParser;
|
|||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Semver\Constraint\MultiConstraint;
|
||||
use Composer\Package\Link;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
@ -37,6 +39,11 @@ use Symfony\Component\Console\Question\Question;
|
|||
*/
|
||||
class UpdateCommand extends BaseCommand
|
||||
{
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
$this->completeInstalledPackage($input, $suggestions) || $this->completePreferInstall($input, $suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
|
|
@ -74,6 +74,12 @@ class VersionGuesser
|
|||
return null;
|
||||
}
|
||||
|
||||
// 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') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$versionData = $this->guessGitVersion($packageConfig, $path);
|
||||
if (null !== $versionData && null !== $versionData['version']) {
|
||||
return $this->postprocess($versionData);
|
||||
|
|
|
@ -44,8 +44,12 @@ class HttpDownloader
|
|||
private $config;
|
||||
/** @var array<Job> */
|
||||
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 */
|
||||
|
@ -73,22 +77,19 @@ class HttpDownloader
|
|||
|
||||
$this->disabled = (bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK');
|
||||
|
||||
// Setup TLS options
|
||||
// The cafile option can be set via config.json
|
||||
if ($disableTls === false) {
|
||||
$this->options = StreamContextFactory::getTlsDefaults($options, $io);
|
||||
if ($disableTls === true) {
|
||||
// make sure the tlsDefaultOptions are not loaded later
|
||||
$this->tlsDefaultOptions = [];
|
||||
}
|
||||
|
||||
// handle the other externally set options normally.
|
||||
$this->options = array_replace_recursive($this->options, $options);
|
||||
$this->disableTls = $disableTls;
|
||||
$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));
|
||||
}
|
||||
|
@ -171,7 +172,13 @@ class HttpDownloader
|
|||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,7 +198,7 @@ class HttpDownloader
|
|||
*/
|
||||
private function addJob(array $request, bool $sync = false): array
|
||||
{
|
||||
$request['options'] = array_replace_recursive($this->options, $request['options']);
|
||||
$request['options'] = array_replace_recursive($this->getOptions(), $request['options']);
|
||||
|
||||
/** @var Job */
|
||||
$job = array(
|
||||
|
@ -211,8 +218,6 @@ 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;
|
||||
|
@ -285,6 +290,15 @@ 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
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Test;
|
||||
|
||||
use Composer\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandCompletionTester;
|
||||
|
||||
/**
|
||||
* Validate autocompletion for all commands.
|
||||
*
|
||||
* @author Jérôme Tamarelle <jerome@tamarelle.net>
|
||||
*/
|
||||
class CompletionFunctionalTest extends TestCase
|
||||
{
|
||||
public function getCommandSuggestions(): iterable
|
||||
{
|
||||
$randomProject = '104corp/cache';
|
||||
$installedPackages = ['composer/semver', 'psr/log'];
|
||||
$preferInstall = ['dist', 'source', 'auto'];
|
||||
|
||||
yield ['archive ', [$randomProject]];
|
||||
yield ['archive symfony/http-', ['symfony/http-kernel', 'symfony/http-foundation']];
|
||||
yield ['archive --format ', ['tar', 'zip']];
|
||||
|
||||
yield ['create-project ', [$randomProject]];
|
||||
yield ['create-project symfony/skeleton --prefer-install ', $preferInstall];
|
||||
|
||||
yield ['depends ', $installedPackages];
|
||||
yield ['why ', $installedPackages];
|
||||
|
||||
yield ['exec ', ['composer', 'compile']];
|
||||
|
||||
yield ['browse ', $installedPackages];
|
||||
yield ['home -H ', $installedPackages];
|
||||
|
||||
yield ['init --require ', [$randomProject]];
|
||||
yield ['init --require-dev foo/bar --require-dev ', [$randomProject]];
|
||||
|
||||
yield ['install --prefer-install ', $preferInstall];
|
||||
yield ['install ', $installedPackages];
|
||||
|
||||
yield ['outdated ', $installedPackages];
|
||||
|
||||
yield ['prohibits ', [$randomProject]];
|
||||
yield ['why-not symfony/http-ker', ['symfony/http-kernel']];
|
||||
|
||||
yield ['reinstall --prefer-install ', $preferInstall];
|
||||
yield ['reinstall ', $installedPackages];
|
||||
|
||||
yield ['remove ', $installedPackages];
|
||||
|
||||
yield ['require --prefer-install ', $preferInstall];
|
||||
yield ['require ', [$randomProject]];
|
||||
yield ['require --dev symfony/http-', ['symfony/http-kernel', 'symfony/http-foundation']];
|
||||
|
||||
yield ['run-script ', ['compile', 'test', 'phpstan']];
|
||||
yield ['run-script test ', null];
|
||||
|
||||
yield ['search --format ', ['text', 'json']];
|
||||
|
||||
yield ['show --format ', ['text', 'json']];
|
||||
yield ['info ', $installedPackages];
|
||||
|
||||
yield ['suggests ', $installedPackages];
|
||||
|
||||
yield ['update --prefer-install ', $preferInstall];
|
||||
yield ['update ', $installedPackages];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getCommandSuggestions
|
||||
*
|
||||
* @param string $input The command that is typed
|
||||
* @param string[]|null $expectedSuggestions Sample expected suggestions. Null if nothing is expected.
|
||||
*/
|
||||
public function testComplete(string $input, ?array $expectedSuggestions): void
|
||||
{
|
||||
$input = explode(' ', $input);
|
||||
$commandName = array_shift($input);
|
||||
$command = $this->getApplication()->get($commandName);
|
||||
|
||||
$tester = new CommandCompletionTester($command);
|
||||
$suggestions = $tester->complete($input);
|
||||
|
||||
if (null === $expectedSuggestions) {
|
||||
$this->assertEmpty($suggestions);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$diff = array_diff($expectedSuggestions, $suggestions);
|
||||
$this->assertEmpty($diff, sprintf('Suggestions must contain "%s". Got "%s".', implode('", "', $diff), implode('", "', $suggestions)));
|
||||
}
|
||||
|
||||
private function getApplication(): Application
|
||||
{
|
||||
return new Application();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue