diff --git a/README.md b/README.md index f0693158b..ebdcf2479 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Global installation of composer (via homebrew) Installing via this homebrew formula will always get you the latest composer version. -1. run `brew uninstall composer ; brew install https://raw.github.com/gist/1574469/composer.rb` +1. run `brew uninstall composer ; brew install --HEAD https://raw.github.com/gist/1574469/composer.rb` 2. Change into a project directory `cd /path/to/my/project` 3. Use composer as you normally would `composer.phar install` diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7ba5d7997..246d305e4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,6 +20,9 @@ ./src/Composer/ + + ./src/Composer/Autoload/ClassLoader.php + diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index aa720adaa..f695487ff 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -16,10 +16,12 @@ use Composer\Composer; use Composer\Package\PackageInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * @author Justin Rainbow + * @author Jordi Boggiano */ class DependsCommand extends Command { @@ -27,13 +29,14 @@ class DependsCommand extends Command { $this ->setName('depends') - ->setDescription('Where is a package used?') + ->setDescription('Shows which packages depend on the given package') ->setDefinition(array( - new InputArgument('package', InputArgument::REQUIRED, 'the package to inspect') + new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'), + new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', array('requires', 'recommends', 'suggests')) )) ->setHelp(<<php composer.phar depends composer/composer EOT @@ -46,7 +49,11 @@ EOT $composer = $this->getComposer(); $references = $this->getReferences($input, $output, $composer); - $this->printReferences($input, $output, $references); + if ($input->getOption('verbose')) { + $this->printReferences($input, $output, $references); + } else { + $this->printPackages($input, $output, $references); + } } /** @@ -63,18 +70,20 @@ EOT $needle = $input->getArgument('package'); $references = array(); + $verbose = (Boolean) $input->getOption('verbose'); - // check if we have a local installation so we can grab the right package/version - $repos = array_merge( - array($composer->getRepositoryManager()->getLocalRepository()), - $composer->getRepositoryManager()->getRepositories() - ); + $repos = $composer->getRepositoryManager()->getRepositories(); + $types = $input->getOption('link-type'); foreach ($repos as $repository) { foreach ($repository->getPackages() as $package) { - foreach (array('requires', 'recommends', 'suggests') as $type) { + foreach ($types as $type) { foreach ($package->{'get'.$type}() as $link) { if ($link->getTarget() === $needle) { - $references[] = array($type, $package, $link); + if ($verbose) { + $references[] = array($type, $package, $link); + } else { + $references[$package->getName()] = $package->getPrettyName(); + } } } } @@ -90,4 +99,12 @@ EOT $output->writeln($ref[1]->getPrettyName() . ' ' . $ref[1]->getPrettyVersion() . ' ' . $ref[0] . ' ' . $ref[2]->getPrettyConstraint()); } } + + private function printPackages(InputInterface $input, OutputInterface $output, array $packages) + { + ksort($packages); + foreach ($packages as $package) { + $output->writeln($package); + } + } } \ No newline at end of file diff --git a/src/Composer/Command/ShowCommand.php b/src/Composer/Command/ShowCommand.php index 224053c8e..334cd8997 100644 --- a/src/Composer/Command/ShowCommand.php +++ b/src/Composer/Command/ShowCommand.php @@ -85,11 +85,9 @@ EOT $composer->getRepositoryManager()->getRepositories() ); foreach ($repos as $repository) { - foreach ($repository->getPackages() as $package) { - if ($package->getName() === $input->getArgument('package')) { - if (null === $highestVersion || version_compare($package->getVersion(), $highestVersion->getVersion(), '>=')) { - $highestVersion = $package; - } + foreach ($repository->findPackagesByName($input->getArgument('package')) as $package) { + if (null === $highestVersion || version_compare($package->getVersion(), $highestVersion->getVersion(), '>=')) { + $highestVersion = $package; } } } @@ -135,10 +133,8 @@ EOT $versions = array(); foreach ($composer->getRepositoryManager()->getRepositories() as $repository) { - foreach ($repository->getPackages() as $version) { - if ($version->getName() === $package->getName()) { - $versions[] = $version->getPrettyVersion(); - } + foreach ($repository->findPackagesByName($package->getName()) as $version) { + $versions[] = $version->getPrettyVersion(); } } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 60df563bf..affd7f1e1 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -21,12 +21,8 @@ use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Finder\Finder; use Composer\Command; use Composer\Composer; -use Composer\Installer; -use Composer\Downloader; -use Composer\Repository; -use Composer\Package; -use Composer\Json\JsonFile; -use Composer\IO\IOInterface; +use Composer\Factory; +use Composer\IO\IOInterface; use Composer\IO\ConsoleIO; /** @@ -78,7 +74,12 @@ class Application extends BaseApplication public function getComposer() { if (null === $this->composer) { - $this->composer = self::bootstrapComposer($this->io); + try { + $this->composer = Factory::create($this->io); + } catch (\InvalidArgumentException $e) { + $this->io->writeln($e->getMessage()); + exit(1); + } } return $this->composer; @@ -92,93 +93,6 @@ class Application extends BaseApplication return $this->io; } - /** - * Bootstraps a Composer instance - * - * @return Composer - */ - public static function bootstrapComposer(IOInterface $io, $composerFile = null) - { - // load Composer configuration - if (null === $composerFile) { - $composerFile = getenv('COMPOSER') ?: 'composer.json'; - } - - $file = new JsonFile($composerFile); - if (!$file->exists()) { - if ($composerFile === 'composer.json') { - $this->io->writeln('Composer could not find a composer.json file in '.getcwd()); - } else { - $this->io->writeln('Composer could not find the config file: '.$composerFile); - } - $this->io->writeln('To initialize a project, please create a composer.json file as described on the http://packagist.org/ "Getting Started" section'); - exit(1); - } - - // Configuration defaults - $composerConfig = array( - 'vendor-dir' => 'vendor', - ); - - $packageConfig = $file->read(); - - if (isset($packageConfig['config']) && is_array($packageConfig['config'])) { - $packageConfig['config'] = array_merge($composerConfig, $packageConfig['config']); - } else { - $packageConfig['config'] = $composerConfig; - } - - $vendorDir = getenv('COMPOSER_VENDOR_DIR') ?: $packageConfig['config']['vendor-dir']; - if (!isset($packageConfig['config']['bin-dir'])) { - $packageConfig['config']['bin-dir'] = $vendorDir.'/bin'; - } - $binDir = getenv('COMPOSER_BIN_DIR') ?: $packageConfig['config']['bin-dir']; - - // initialize repository manager - $rm = new Repository\RepositoryManager($io); - $rm->setLocalRepository(new Repository\FilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json'))); - $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); - $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); - $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); - $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); - - // initialize download manager - $dm = new Downloader\DownloadManager(); - $dm->setDownloader('git', new Downloader\GitDownloader()); - $dm->setDownloader('svn', new Downloader\SvnDownloader()); - $dm->setDownloader('hg', new Downloader\HgDownloader()); - $dm->setDownloader('pear', new Downloader\PearDownloader($io)); - $dm->setDownloader('zip', new Downloader\ZipDownloader($io)); - - // initialize installation manager - $im = new Installer\InstallationManager($vendorDir); - $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, null)); - $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, $im)); - - // load package - $loader = new Package\Loader\RootPackageLoader($rm); - $package = $loader->load($packageConfig); - - // load default repository unless it's explicitly disabled - if (!isset($packageConfig['repositories']['packagist']) || $packageConfig['repositories']['packagist'] !== false) { - $rm->addRepository(new Repository\ComposerRepository(array('url' => 'http://packagist.org'))); - } - - // init locker - $lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; - $locker = new Package\Locker(new JsonFile($lockFile), $rm, md5_file($composerFile)); - - // initialize composer - $composer = new Composer(); - $composer->setPackage($package); - $composer->setLocker($locker); - $composer->setRepositoryManager($rm); - $composer->setDownloadManager($dm); - $composer->setInstallationManager($im); - - return $composer; - } - /** * Initializes all the composer commands */ diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php new file mode 100644 index 000000000..453fdcc63 --- /dev/null +++ b/src/Composer/Factory.php @@ -0,0 +1,141 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Json\JsonFile; +use Composer\IO\IOInterface; + +/** + * Creates an configured instance of composer. + * + * @author Ryan Weaver + * @author Jordi Boggiano + * @author Igor Wiedler + */ +class Factory +{ + /** + * Creates a Composer instance + * + * @return Composer + */ + public function createComposer(IOInterface $io, $composerFile = null) + { + // load Composer configuration + if (null === $composerFile) { + $composerFile = getenv('COMPOSER') ?: 'composer.json'; + } + + $file = new JsonFile($composerFile); + if (!$file->exists()) { + if ($composerFile === 'composer.json') { + $message = 'Composer could not find a composer.json file in '.getcwd(); + } else { + $message = 'Composer could not find the config file: '.$composerFile; + } + $instructions = 'To initialize a project, please create a composer.json file as described on the http://packagist.org/ "Getting Started" section'; + throw new \InvalidArgumentException($message.PHP_EOL.$instructions); + } + + // Configuration defaults + $composerConfig = array( + 'vendor-dir' => 'vendor', + ); + + $packageConfig = $file->read(); + + if (isset($packageConfig['config']) && is_array($packageConfig['config'])) { + $packageConfig['config'] = array_merge($composerConfig, $packageConfig['config']); + } else { + $packageConfig['config'] = $composerConfig; + } + + $vendorDir = getenv('COMPOSER_VENDOR_DIR') ?: $packageConfig['config']['vendor-dir']; + if (!isset($packageConfig['config']['bin-dir'])) { + $packageConfig['config']['bin-dir'] = $vendorDir.'/bin'; + } + $binDir = getenv('COMPOSER_BIN_DIR') ?: $packageConfig['config']['bin-dir']; + + // initialize repository manager + $rm = $this->createRepositoryManager($io, $vendorDir); + + // initialize download manager + $dm = $this->createDownloadManager($io); + + // initialize installation manager + $im = $this->createInstallationManager($rm, $dm, $vendorDir, $binDir, $io); + + // load package + $loader = new Package\Loader\RootPackageLoader($rm); + $package = $loader->load($packageConfig); + + // load default repository unless it's explicitly disabled + if (!isset($packageConfig['repositories']['packagist']) || $packageConfig['repositories']['packagist'] !== false) { + $rm->addRepository(new Repository\ComposerRepository(array('url' => 'http://packagist.org'))); + } + + // init locker + $lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; + $locker = new Package\Locker(new JsonFile($lockFile), $rm, md5_file($composerFile)); + + // initialize composer + $composer = new Composer(); + $composer->setPackage($package); + $composer->setLocker($locker); + $composer->setRepositoryManager($rm); + $composer->setDownloadManager($dm); + $composer->setInstallationManager($im); + + return $composer; + } + + protected function createRepositoryManager(IOInterface $io, $vendorDir) + { + $rm = new Repository\RepositoryManager($io); + $rm->setLocalRepository(new Repository\FilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json'))); + $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); + $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); + $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); + $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); + + return $rm; + } + + protected function createDownloadManager(IOInterface $io) + { + $dm = new Downloader\DownloadManager(); + $dm->setDownloader('git', new Downloader\GitDownloader()); + $dm->setDownloader('svn', new Downloader\SvnDownloader()); + $dm->setDownloader('hg', new Downloader\HgDownloader()); + $dm->setDownloader('pear', new Downloader\PearDownloader($io)); + $dm->setDownloader('zip', new Downloader\ZipDownloader($io)); + + return $dm; + } + + protected function createInstallationManager(Repository\RepositoryManager $rm, Downloader\DownloadManager $dm, $vendorDir, $binDir, IOInterface $io) + { + $im = new Installer\InstallationManager($vendorDir); + $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, null)); + $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, $im)); + + return $im; + } + + static public function create(IOInterface $io, $composerFile = null) + { + $factory = new static(); + + return $factory->createComposer($io, $composerFile); + } +} diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 22aa4d265..03c85250e 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -15,11 +15,6 @@ namespace Composer\Json; use Composer\Repository\RepositoryManager; use Composer\Composer; -// defined as of PHP 5.4 -if (!defined('JSON_PRETTY_PRINT')) { - define('JSON_PRETTY_PRINT', 128); -} - /** * Reads/writes json files. * @@ -80,8 +75,9 @@ class JsonFile * Writes json file. * * @param array $hash writes hash into json file + * @param Boolean $prettyPrint If true, output is pretty-printed */ - public function write(array $hash) + public function write(array $hash, $prettyPrint = true) { $dir = dirname($this->path); if (!is_dir($dir)) { @@ -96,7 +92,86 @@ class JsonFile ); } } - file_put_contents($this->path, json_encode($hash, JSON_PRETTY_PRINT)); + file_put_contents($this->path, $this->encode($hash, $prettyPrint)); + } + + /** + * Encodes an array into (optionally pretty-printed) JSON + * + * Original code for this function can be found at: + * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ + * + * @param array $hash Data to encode into a formatted JSON string + * @param Boolean $prettyPrint If true, output is pretty-printed + * @return string Indented version of the original JSON string + */ + public function encode(array $hash, $prettyPrint = true) + { + if ($prettyPrint && defined('JSON_PRETTY_PRINT')) { + return json_encode($hash, JSON_PRETTY_PRINT); + } + + $json = json_encode($hash); + + if (!$prettyPrint) { + return $json; + } + + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = ' '; + $newLine = "\n"; + $prevChar = ''; + $outOfQuotes = true; + + for ($i = 0; $i <= $strLen; $i++) { + // Grab the next character in the string + $char = substr($json, $i, 1); + + // Are we inside a quoted string? + if ('"' === $char && '\\' !== $prevChar) { + $outOfQuotes = !$outOfQuotes; + } elseif (':' === $char && $outOfQuotes) { + // Add a space after the : character + $char .= ' '; + } elseif (('}' === $char || ']' === $char) && $outOfQuotes) { + $pos--; + + if ('{' !== $prevChar && '[' !== $prevChar) { + // If this character is the end of an element, + // output a new line and indent the next line + $result .= $newLine; + for ($j = 0; $j < $pos; $j++) { + $result .= $indentStr; + } + } else { + // Collapse empty {} and [] + $result = rtrim($result); + } + } + + // Add the character to the result string + $result .= $char; + + // If the last character was the beginning of an element, + // output a new line and indent the next line + if ((',' === $char || '{' === $char || '[' === $char) && $outOfQuotes) { + $result .= $newLine; + + if ('{' === $char || '[' === $char) { + $pos++; + } + + for ($j = 0; $j < $pos; $j++) { + $result .= $indentStr; + } + } + + $prevChar = $char; + } + + return $result; } /** diff --git a/src/Composer/Repository/ArrayRepository.php b/src/Composer/Repository/ArrayRepository.php index 06910bbe3..36ed56b0b 100644 --- a/src/Composer/Repository/ArrayRepository.php +++ b/src/Composer/Repository/ArrayRepository.php @@ -41,6 +41,24 @@ class ArrayRepository implements RepositoryInterface } } + /** + * {@inheritDoc} + */ + public function findPackagesByName($name) + { + // normalize name + $name = strtolower($name); + $packages = array(); + + foreach ($this->getPackages() as $package) { + if ($package->getName() === $name) { + $packages[] = $package; + } + } + + return $packages; + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Repository/RepositoryInterface.php b/src/Composer/Repository/RepositoryInterface.php index a94ecea4c..2c1f7d43c 100644 --- a/src/Composer/Repository/RepositoryInterface.php +++ b/src/Composer/Repository/RepositoryInterface.php @@ -41,6 +41,15 @@ interface RepositoryInterface extends \Countable */ function findPackage($name, $version); + /** + * Searches for packages by it's name. + * + * @param string $name package name + * + * @return array + */ + function findPackagesByName($name); + /** * Returns list of registered packages. * diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index c2ed0f2bf..7496834e6 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -115,6 +115,25 @@ class SolverTest extends TestCase $this->checkSolverResult(array()); } + public function testSolverUpdateDoesOnlyUpdate() + { + $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1')); + $this->reposComplete(); + + $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0.0.0'), 'requires'))); + + $this->request->install('A', new VersionConstraint('=', '1.0.0.0')); + $this->request->install('B', new VersionConstraint('=', '1.1.0.0')); + $this->request->update('A', new VersionConstraint('=', '1.0.0.0')); + $this->request->update('B', new VersionConstraint('=', '1.0.0.0')); + + $this->checkSolverResult(array( + array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB), + )); + } + public function testSolverUpdateSingle() { $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 93f7ecef7..75e798388 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -84,6 +84,15 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $this->expectParseException('missing comma on line 2, char 21', $json); } + public function testSimpleJsonString() + { + $data = array('name' => 'composer/composer'); + $json = '{ + "name": "composer\/composer" +}'; + $this->assertJsonFormat($json, $data); + } + private function expectParseException($text, $json) { try { @@ -93,4 +102,12 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $this->assertContains($text, $e->getMessage()); } } + + private function assertJsonFormat($json, $data) + { + $file = new JsonFile('composer.json'); + + $this->assertEquals($json, $file->encode($data)); + } + } diff --git a/tests/Composer/Test/Repository/ArrayRepositoryTest.php b/tests/Composer/Test/Repository/ArrayRepositoryTest.php index def78e36e..5e29f5ed6 100644 --- a/tests/Composer/Test/Repository/ArrayRepositoryTest.php +++ b/tests/Composer/Test/Repository/ArrayRepositoryTest.php @@ -50,4 +50,20 @@ class ArrayRepositoryTest extends TestCase $this->assertTrue($repo->hasPackage($this->getPackage('foo', '1'))); $this->assertFalse($repo->hasPackage($this->getPackage('bar', '1'))); } + + public function testFindPackagesByName() + { + $repo = new ArrayRepository(); + $repo->addPackage($this->getPackage('foo', '1')); + $repo->addPackage($this->getPackage('bar', '2')); + $repo->addPackage($this->getPackage('bar', '3')); + + $foo = $repo->findPackagesByName('foo'); + $this->assertCount(1, $foo); + $this->assertEquals('foo', $foo[0]->getName()); + + $bar = $repo->findPackagesByName('bar'); + $this->assertCount(2, $bar); + $this->assertEquals('bar', $bar[0]->getName()); + } }