Remove filterPackages and add RepositoryInterface::search, refactor all commands to use new methods and remove all usage of the full package list for Composer repositories that support providers, fixes #1646

main
Jordi Boggiano 11 years ago
parent 095852933e
commit be861f090a

@ -73,9 +73,8 @@ EOT
}, $input->getOption('link-type'));
$messages = array();
$repo->filterPackages(function ($package) use ($needle, $types, $linkTypes, &$messages) {
static $outputPackages = array();
$outputPackages = array();
foreach ($repo->getPackages() as $package) {
foreach ($types as $type) {
foreach ($package->{'get'.$linkTypes[$type][0]}() as $link) {
if ($link->getTarget() === $needle) {
@ -86,7 +85,7 @@ EOT
}
}
}
});
}
if ($messages) {
sort($messages);

@ -292,15 +292,7 @@ EOT
));
}
$token = strtolower($name);
$this->repos->filterPackages(function ($package) use ($token, &$packages) {
if (false !== strpos($package->getName(), $token)) {
$packages[] = $package;
}
});
return $packages;
return $this->repos->search($name);
}
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array())
@ -339,31 +331,57 @@ EOT
''
));
$exactMatch = null;
$choices = array();
foreach ($matches as $position => $package) {
$output->writeln(sprintf(' <info>%5s</info> %s <comment>%s</comment>', "[$position]", $package->getPrettyName(), $package->getPrettyVersion()));
$choices[] = sprintf(' <info>%5s</info> %s', "[$position]", $package['name']);
if ($package['name'] === $package) {
$exactMatch = true;
break;
}
}
$output->writeln('');
// no match, prompt which to pick
if (!$exactMatch) {
$output->writeln($choices);
$output->writeln('');
$validator = function ($selection) use ($matches) {
if ('' === $selection) {
return false;
}
$validator = function ($selection) use ($matches) {
if ('' === $selection) {
return false;
}
if (!is_numeric($selection) && preg_match('{^\s*(\S+) +(\S.*)\s*}', $selection, $matches)) {
return $matches[1].' '.$matches[2];
}
if (!is_numeric($selection) && preg_match('{^\s*(\S+)\s+(\S.*)\s*$}', $selection, $matches)) {
return $matches[1].' '.$matches[2];
}
if (!isset($matches[(int) $selection])) {
throw new \Exception('Not a valid selection');
}
if (!isset($matches[(int) $selection])) {
throw new \Exception('Not a valid selection');
}
$package = $matches[(int) $selection];
$package = $matches[(int) $selection];
return sprintf('%s %s', $package->getName(), $package->getPrettyVersion());
};
return $package['name'];
};
$package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or a "[package] [version]" couple if it is not listed', false, ':'), $validator, 3);
$package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or the complete package name if it is not listed', false, ':'), $validator, 3);
}
// no constraint yet, prompt user
if (false !== $package && false === strpos($package, ' ')) {
$validator = function ($input) {
$input = trim($input);
return $input ?: false;
};
$constraint = $dialog->askAndValidate($output, $dialog->getQuestion('Enter the version constraint to require', false, ':'), $validator, 3);
if (false === $constraint) {
continue;
}
$package .= ' '.$constraint;
}
if (false !== $package) {
$requires[] = $package;

@ -109,7 +109,7 @@ EOT
->setPreferDist($input->getOption('prefer-dist'))
->setDevMode($input->getOption('dev'))
->setUpdate(true)
->setUpdateWhitelist($requirements);
->setUpdateWhitelist(array_keys($requirements));
;
if (!$install->run()) {

@ -18,6 +18,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\AliasPackage;
use Composer\Factory;
@ -66,79 +67,13 @@ EOT
$repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
}
$this->onlyName = $input->getOption('only-name');
$this->tokens = $input->getArgument('tokens');
$this->output = $output;
$repos->filterPackages(array($this, 'processPackage'), 'Composer\Package\CompletePackage');
$onlyName = $input->getOption('only-name');
foreach ($this->lowMatches as $details) {
$output->writeln($details['name'] . '<comment>:</comment> '. $details['description']);
}
}
public function processPackage($package)
{
if ($package instanceof AliasPackage || isset($this->matches[$package->getName()])) {
return;
}
$flags = $onlyName ? RepositoryInterface::SEARCH_NAME : RepositoryInterface::SEARCH_FULLTEXT;
$results = $repos->search(implode(' ', $input->getArgument('tokens')), $flags);
foreach ($this->tokens as $token) {
if (!$score = $this->matchPackage($package, $token)) {
continue;
}
if (false !== ($pos = stripos($package->getName(), $token))) {
$name = substr($package->getPrettyName(), 0, $pos)
. '<highlight>' . substr($package->getPrettyName(), $pos, strlen($token)) . '</highlight>'
. substr($package->getPrettyName(), $pos + strlen($token));
} else {
$name = $package->getPrettyName();
}
$description = strtok($package->getDescription(), "\r\n");
if (false !== ($pos = stripos($description, $token))) {
$description = substr($description, 0, $pos)
. '<highlight>' . substr($description, $pos, strlen($token)) . '</highlight>'
. substr($description, $pos + strlen($token));
}
if ($score >= 3) {
$this->output->writeln($name . '<comment>:</comment> '. $description);
$this->matches[$package->getName()] = true;
} else {
$this->lowMatches[$package->getName()] = array(
'name' => $name,
'description' => $description,
);
}
return;
foreach ($results as $result) {
$output->writeln($result['name'] . (isset($result['description']) ? ' '. $result['description'] : ''));
}
}
/**
* tries to find a token within the name/keywords/description
*
* @param CompletePackageInterface $package
* @param string $token
* @return boolean
*/
private function matchPackage(CompletePackageInterface $package, $token)
{
$score = 0;
if (false !== stripos($package->getName(), $token)) {
$score += 5;
}
if (!$this->onlyName && false !== stripos(join(',', $package->getKeywords() ?: array()), $token)) {
$score += 3;
}
if (!$this->onlyName && false !== stripos($package->getDescription(), $token)) {
$score += 1;
}
return $score;
}
}

