Skip to content

Commit

Permalink
Improve package suggestions, show only vendors by default when showin…
Browse files Browse the repository at this point in the history
…g all available packages, add support for -p/-a in show command
  • Loading branch information
Seldaek committed May 12, 2022
1 parent 1162629 commit 3b2745a
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 23 deletions.
103 changes: 86 additions & 17 deletions src/Composer/Command/CompletionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -46,9 +48,9 @@ private function suggestPreferInstall(): array
/**
* 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())];

Expand All @@ -59,46 +61,113 @@ private function suggestInstalledPackage(): \Closure
$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 $package->getName();
}, $installedRepo->getPackages());
return array_merge(
array_map(function (PackageInterface $package) {
return $package->getName();
}, $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;
}

// 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;
}

return array_column(array_slice($packages, 0, 150), 'name');
$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);
});
};
}
}
2 changes: 1 addition & 1 deletion src/Composer/Command/DependsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected function configure(): void
->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'),
))
Expand Down
4 changes: 2 additions & 2 deletions src/Composer/Command/InitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ protected function configure()
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'),
Expand Down
2 changes: 1 addition & 1 deletion src/Composer/Command/RemoveCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ protected function configure()
->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.'),
Expand Down
2 changes: 1 addition & 1 deletion src/Composer/Command/RequireCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected function configure()
->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.'),
Expand Down
18 changes: 17 additions & 1 deletion src/Composer/Command/ShowCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
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;
Expand Down Expand Up @@ -75,7 +76,7 @@ protected function configure()
->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'),
Expand Down Expand Up @@ -109,6 +110,21 @@ protected function configure()
;
}

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;
Expand Down

0 comments on commit 3b2745a

Please sign in to comment.