PageRenderTime 65ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Composer/Command/CompletionTrait.php

https://github.com/composer/composer
PHP | 209 lines | 139 code | 28 blank | 42 comment | 18 complexity | 5585e6679367fbc3ee5b267ba9a68dd2 MD5 | raw file
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Command;
  12. use Composer\Composer;
  13. use Composer\Package\BasePackage;
  14. use Composer\Package\PackageInterface;
  15. use Composer\Pcre\Preg;
  16. use Composer\Repository\CompositeRepository;
  17. use Composer\Repository\InstalledRepository;
  18. use Composer\Repository\PlatformRepository;
  19. use Composer\Repository\RepositoryInterface;
  20. use Composer\Repository\RootPackageRepository;
  21. use Symfony\Component\Console\Completion\CompletionInput;
  22. /**
  23. * Adds completion to arguments and options.
  24. *
  25. * @internal
  26. */
  27. trait CompletionTrait
  28. {
  29. /**
  30. * @see BaseCommand::requireComposer()
  31. */
  32. abstract public function requireComposer(bool $disablePlugins = null, bool $disableScripts = null): Composer;
  33. /**
  34. * Suggestion values for "prefer-install" option
  35. *
  36. * @return string[]
  37. */
  38. private function suggestPreferInstall(): array
  39. {
  40. return ['dist', 'source', 'auto'];
  41. }
  42. /**
  43. * Suggest package names from root requirements.
  44. */
  45. private function suggestRootRequirement(): \Closure
  46. {
  47. return function (CompletionInput $input): array {
  48. $composer = $this->requireComposer();
  49. return array_merge(array_keys($composer->getPackage()->getRequires()), array_keys($composer->getPackage()->getDevRequires()));
  50. };
  51. }
  52. /**
  53. * Suggest package names from installed.
  54. */
  55. private function suggestInstalledPackage(bool $includePlatformPackages = false): \Closure
  56. {
  57. return function (CompletionInput $input) use ($includePlatformPackages): array {
  58. $composer = $this->requireComposer();
  59. $installedRepos = [new RootPackageRepository(clone $composer->getPackage())];
  60. $locker = $composer->getLocker();
  61. if ($locker->isLocked()) {
  62. $installedRepos[] = $locker->getLockedRepository(true);
  63. } else {
  64. $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
  65. }
  66. $platformHint = [];
  67. if ($includePlatformPackages) {
  68. if ($locker->isLocked()) {
  69. $platformRepo = new PlatformRepository(array(), $locker->getPlatformOverrides());
  70. } else {
  71. $platformRepo = new PlatformRepository(array(), $composer->getConfig()->get('platform'));
  72. }
  73. if ($input->getCompletionValue() === '') {
  74. // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes
  75. $hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99];
  76. foreach ($platformRepo->getPackages() as $pkg) {
  77. foreach ($hintsToFind as $hintPrefix => $hintCount) {
  78. if (str_starts_with($pkg->getName(), $hintPrefix)) {
  79. if ($hintCount === 0 || $hintCount >= 99) {
  80. $platformHint[] = $pkg->getName();
  81. $hintsToFind[$hintPrefix]++;
  82. } elseif ($hintCount === 1) {
  83. unset($hintsToFind[$hintPrefix]);
  84. $platformHint[] = substr($pkg->getName(), 0, max(strlen($pkg->getName()) - 3, strlen($hintPrefix) + 1)).'...';
  85. }
  86. continue 2;
  87. }
  88. }
  89. }
  90. } else {
  91. $installedRepos[] = $platformRepo;
  92. }
  93. }
  94. $installedRepo = new InstalledRepository($installedRepos);
  95. return array_merge(
  96. array_map(static function (PackageInterface $package) {
  97. return $package->getName();
  98. }, $installedRepo->getPackages()),
  99. $platformHint
  100. );
  101. };
  102. }
  103. /**
  104. * Suggest package names available on all configured repositories.
  105. */
  106. private function suggestAvailablePackage(int $max = 99): \Closure
  107. {
  108. return function (CompletionInput $input) use ($max): array {
  109. if ($max < 1) {
  110. return [];
  111. }
  112. $composer = $this->requireComposer();
  113. $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
  114. $results = [];
  115. $showVendors = false;
  116. if (!str_contains($input->getCompletionValue(), '/')) {
  117. $results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR);
  118. $showVendors = true;
  119. }
  120. // if we get a single vendor, we expand it into its contents already
  121. if (\count($results) <= 1) {
  122. $results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME);
  123. $showVendors = false;
  124. }
  125. $results = array_column($results, 'name');
  126. if ($showVendors) {
  127. $results = array_map(static function (string $name): string {
  128. return $name.'/';
  129. }, $results);
  130. // sort shorter results first to avoid auto-expanding the completion to a longer string than needed
  131. usort($results, static function (string $a, string $b) {
  132. $lenA = \strlen($a);
  133. $lenB = \strlen($b);
  134. if ($lenA === $lenB) {
  135. return $a <=> $b;
  136. }
  137. return $lenA - $lenB;
  138. });
  139. $pinned = [];
  140. // ensure if the input is an exact match that it is always in the result set
  141. $completionInput = $input->getCompletionValue().'/';
  142. if (false !== ($exactIndex = array_search($completionInput, $results, true))) {
  143. $pinned[] = $completionInput;
  144. array_splice($results, $exactIndex, 1);
  145. }
  146. return array_merge($pinned, array_slice($results, 0, $max - \count($pinned)));
  147. }
  148. return array_slice($results, 0, $max);
  149. };
  150. }
  151. /**
  152. * Suggest package names available on all configured repositories or
  153. * platform packages from the ones available on the currently-running PHP
  154. */
  155. private function suggestAvailablePackageInclPlatform(): \Closure
  156. {
  157. return function (CompletionInput $input): array {
  158. if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) {
  159. $matches = $this->suggestPlatformPackage()($input);
  160. } else {
  161. $matches = [];
  162. }
  163. return array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input));
  164. };
  165. }
  166. /**
  167. * Suggest platform packages from the ones available on the currently-running PHP
  168. */
  169. private function suggestPlatformPackage(): \Closure
  170. {
  171. return function (CompletionInput $input): array {
  172. $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform'));
  173. $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*');
  174. return array_filter(array_map(static function (PackageInterface $package) {
  175. return $package->getName();
  176. }, $repos->getPackages()), static function (string $name) use ($pattern): bool {
  177. return Preg::isMatch($pattern, $name);
  178. });
  179. };
  180. }
  181. }