diff --git a/bin/composer b/bin/composer new file mode 100755 index 000000000..8d63a0b7c --- /dev/null +++ b/bin/composer @@ -0,0 +1,16 @@ +#!/bin/env php +addDownloader('git', new GitDownloader); +$composer->addInstaller('library', new LibraryInstaller); + +$cmd = new InstallCommand(); +$cmd->install($composer); \ No newline at end of file diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php new file mode 100644 index 000000000..d2faabe35 --- /dev/null +++ b/src/Composer/Command/InstallCommand.php @@ -0,0 +1,71 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +/** + * @author Jordi Boggiano + */ +class InstallCommand +{ + protected $composer; + + public function install($composer) + { + $this->composer = $composer; + + $config = $this->loadConfig(); + + // TODO this should just do dependency solving based on all repositories + $packages = array(); + foreach ($composer->getRepositories() as $repository) { + $packages = array_merge($packages, $repository->getPackages()); + } + + $lock = array(); + + // TODO this should use the transaction returned by the solver + foreach ($config['require'] as $name => $version) { + foreach ($packages as $pkg) { + if ($pkg->getName() === $name) { + $package = $pkg; + break; + } + } + if (!isset($package)) { + throw new \UnexpectedValueException('Could not find package '.$name.' in any of your repositories'); + } + $downloader = $composer->getDownloader($package->getSourceType()); + $installer = $composer->getInstaller($package->getType()); + $lock[$name] = $installer->install($package, $downloader); + } + + $this->storeLockFile($lock); + } + + protected function loadConfig() + { + if (!file_exists('composer.json')) { + throw new \UnexpectedValueException('composer.json config file not found in '.getcwd()); + } + $config = json_decode(file_get_contents('composer.json'), true); + if (!$config) { + throw new \UnexpectedValueException('Incorrect composer.json file'); + } + return $config; + } + + protected function storeLockFile(array $content) + { + file_put_contents('composer.lock', json_encode($content)); + } +} \ No newline at end of file diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php new file mode 100644 index 000000000..d74ebe6c4 --- /dev/null +++ b/src/Composer/Composer.php @@ -0,0 +1,89 @@ + + * 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\Repository\ComposerRepository; + +/** + * @author Jordi Boggiano + */ +class Composer +{ + protected $repositories = array(); + protected $downloaders = array(); + protected $installers = array(); + + public function __construct() + { + $this->addRepository('Packagist', array('composer' => 'http://packagist.org')); + } + + public function addDownloader($type, $downloader) + { + $this->downloaders[$type] = $downloader; + } + + public function getDownloader($type) + { + if (!isset($this->downloaders[$type])) { + throw new \UnexpectedValueException('Unknown source type: '.$type); + } + return $this->downloaders[$type]; + } + + public function addInstaller($type, $installer) + { + $this->installers[$type] = $installer; + } + + public function getInstaller($type) + { + if (!isset($this->installers[$type])) { + throw new \UnexpectedValueException('Unknown dependency type: '.$type); + } + return $this->installers[$type]; + } + + public function addRepository($name, $spec) + { + if (null === $spec) { + unset($this->repositories[$name]); + } + if (is_array($spec) && count($spec)) { + return $this->repositories[$name] = $this->createRepository(key($spec), current($spec)); + } + throw new \UnexpectedValueException('Invalid repositories specification '.var_export($spec, true)); + } + + public function getRepositories() + { + return $this->repositories; + } + + public function createRepository($type, $url) + { + $url = rtrim($url, '/'); + + switch ($type) { + case 'git-bare': + case 'git-package': + case 'git-multi': + throw new \Exception($type.' repositories not supported yet'); + break; + + case 'composer': + return new ComposerRepository($url); + break; + } + } +} \ No newline at end of file diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php new file mode 100644 index 000000000..76c1c6f9e --- /dev/null +++ b/src/Composer/Downloader/GitDownloader.php @@ -0,0 +1,48 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Downloader; + +use Composer\Package\PackageInterface; + +/** + * @author Jordi Boggiano + */ +class GitDownloader +{ + protected $clone; + + public function __construct($clone = true) + { + $this->clone = $clone; + } + + public function download(PackageInterface $package, $path) + { + $oldDir = getcwd(); + if (!is_dir($path)) { + if (file_exists($path)) { + throw new \UnexpectedValueException($path.' exists and is not a directory.'); + } + if (!mkdir($path, 0777, true)) { + throw new \UnexpectedValueException($path.' does not exist and could not be created.'); + } + } + chdir($path); + if ($this->clone) { + exec('git clone '.escapeshellarg($package->getSourceUrl()).' -b master '.escapeshellarg($package->getName())); + } else { + exec('git archive --format=tar --prefix='.escapeshellarg($package->getName()).' --remote='.escapeshellarg($package->getSourceUrl()).' master | tar -xf -'); + } + chdir($oldDir); + } +} \ No newline at end of file diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php new file mode 100644 index 000000000..9ea5c7f71 --- /dev/null +++ b/src/Composer/Installer/LibraryInstaller.php @@ -0,0 +1,34 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Installer; + +use Composer\Package\PackageInterface; + +/** + * @author Jordi Boggiano + */ +class LibraryInstaller +{ + protected $dir; + + public function __construct($dir = 'vendor') + { + $this->dir = $dir; + } + + public function install(PackageInterface $package, $downloader) + { + $downloader->download($package, $this->dir); + return array('version' => $package->getVersion()); + } +} \ No newline at end of file diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php new file mode 100644 index 000000000..c4ca9189a --- /dev/null +++ b/src/Composer/Repository/ComposerRepository.php @@ -0,0 +1,89 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Repository; + +use Composer\Package\MemoryPackage; + +/** + * @author Jordi Boggiano + */ +class ComposerRepository extends ArrayRepository +{ + protected $packages; + + public function __construct($url) + { + $this->url = $url; + } + + protected function initialize() + { + parent::initialize(); + $packages = @json_decode(file_get_contents($this->url.'/packages.json'), true); + if (!$packages) { + throw new \UnexpectedValueException('Could not parse package list from the '.$this->url.' registry'); + } + + foreach ($packages as $data) { + $this->createPackages($data); + } + } + + protected function createPackages($data) + { + foreach ($data['versions'] as $rev) { + $version = $this->parseVersion($rev['version']); + + $package = new MemoryPackage($rev['name'], $version['version'], $version['type']); + $package->setSourceType($rev['source']['type']); + $package->setSourceUrl($rev['source']['url']); + + if (isset($rev['license'])) { + $package->setLicense($rev['license']); + } + //if (isset($rev['require'])) { + // $package->setRequires($rev['require']); + //} + //if (isset($rev['conflict'])) { + // $package->setConflicts($rev['conflict']); + //} + //if (isset($rev['provide'])) { + // $package->setProvides($rev['provide']); + //} + //if (isset($rev['replace'])) { + // $package->setReplaces($rev['replace']); + //} + //if (isset($rev['recommend'])) { + // $package->setRecommends($rev['recommend']); + //} + //if (isset($rev['suggest'])) { + // $package->setSuggests($rev['suggest']); + //} + $this->addPackage($package); + } + } + + protected function parseVersion($version) + { + if (!preg_match('#^v?(\d+)(\.\d+)?(\.\d+)?-?(beta|RC\d+|alpha|dev)?$#i', $version, $matches)) { + throw new \UnexpectedValueException('Invalid version string '.$version); + } + + return array( + 'version' => $matches[1] + .(!empty($matches[2]) ? $matches[2] : '.0') + .(!empty($matches[3]) ? $matches[3] : '.0'), + 'type' => strtolower(!empty($matches[4]) ? $matches[4] : 'stable'), + ); + } +} \ No newline at end of file