1
0
Fork 0

Improve package suggestions, show only vendors by default when showing all available packages, add support for -p/-a in show command

pull/10320/head
Jordi Boggiano 2022-05-12 22:51:11 +02:00
parent 1162629a17
commit 3b2745a00d
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
6 changed files with 108 additions and 23 deletions

View File

@ -13,7 +13,9 @@
namespace Composer\Command;
use Composer\Composer;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\InstalledRepository;
use Composer\Repository\PlatformRepository;
@ -46,9 +48,9 @@ trait CompletionTrait
/**
* Suggest package names from installed.
*/
private function suggestInstalledPackage(): \Closure
private function suggestInstalledPackage(bool $includePlatformPackages = false): \Closure
{
return function (): array {
return function (CompletionInput $input) use ($includePlatformPackages): array {
$composer = $this->requireComposer();
$installedRepos = [new RootPackageRepository(clone $composer->getPackage())];
@ -59,46 +61,113 @@ trait CompletionTrait
$installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
}
$platformHint = [];
if ($includePlatformPackages) {
if ($locker->isLocked()) {
$platformRepo = new PlatformRepository(array(), $locker->getPlatformOverrides());
} else {
$platformRepo = new PlatformRepository(array(), $composer->getConfig()->get('platform') ?: array());
}
if ($input->getCompletionValue() === '') {
// to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes
$hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99];
foreach ($platformRepo->getPackages() as $pkg) {
foreach ($hintsToFind as $hintPrefix => $hintCount) {
if (str_starts_with($pkg->getName(), $hintPrefix)) {
if ($hintCount === 0 || $hintCount >= 99) {
$platformHint[] = $pkg->getName();
$hintsToFind[$hintPrefix]++;
} elseif ($hintCount === 1) {
unset($hintsToFind[$hintPrefix]);
$platformHint[] = substr($pkg->getName(), 0, max(strlen($pkg->getName()) - 3, strlen($hintPrefix) + 1)).'...';
}
continue 2;
}
}
}
} else {
$installedRepos[] = $platformRepo;
}
}
$installedRepo = new InstalledRepository($installedRepos);
return array_map(function (PackageInterface $package) {
return array_merge(
array_map(function (PackageInterface $package) {
return $package->getName();
}, $installedRepo->getPackages());
}, $installedRepo->getPackages()),
$platformHint
);
};
}
/**
* Suggest package names available on all configured repositories.
* @todo rework to list packages from cache
*/
private function suggestAvailablePackage(): \Closure
private function suggestAvailablePackage(int $max = 99): \Closure
{
return function (CompletionInput $input) {
return function (CompletionInput $input) use ($max): array {
if ($max < 1) {
return [];
}
$composer = $this->requireComposer();
$repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
$packages = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME);
$results = [];
if (!str_contains($input->getCompletionValue(), '/')) {
$results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR);
$vendors = true;
}
return array_column(array_slice($packages, 0, 150), 'name');
// if we get a single vendor, we expand it into its contents already
if (\count($results) <= 1) {
$results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME);
$vendors = false;
}
$results = array_column(array_slice($results, 0, $max), 'name');
if ($vendors) {
$results = array_map(function (string $name): string {
return $name.'/';
}, $results);
}
return $results;
};
}
/**
* Suggest package names available on all configured repositories or
* ext- packages from the ones available on the currently-running PHP
* platform packages from the ones available on the currently-running PHP
*/
private function suggestAvailablePackageOrExtension(): \Closure
private function suggestAvailablePackageInclPlatform(): \Closure
{
return function (CompletionInput $input) {
if (!str_starts_with($input->getCompletionValue(), 'ext-')) {
return $this->suggestAvailablePackage()($input);
return function (CompletionInput $input): array {
if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) {
$matches = $this->suggestPlatformPackage()($input);
} else {
$matches = [];
}
return array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input));
};
}
/**
* Suggest platform packages from the ones available on the currently-running PHP
*/
private function suggestPlatformPackage(): \Closure
{
return function (CompletionInput $input): array {
$repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform') ?? []);
return array_map(function (PackageInterface $package) {
$pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*');
return array_filter(array_map(function (PackageInterface $package) {
return $package->getName();
}, $repos->getPackages());
}, $repos->getPackages()), function (string $name) use ($pattern): bool {
return Preg::isMatch($pattern, $name);
});
};
}
}

View File

@ -36,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', null, $this->suggestInstalledPackage()),
new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage(true)),
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'),
))

View File

@ -59,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"', 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('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->suggestAvailablePackageInclPlatform()),
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->suggestAvailablePackageInclPlatform()),
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'),

View File

@ -43,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.', null, $this->suggestInstalledPackage()),
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Packages that should be removed.', null, $this->suggestInstalledPackage(true)),
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.'),

View File

@ -68,7 +68,7 @@ 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"', null, $this->suggestAvailablePackageOrExtension()),
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->suggestAvailablePackageInclPlatform()),
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.'),

View File

@ -40,6 +40,7 @@ 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\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Console\Input\InputArgument;
@ -75,7 +76,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.', null, $this->suggestInstalledPackage()),
new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestPackageBasedOnMode()),
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'),
@ -109,6 +110,21 @@ EOT
;
}
protected function suggestPackageBasedOnMode(): \Closure
{
return function (CompletionInput $input) {
if ($input->getOption('available') || $input->getOption('all')) {
return $this->suggestAvailablePackageInclPlatform()($input);
}
if ($input->getOption('platform')) {
return $this->suggestPlatformPackage()($input);
}
return $this->suggestInstalledPackage()($input);
};
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->versionParser = new VersionParser;