@ -13,8 +13,11 @@
namespace Composer\Command;
use Composer\Composer;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\Factory;
use Composer\Package\CompletePackageInterface;
use Composer\Package\LinkConstraint\VersionConstraint;
use Composer\Package\Version\VersionParser;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
@ -22,6 +25,7 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\ComposerRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
@ -122,20 +126,39 @@ EOT
// list packages
$packages = array();
$repos->filterPackages(function ($package) use (&$packages, $platformRepo, $installedRepo) {
if ($platformRepo->hasPackage($package)) {
if ($repos instanceof CompositeRepository) {
$repos = $repos->getRepositories();
} elseif (!is_array($repos)) {
$repos = array($repos);
}
foreach ($repos as $repo) {
if ($repo === $platformRepo) {
$type = '<info>platform</info>:';
} elseif ($installedRepo->hasPackage($package)) {
} elseif (
$repo === $installedRepo
|| ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
) {
$type = '<info>installed</info>:';
} else {
$type = '<comment>available</comment>:';
}
if (!isset($packages[$type][$package->getName()])
|| version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')
) {
$packages[$type][$package->getName()] = $package;
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
foreach ($repo->getProviderNames() as $name) {
$packages[$type][$name] = $name;
}
} else {
foreach ($repo->getPackages() as $package) {
if (!isset($packages[$type][$package->getName()])
|| !is_object($packages[$type][$package->getName()])
|| version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')
) {
$packages[$type][$package->getName()] = $package;
}
}
}
}, 'Composer\Package\CompletePackage');
}
$tree = !$input->getOption('platform') && !$input->getOption('installed') && !$input->getOption('available');
$indent = $tree ? ' ' : '';
@ -148,8 +171,12 @@ EOT
$nameLength = $versionLength = 0;
foreach ($packages[$type] as $package) {
$nameLength = max($nameLength, strlen($package->getPrettyName()));
$versionLength = max($versionLength, strlen($this->versionParser->formatVersion($package)));
if (is_object($package)) {
$nameLength = max($nameLength, strlen($package->getPrettyName()));
$versionLength = max($versionLength, strlen($this->versionParser->formatVersion($package)));
} else {
$nameLength = max($nameLength, $package);
}
}
list($width) = $this->getApplication()->getTerminalDimensions();
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
@ -159,19 +186,23 @@ EOT
$writeVersion = !$input->getOption('name-only') && $showVersion && ($nameLength + $versionLength + 3 <= $width);
$writeDescription = !$input->getOption('name-only') && ($nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width);
foreach ($packages[$type] as $package) {
$output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
if (is_object($package)) {
$output->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
if ($writeVersion) {
$output->write(' ' . str_pad($this->versionParser->formatVersion($package), $versionLength, ' '), false);
}
if ($writeVersion) {
$output->write(' ' . str_pad($this->versionParser->formatVersion($package), $versionLength, ' '), false);
}
if ($writeDescription) {
$description = strtok($package->getDescription(), "\r\n");
$remaining = $width - $nameLength - $versionLength - 4;
if (strlen($description) > $remaining) {
$description = substr($description, 0, $remaining - 3) . '...';
if ($writeDescription) {
$description = strtok($package->getDescription(), "\r\n");
$remaining = $width - $nameLength - $versionLength - 4;
if (strlen($description) > $remaining) {
$description = substr($description, 0, $remaining - 3) . '...';
}
$output->write(' ' . $description);
}
$output->write(' ' . $description);
} else {
$output->write($indent . $package);
}
$output->writeln('');
}
@ -195,51 +226,46 @@ EOT
protected function getPackage(RepositoryInterface $installedRepo, RepositoryInterface $repos, $name, $version = null)
{
$name = strtolower($name);
$constraint = null;
if ($version) {
$version = $this->versionParser->normalize($version);
$constraint = new VersionConstraint('=', $version);
}
$match = null;
$matches = array();
$repos->filterPackages(function ($package) use ($name, $version, &$matches) {
if ($package->getName() === $name) {
$matches[] = $package;
}
}, 'Composer\Package\CompletePackage');
if (null === $version) {
// search for a locally installed version
foreach ($matches as $package) {
if ($installedRepo->hasPackage($package)) {
$match = $package;
break;
}
$policy = new DefaultPolicy();
$pool = new Pool('dev');
$pool->addRepository($repos);
$matchedPackage = null;
$matches = $pool->whatProvides($name, $constraint);
foreach ($matches as $index => $package) {
// skip providers/replacers
if ($package->getName() !== $name) {
unset($matches[$index]);
continue;
}
if (!$match) {
// fallback to the highest version
foreach ($matches as $package) {
if (null === $match || version_compare($package->getVersion(), $match->getVersion(), '>=')) {
$match = $package;
}
}
}
} else {
// select the specified version
foreach ($matches as $package) {
if ($package->getVersion() === $version) {
$match = $package;
}
// select an exact match if it is in the installed repo and no specific version was required
if (null === $version && $installedRepo->hasPackage($package)) {
$matchedPackage = $package;
}
$matches[$index] = $package->getId();
}
// select prefered package according to policy rules
if (!$matchedPackage && $matches && $prefered = $policy->selectPreferedPackages($pool, array(), $matches)) {
$matchedPackage = $pool->literalToPackage($prefered[0]);
}
// build versions array
$versions = array();
foreach ($matches as $package) {
$package = $pool->literalToPackage($package);
$versions[$package->getPrettyVersion()] = $package->getVersion();
}
return array($match, $versions);
return array($matchedPackage, $versions);
}
/**

@ -214,13 +214,13 @@ class Installer
// output suggestions
foreach ($this->suggestedPackages as $suggestion) {
$target = $suggestion['target'];
if ($installedRepo->filterPackages(function (PackageInterface $package) use ($target) {
foreach ($installedRepo->getPackages() as $package) {
if (in_array($target, $package->getNames())) {
return false;
continue 2;
}
})) {
$this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
}
$this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
}
if (!$this->dryRun) {

@ -74,6 +74,27 @@ class ArrayRepository implements RepositoryInterface
return $packages;
}
/**
* {@inheritDoc}
*/
public function search($query, $mode = 0)
{
$regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i';
$matches = array();
foreach ($this->getPackages() as $package) {
// TODO implement SEARCH_FULLTEXT handling with keywords/description matching
if (preg_match($regex, $package->getName())) {
$matches[] = array(
'name' => $package->getName(),
'description' => $package->getDescription(),
);
}
}
return $matches;
}
/**
* {@inheritDoc}
*/
@ -112,20 +133,6 @@ class ArrayRepository implements RepositoryInterface
}
}
/**
* {@inheritDoc}
*/
public function filterPackages($callback, $class = 'Composer\Package\Package')
{
foreach ($this->getPackages() as $package) {
if (false === call_user_func($callback, $package)) {
return false;
}
}
return true;
}
protected function createAliasPackage(PackageInterface $package, $alias = null, $prettyAlias = null)
{
return new AliasPackage($package, $alias ?: $package->getAlias(), $prettyAlias ?: $package->getPrettyAlias());

@ -135,24 +135,54 @@ class ComposerRepository extends ArrayRepository implements StreamableRepository
/**
* {@inheritDoc}
*/
public function filterPackages($callback, $class = 'Composer\Package\Package')
public function search($query, $mode = 0)
{
if (null === $this->rawData) {
$this->rawData = $this->loadDataFromServer();
$this->loadRootServerFile();
if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) {
$url = str_replace('%query%', $query, $this->searchUrl);
$json = $this->rfs->getContents($url, $url, false);
$results = JsonFile::parseJson($json, $url);
return $results['results'];
}
foreach ($this->rawData as $package) {
if (false === call_user_func($callback, $package = $this->createPackage($package, $class))) {
return false;
}
if ($package->getAlias()) {
if (false === call_user_func($callback, $this->createAliasPackage($package))) {
return false;
if ($this->hasProviders()) {
$results = array();
$regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i';
foreach ($this->getProviderNames() as $name) {
if (preg_match($regex, $name)) {
$results[] = array('name' => $name);
}
}
return $results;
}
return parent::search($query, $mode);
}
public function getProviderNames()
{
$this->loadRootServerFile();
if (null === $this->providerListing) {
$this->loadProviderListings($this->loadRootServerFile());
}
if ($this->providersUrl) {
return array_keys($this->providerListing);
}
// BC handling for old providers-includes
$providers = array();
foreach (array_keys($this->providerListing) as $provider) {
$providers[] = substr($provider, 2, -5);
}
return true;
return $providers;
}
/**
@ -196,15 +226,15 @@ class ComposerRepository extends ArrayRepository implements StreamableRepository
public function whatProvides(Pool $pool, $name)
{
// skip platform packages
if ($name === 'php' || in_array(substr($name, 0, 4), array('ext-', 'lib-'), true) || $name === '__root__') {
return array();
}
if (isset($this->providers[$name])) {
return $this->providers[$name];
}
// skip platform packages
if (preg_match('{^(?:php(?:-64bit)?|(?:ext|lib)-[^/]+)$}i', $name) || '__root__' === $name) {
return array();
}
if (null === $this->providerListing) {
$this->loadProviderListings($this->loadRootServerFile());
}

@ -94,6 +94,20 @@ class CompositeRepository implements RepositoryInterface
return call_user_func_array('array_merge', $packages);
}
/**
* {@inheritdoc}
*/
public function search($query, $mode = 0)
{
$matches = array();
foreach ($this->repositories as $repository) {
/* @var $repository RepositoryInterface */
$matches[] = $repository->search($query, $mode);
}
return call_user_func_array('array_merge', $matches);
}
/**
* {@inheritDoc}
*/

@ -19,9 +19,13 @@ use Composer\Package\PackageInterface;
*
* @author Nils Adermann <naderman@naderman.de>
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface RepositoryInterface extends \Countable
{
const SEARCH_FULLTEXT = 0;
const SEARCH_NAME = 1;
/**
* Checks if specified package registered (installed).
*
@ -52,23 +56,18 @@ interface RepositoryInterface extends \Countable
public function findPackages($name, $version = null);
/**
* Filters all the packages through a callback
*
* The packages are not guaranteed to be instances in the repository
* and this can only be used for streaming through a list of packages.
*
* If the callback returns false, the process stops
* Returns list of registered packages.
*
* @param callable $callback
* @param string $class
* @return bool false if the process was interrupted, true otherwise
* @return array
*/
public function filterPackages($callback, $class = 'Composer\Package\Package');
public function getPackages();
/**
* Returns list of registered packages.
* Searches the repository for packages containing the query
*
* @return array
* @param string $query search query
* @param int $mode a set of SEARCH_* constants to search on, implementations should do a best effort only
* @return array[] an array of array('name' => '...', 'description' => '...')
*/
public function getPackages();
public function search($query, $mode = 0);
}

@ -42,7 +42,7 @@ class ComposerRepositoryTest extends TestCase
);
$repository
->expects($this->once())
->expects($this->exactly(2))
->method('loadRootServerFile')
->will($this->returnValue($repoPackages));
@ -50,7 +50,7 @@ class ComposerRepositoryTest extends TestCase
$stubPackage = $this->getPackage('stub/stub', '1.0.0');
$repository
->expects($this->at($at + 1))
->expects($this->at($at + 2))
->method('createPackage')
->with($this->identicalTo($arg), $this->equalTo('Composer\Package\CompletePackage'))
->will($this->returnValue($stubPackage));

Loading…
Cancel
Save