From de7f666118df3f9b4ed6aebd798ed5ae8a186081 Mon Sep 17 00:00:00 2001 From: Leszek Prabucki Date: Sun, 22 Jan 2012 22:06:09 +0100 Subject: [PATCH 01/62] Added and extended some unit tests --- .../Test/DependencyResolver/PoolTest.php | 61 +++++ .../RuleSetIteratorTest.php | 19 ++ .../Test/DependencyResolver/RuleSetTest.php | 139 ++++++++++- .../Test/DependencyResolver/RuleTest.php | 170 ++++++++++++++ .../Test/Downloader/GitDownloaderTest.php | 111 +++++++++ .../Test/Downloader/HgDownloaderTest.php | 111 +++++++++ tests/Composer/Test/IO/ConsoleIOTest.php | 219 ++++++++++++++++++ 7 files changed, 829 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/DependencyResolver/RuleTest.php create mode 100644 tests/Composer/Test/Downloader/GitDownloaderTest.php create mode 100644 tests/Composer/Test/Downloader/HgDownloaderTest.php create mode 100644 tests/Composer/Test/IO/ConsoleIOTest.php diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index c4dcb0bcc..643be0428 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -30,4 +30,65 @@ class PoolTest extends TestCase $this->assertEquals(array($package), $pool->whatProvides('foo')); $this->assertEquals(array($package), $pool->whatProvides('foo')); } + + /** + * @expectedException \RuntimeException + */ + public function testGetPriorityForNotRegisteredRepository() + { + $pool = new Pool; + $repository = new ArrayRepository; + + $pool->getPriority($repository); + } + + public function testGetPriorityWhenRepositoryIsRegistered() + { + $pool = new Pool; + $firstRepository = new ArrayRepository; + $pool->addRepository($firstRepository); + $secondRepository = new ArrayRepository; + $pool->addRepository($secondRepository); + + $firstPriority = $pool->getPriority($firstRepository); + $secondPriority = $pool->getPriority($secondRepository); + + $this->assertEquals(0, $firstPriority); + $this->assertEquals(1, $secondPriority); + } + + public function testPackageById() + { + $pool = new Pool; + $repository = new ArrayRepository; + $package = $this->getPackage('foo', '1'); + + $repository->addPackage($package); + $pool->addRepository($repository); + + $this->assertSame($package, $pool->packageById(1)); + } + + public function testWhatProvidesWhenPackageCannotBeFound() + { + $pool = new Pool; + + $this->assertEquals(array(), $pool->whatProvides('foo')); + } + + public function testGetMaxId() + { + $pool = new Pool; + $repository = new ArrayRepository; + $firstPackage = $this->getPackage('foo', '1'); + $secondPackage = $this->getPackage('foo1', '1'); + + $this->assertEquals(0, $pool->getMaxId()); + + $repository->addPackage($firstPackage); + $repository->addPackage($secondPackage); + $pool->addRepository($repository); + + $this->assertEquals(2, $pool->getMaxId()); + } } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php index 2daa64eb6..d45f9a561 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php @@ -52,4 +52,23 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expected, $result); } + + public function testKeys() + { + $ruleSetIterator = new RuleSetIterator($this->rules); + + $result = array(); + foreach ($ruleSetIterator as $key => $rule) + { + $result[] = $key; + } + + $expected = array( + RuleSet::TYPE_JOB, + RuleSet::TYPE_JOB, + RuleSet::TYPE_UPDATE, + ); + + $this->assertEquals($expected, $result); + } } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index fa42d4522..be37b8795 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -14,8 +14,10 @@ namespace Composer\Test\DependencyResolver; use Composer\DependencyResolver\Rule; use Composer\DependencyResolver\RuleSet; +use Composer\DependencyResolver\Literal; +use Composer\Test\TestCase; -class RuleSetTest extends \PHPUnit_Framework_TestCase +class RuleSetTest extends TestCase { public function testAdd() { @@ -41,4 +43,139 @@ class RuleSetTest extends \PHPUnit_Framework_TestCase $this->assertEquals($rules, $ruleSet->getRules()); } + + /** + * @expectedException \OutOfBoundsException + */ + public function testAddWhenTypeIsNotRecognized() + { + $ruleSet = new RuleSet; + + $ruleSet->add(new Rule(array(), 'job1', null), 7); + } + + public function testAddWhenTypeIsUnknow() + { + $ruleSet = new RuleSet; + + $rule = new Rule(array(), 'job1', null); + $ruleSet->add($rule, -1); + + $rules = $ruleSet->getRules(); + $this->assertSame($rule, $rules[-1][0]); + } + + public function testCount() + { + $ruleSet = new RuleSet; + + $ruleSet->add(new Rule(array(), 'job1', null), RuleSet::TYPE_JOB); + $ruleSet->add(new Rule(array(), 'job2', null), RuleSet::TYPE_JOB); + + $this->assertEquals(2, $ruleSet->count()); + } + + public function testRuleById() + { + $ruleSet = new RuleSet; + + $rule = new Rule(array(), 'job1', null); + $ruleSet->add($rule, RuleSet::TYPE_JOB); + + $this->assertSame($rule, $ruleSet->ruleById(0)); + } + + public function testGetIterator() + { + $ruleSet = new RuleSet; + + $rule1 = new Rule(array(), 'job1', null); + $rule2 = new Rule(array(), 'job1', null); + $ruleSet->add($rule1, RuleSet::TYPE_JOB); + $ruleSet->add($rule2, RuleSet::TYPE_UPDATE); + + $iterator = $ruleSet->getIterator(); + + $this->assertSame($rule1, $iterator->current()); + $iterator->next(); + $this->assertSame($rule2, $iterator->current()); + } + + public function testGetIteratorFor() + { + $ruleSet = new RuleSet; + $rule1 = new Rule(array(), 'job1', null); + $rule2 = new Rule(array(), 'job1', null); + + $ruleSet->add($rule1, RuleSet::TYPE_JOB); + $ruleSet->add($rule2, RuleSet::TYPE_UPDATE); + + $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_UPDATE); + + $this->assertSame($rule2, $iterator->current()); + } + + public function testGetIteratorWithout() + { + $ruleSet = new RuleSet; + $rule1 = new Rule(array(), 'job1', null); + $rule2 = new Rule(array(), 'job1', null); + + $ruleSet->add($rule1, RuleSet::TYPE_JOB); + $ruleSet->add($rule2, RuleSet::TYPE_UPDATE); + + $iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_JOB); + + $this->assertSame($rule2, $iterator->current()); + } + + public function testContainsEqual() + { + $ruleSet = new RuleSet; + + $rule = $this->getRuleMock(); + $rule->expects($this->any()) + ->method('getHash') + ->will($this->returnValue('rule_1_hash')); + $rule->expects($this->any()) + ->method('equals') + ->will($this->returnValue(true)); + + $rule2 = $this->getRuleMock(); + $rule2->expects($this->any()) + ->method('getHash') + ->will($this->returnValue('rule_2_hash')); + + $rule3 = $this->getRuleMock(); + $rule3->expects($this->any()) + ->method('getHash') + ->will($this->returnValue('rule_1_hash')); + $rule3->expects($this->any()) + ->method('equal') + ->will($this->returnValue(false)); + + $ruleSet->add($rule, RuleSet::TYPE_UPDATE); + + $this->assertTrue($ruleSet->containsEqual($rule)); + $this->assertFalse($ruleSet->containsEqual($rule2)); + $this->assertFalse($ruleSet->containsEqual($rule3)); + } + + public function testToString() + { + $ruleSet = new RuleSet; + $literal = new Literal($this->getPackage('foo', '2.1'), true); + $rule = new Rule(array($literal), 'job1', null); + + $ruleSet->add($rule, RuleSet::TYPE_UPDATE); + + $this->assertContains('UPDATE : (+foo-2.1.0.0)', $ruleSet->__toString()); + } + + private function getRuleMock() + { + return $this->getMockBuilder('Composer\DependencyResolver\Rule') + ->disableOriginalConstructor() + ->getMock(); + } } diff --git a/tests/Composer/Test/DependencyResolver/RuleTest.php b/tests/Composer/Test/DependencyResolver/RuleTest.php new file mode 100644 index 000000000..f76f46eb1 --- /dev/null +++ b/tests/Composer/Test/DependencyResolver/RuleTest.php @@ -0,0 +1,170 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\DependencyResolver; + +use Composer\DependencyResolver\Rule; +use Composer\DependencyResolver\Literal; +use Composer\Test\TestCase; + +class RuleTest extends TestCase +{ + public function testGetHash() + { + $rule = new Rule(array(), 'job1', null); + $rule->ruleHash = '123'; + + $this->assertEquals('123', $rule->getHash()); + } + + public function testSetAndGetId() + { + $rule = new Rule(array(), 'job1', null); + $rule->setId(666); + + $this->assertEquals(666, $rule->getId()); + } + + public function testEqualsForRulesWithDifferentHashes() + { + $rule = new Rule(array(), 'job1', null); + $rule->ruleHash = '123'; + + $rule2 = new Rule(array(), 'job1', null); + $rule2->ruleHash = '321'; + + $this->assertFalse($rule->equals($rule2)); + } + + public function testEqualsForRulesWithDifferentLiterals() + { + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(1)); + $rule = new Rule(array($literal), 'job1', null); + $rule->ruleHash = '123'; + + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(12)); + $rule2 = new Rule(array($literal), 'job1', null); + $rule2->ruleHash = '123'; + + $this->assertFalse($rule->equals($rule2)); + } + + public function testEqualsForRulesWithDifferLiteralsQuantity() + { + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(1)); + $literal2 = $this->getLiteralMock(); + $literal2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(12)); + + $rule = new Rule(array($literal, $literal2), 'job1', null); + $rule->ruleHash = '123'; + $rule2 = new Rule(array($literal), 'job1', null); + $rule2->ruleHash = '123'; + + $this->assertFalse($rule->equals($rule2)); + } + + public function testEqualsForRulesWithThisSameLiterals() + { + $literal = $this->getLiteralMock(); + $literal->expects($this->any()) + ->method('getId') + ->will($this->returnValue(1)); + $literal2 = $this->getLiteralMock(); + $literal2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(12)); + + $rule = new Rule(array($literal, $literal2), 'job1', null); + $rule2 = new Rule(array($literal, $literal2), 'job1', null); + + $this->assertTrue($rule->equals($rule2)); + } + + public function testSetAndGetType() + { + $rule = new Rule(array(), 'job1', null); + $rule->setType('someType'); + + $this->assertEquals('someType', $rule->getType()); + } + + public function testEnable() + { + $rule = new Rule(array(), 'job1', null); + $rule->disable(); + $rule->enable(); + + $this->assertTrue($rule->isEnabled()); + $this->assertFalse($rule->isDisabled()); + } + + public function testDisable() + { + $rule = new Rule(array(), 'job1', null); + $rule->enable(); + $rule->disable(); + + $this->assertTrue($rule->isDisabled()); + $this->assertFalse($rule->isEnabled()); + } + + public function testSetWeak() + { + $rule = new Rule(array(), 'job1', null); + $rule->setWeak(true); + + $rule2 = new Rule(array(), 'job1', null); + $rule2->setWeak(false); + + $this->assertTrue($rule->isWeak()); + $this->assertFalse($rule2->isWeak()); + } + + public function testIsAssertions() + { + $literal = $this->getLiteralMock(); + $literal2 = $this->getLiteralMock(); + $rule = new Rule(array($literal, $literal2), 'job1', null); + $rule2 = new Rule(array($literal), 'job1', null); + + $this->assertFalse($rule->isAssertion()); + $this->assertTrue($rule2->isAssertion()); + } + + public function testToString() + { + $literal = new Literal($this->getPackage('foo', '2.1'), true); + $literal2 = new Literal($this->getPackage('baz', '1.1'), false); + + $rule = new Rule(array($literal, $literal2), 'job1', null); + + $this->assertEquals('(-baz-1.1.0.0|+foo-2.1.0.0)', $rule->__toString()); + } + + private function getLiteralMock() + { + return $this->getMockBuilder('Composer\DependencyResolver\Literal') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php new file mode 100644 index 000000000..57cbd945d --- /dev/null +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -0,0 +1,111 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Downloader; + +use Composer\Downloader\GitDownloader; + +class GitDownloaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testDownloadForPackageWithoutSourceReference() + { + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new GitDownloader(); + $downloader->download($packageMock, '/path'); + } + + public function testDownload() + { + $expectedGitCommand = 'git clone \'https://github.com/l3l0/composer\' composerPath && cd composerPath && git checkout \'ref\' && git reset --hard \'ref\''; + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->once()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->once()) + ->method('execute') + ->with($this->equalTo($expectedGitCommand)); + + $downloader = new GitDownloader($processExecutor); + $downloader->download($packageMock, 'composerPath'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUpdateforPackageWithoutSourceReference() + { + $initialPackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new GitDownloader(); + $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); + } + + public function testUpdate() + { + $expectedGitUpdateCommand = 'cd composerPath && git fetch && git checkout ref && git reset --hard ref'; + $expectedGitResetCommand = 'cd composerPath && git status --porcelain'; + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->any()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->at(0)) + ->method('execute') + ->with($this->equalTo($expectedGitResetCommand)); + $processExecutor->expects($this->at(1)) + ->method('execute') + ->with($this->equalTo($expectedGitUpdateCommand)); + + $downloader = new GitDownloader($processExecutor); + $downloader->update($packageMock, $packageMock, 'composerPath'); + } + + public function testRemove() + { + $expectedGitResetCommand = 'cd composerPath && git status --porcelain'; + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->any()) + ->method('execute') + ->with($this->equalTo($expectedGitResetCommand)); + + $downloader = new GitDownloader($processExecutor); + $downloader->remove($packageMock, 'composerPath'); + } + + public function testGetInstallationSource() + { + $downloader = new GitDownloader(); + + $this->assertEquals('source', $downloader->getInstallationSource()); + } +} diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php new file mode 100644 index 000000000..d6752eb4a --- /dev/null +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -0,0 +1,111 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Downloader; + +use Composer\Downloader\HgDownloader; + +class HgDownloaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testDownloadForPackageWithoutSourceReference() + { + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new HgDownloader(); + $downloader->download($packageMock, '/path'); + } + + public function testDownload() + { + $expectedGitCommand = '(hg clone \'https://mercurial.dev/l3l0/composer\' composerPath 2> /dev/null) && cd composerPath && hg up \'ref\''; + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->once()) + ->method('getSourceUrl') + ->will($this->returnValue('https://mercurial.dev/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->once()) + ->method('execute') + ->with($this->equalTo($expectedGitCommand)); + + $downloader = new HgDownloader($processExecutor); + $downloader->download($packageMock, 'composerPath'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUpdateforPackageWithoutSourceReference() + { + $initialPackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock = $this->getMock('Composer\Package\PackageInterface'); + $sourcePackageMock->expects($this->once()) + ->method('getSourceReference') + ->will($this->returnValue(null)); + + $downloader = new HgDownloader(); + $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); + } + + public function testUpdate() + { + $expectedGitUpdateCommand = 'cd composerPath && hg pull && hg up \'ref\''; + $expectedGitResetCommand = 'cd composerPath && hg st'; + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $packageMock->expects($this->any()) + ->method('getSourceReference') + ->will($this->returnValue('ref')); + $packageMock->expects($this->any()) + ->method('getSourceUrl') + ->will($this->returnValue('https://github.com/l3l0/composer')); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->at(0)) + ->method('execute') + ->with($this->equalTo($expectedGitResetCommand)); + $processExecutor->expects($this->at(1)) + ->method('execute') + ->with($this->equalTo($expectedGitUpdateCommand)); + + $downloader = new HgDownloader($processExecutor); + $downloader->update($packageMock, $packageMock, 'composerPath'); + } + + public function testRemove() + { + $expectedGitResetCommand = 'cd composerPath && hg st'; + + $packageMock = $this->getMock('Composer\Package\PackageInterface'); + $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + $processExecutor->expects($this->any()) + ->method('execute') + ->with($this->equalTo($expectedGitResetCommand)); + + $downloader = new HgDownloader($processExecutor); + $downloader->remove($packageMock, 'composerPath'); + } + + public function testGetInstallationSource() + { + $downloader = new HgDownloader(); + + $this->assertEquals('source', $downloader->getInstallationSource()); + } +} diff --git a/tests/Composer/Test/IO/ConsoleIOTest.php b/tests/Composer/Test/IO/ConsoleIOTest.php new file mode 100644 index 000000000..78450eb53 --- /dev/null +++ b/tests/Composer/Test/IO/ConsoleIOTest.php @@ -0,0 +1,219 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\IO; + +use Composer\IO\ConsoleIO; +use Composer\Test\TestCase; + +class ConsoleIOTest extends TestCase +{ + public function testIsInteractive() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $inputMock->expects($this->at(0)) + ->method('isInteractive') + ->will($this->returnValue(true)); + $inputMock->expects($this->at(1)) + ->method('isInteractive') + ->will($this->returnValue(false)); + + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + + $this->assertTrue($consoleIO->isInteractive()); + $this->assertFalse($consoleIO->isInteractive()); + } + + public function testWrite() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $outputMock->expects($this->once()) + ->method('write') + ->with($this->equalTo('some information about something'), $this->equalTo(false)); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->write('some information about something', false); + } + + public function testOverwrite() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $outputMock->expects($this->at(0)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(19)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(20)) + ->method('write') + ->with($this->equalTo('some information'), $this->equalTo(false)); + $outputMock->expects($this->at(21)) + ->method('write') + ->with($this->equalTo(' '), $this->equalTo(false)); + $outputMock->expects($this->at(24)) + ->method('write') + ->with($this->equalTo(' '), $this->equalTo(false)); + $outputMock->expects($this->at(25)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(28)) + ->method('write') + ->with($this->equalTo("\x08"), $this->equalTo(false)); + $outputMock->expects($this->at(29)) + ->method('write') + ->with($this->equalTo('')); + + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->overwrite('some information', true, 20); + } + + public function testAsk() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $dialogMock = $this->getMock('Symfony\Component\Console\Helper\DialogHelper'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $dialogMock->expects($this->once()) + ->method('ask') + ->with($this->isInstanceOf('Symfony\Component\Console\Output\OutputInterface'), + $this->equalTo('Why?'), + $this->equalTo('default')); + $helperMock->expects($this->once()) + ->method('get') + ->with($this->equalTo('dialog')) + ->will($this->returnValue($dialogMock)); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->ask('Why?', 'default'); + } + + public function testAskConfirmation() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $dialogMock = $this->getMock('Symfony\Component\Console\Helper\DialogHelper'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $dialogMock->expects($this->once()) + ->method('askConfirmation') + ->with($this->isInstanceOf('Symfony\Component\Console\Output\OutputInterface'), + $this->equalTo('Why?'), + $this->equalTo('default')); + $helperMock->expects($this->once()) + ->method('get') + ->with($this->equalTo('dialog')) + ->will($this->returnValue($dialogMock)); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->askConfirmation('Why?', 'default'); + } + + public function testAskAndValidate() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $dialogMock = $this->getMock('Symfony\Component\Console\Helper\DialogHelper'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $dialogMock->expects($this->once()) + ->method('askAndValidate') + ->with($this->isInstanceOf('Symfony\Component\Console\Output\OutputInterface'), + $this->equalTo('Why?'), + $this->equalTo('validator'), + $this->equalTo(10), + $this->equalTo('default')); + $helperMock->expects($this->once()) + ->method('get') + ->with($this->equalTo('dialog')) + ->will($this->returnValue($dialogMock)); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->askAndValidate('Why?', 'validator', 10, 'default'); + } + + public function testSetAndGetAuthorization() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + + $this->assertEquals( + array('username' => 'l3l0', 'password' => 'passwd'), + $consoleIO->getAuthorization('repoName') + ); + } + + public function testGetAuthorizationWhenDidNotSet() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + + $this->assertEquals( + array('username' => null, 'password' => null), + $consoleIO->getAuthorization('repoName') + ); + } + + public function testHasAuthorization() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + + $this->assertTrue($consoleIO->hasAuthorization('repoName')); + $this->assertFalse($consoleIO->hasAuthorization('repoName2')); + } + + public function testGetLastUsername() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + $consoleIO->setAuthorization('repoName2', 'l3l02', 'passwd2'); + + $this->assertEquals('l3l02', $consoleIO->getLastUsername()); + } + + public function testGetLastPassword() + { + $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet'); + + $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock); + $consoleIO->setAuthorization('repoName', 'l3l0', 'passwd'); + $consoleIO->setAuthorization('repoName2', 'l3l02', 'passwd2'); + + $this->assertEquals('passwd2', $consoleIO->getLastPassword()); + } +} From 9488b0f85f6dc578f807fa3da1522190495e29fa Mon Sep 17 00:00:00 2001 From: Leszek Prabucki Date: Sun, 22 Jan 2012 22:11:10 +0100 Subject: [PATCH 02/62] Fixed code. Changes improved code testability --- src/Composer/DependencyResolver/Rule.php | 4 +++- src/Composer/Downloader/GitDownloader.php | 1 + src/Composer/Downloader/HgDownloader.php | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index d08c4a5ed..aab1dabab 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -29,6 +29,8 @@ class Rule public $next1; public $next2; + public $ruleHash; + public function __construct(array $literals, $reason, $reasonData) { // sort all packages ascending by id @@ -85,7 +87,7 @@ class Rule } for ($i = 0, $n = count($this->literals); $i < $n; $i++) { - if (!$this->literals[$i]->getId() === $rule->literals[$i]->getId()) { + if (!($this->literals[$i]->getId() === $rule->literals[$i]->getId())) { return false; } } diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 170749674..2a51c1e9f 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -48,6 +48,7 @@ class GitDownloader extends VcsDownloader */ protected function enforceCleanDirectory($path) { + $output = array(); $this->process->execute(sprintf('cd %s && git status --porcelain', escapeshellarg($path)), $output); if (trim($output)) { throw new \RuntimeException('Source directory has uncommitted changes'); diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 768280845..a9953db0d 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -48,6 +48,7 @@ class HgDownloader extends VcsDownloader */ protected function enforceCleanDirectory($path) { + $output = array(); $this->process->execute(sprintf('cd %s && hg st', escapeshellarg($path)), $output); if (trim($output)) { throw new \RuntimeException('Source directory has uncommitted changes'); From 19878c2dc10143de20391b805df26a1ca922168b Mon Sep 17 00:00:00 2001 From: Leszek Prabucki Date: Mon, 23 Jan 2012 08:40:34 +0100 Subject: [PATCH 03/62] Fixed tests after update and merge of changes from upstream/master --- .../Test/Downloader/GitDownloaderTest.php | 20 +++++++------- .../Test/Downloader/HgDownloaderTest.php | 26 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 57cbd945d..7d563d2e4 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -26,13 +26,13 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->method('getSourceReference') ->will($this->returnValue(null)); - $downloader = new GitDownloader(); + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface')); $downloader->download($packageMock, '/path'); } public function testDownload() { - $expectedGitCommand = 'git clone \'https://github.com/l3l0/composer\' composerPath && cd composerPath && git checkout \'ref\' && git reset --hard \'ref\''; + $expectedGitCommand = 'git clone \'https://github.com/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''; $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -45,7 +45,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->method('execute') ->with($this->equalTo($expectedGitCommand)); - $downloader = new GitDownloader($processExecutor); + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->download($packageMock, 'composerPath'); } @@ -60,14 +60,14 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->method('getSourceReference') ->will($this->returnValue(null)); - $downloader = new GitDownloader(); + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface')); $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); } public function testUpdate() { - $expectedGitUpdateCommand = 'cd composerPath && git fetch && git checkout ref && git reset --hard ref'; - $expectedGitResetCommand = 'cd composerPath && git status --porcelain'; + $expectedGitUpdateCommand = 'cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\''; + $expectedGitResetCommand = 'cd \'composerPath\' && git status --porcelain'; $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) @@ -84,13 +84,13 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->method('execute') ->with($this->equalTo($expectedGitUpdateCommand)); - $downloader = new GitDownloader($processExecutor); + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->update($packageMock, $packageMock, 'composerPath'); } public function testRemove() { - $expectedGitResetCommand = 'cd composerPath && git status --porcelain'; + $expectedGitResetCommand = 'cd \'composerPath\' && git status --porcelain'; $packageMock = $this->getMock('Composer\Package\PackageInterface'); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); @@ -98,13 +98,13 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->method('execute') ->with($this->equalTo($expectedGitResetCommand)); - $downloader = new GitDownloader($processExecutor); + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->remove($packageMock, 'composerPath'); } public function testGetInstallationSource() { - $downloader = new GitDownloader(); + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface')); $this->assertEquals('source', $downloader->getInstallationSource()); } diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index d6752eb4a..81429194d 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -26,13 +26,13 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase ->method('getSourceReference') ->will($this->returnValue(null)); - $downloader = new HgDownloader(); + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface')); $downloader->download($packageMock, '/path'); } public function testDownload() { - $expectedGitCommand = '(hg clone \'https://mercurial.dev/l3l0/composer\' composerPath 2> /dev/null) && cd composerPath && hg up \'ref\''; + $expectedGitCommand = 'hg clone \'https://mercurial.dev/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && hg up \'ref\''; $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -45,7 +45,7 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase ->method('execute') ->with($this->equalTo($expectedGitCommand)); - $downloader = new HgDownloader($processExecutor); + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->download($packageMock, 'composerPath'); } @@ -60,14 +60,14 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase ->method('getSourceReference') ->will($this->returnValue(null)); - $downloader = new HgDownloader(); + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface')); $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); } public function testUpdate() { - $expectedGitUpdateCommand = 'cd composerPath && hg pull && hg up \'ref\''; - $expectedGitResetCommand = 'cd composerPath && hg st'; + $expectedUpdateCommand = 'cd \'composerPath\' && hg pull && hg up \'ref\''; + $expectedResetCommand = 'cd \'composerPath\' && hg st'; $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) @@ -79,32 +79,32 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->at(0)) ->method('execute') - ->with($this->equalTo($expectedGitResetCommand)); + ->with($this->equalTo($expectedResetCommand)); $processExecutor->expects($this->at(1)) ->method('execute') - ->with($this->equalTo($expectedGitUpdateCommand)); + ->with($this->equalTo($expectedUpdateCommand)); - $downloader = new HgDownloader($processExecutor); + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->update($packageMock, $packageMock, 'composerPath'); } public function testRemove() { - $expectedGitResetCommand = 'cd composerPath && hg st'; + $expectedResetCommand = 'cd \'composerPath\' && hg st'; $packageMock = $this->getMock('Composer\Package\PackageInterface'); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->any()) ->method('execute') - ->with($this->equalTo($expectedGitResetCommand)); + ->with($this->equalTo($expectedResetCommand)); - $downloader = new HgDownloader($processExecutor); + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); $downloader->remove($packageMock, 'composerPath'); } public function testGetInstallationSource() { - $downloader = new HgDownloader(); + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface')); $this->assertEquals('source', $downloader->getInstallationSource()); } From 3f38eede8ab1b9a19c0e87038593651f0fcd80c6 Mon Sep 17 00:00:00 2001 From: Leszek Prabucki Date: Mon, 23 Jan 2012 08:41:59 +0100 Subject: [PATCH 04/62] Made changes which fied warnings and errors in tests. --- src/Composer/Downloader/GitDownloader.php | 1 - src/Composer/Downloader/HgDownloader.php | 1 - src/Composer/Downloader/VcsDownloader.php | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 2a51c1e9f..170749674 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -48,7 +48,6 @@ class GitDownloader extends VcsDownloader */ protected function enforceCleanDirectory($path) { - $output = array(); $this->process->execute(sprintf('cd %s && git status --porcelain', escapeshellarg($path)), $output); if (trim($output)) { throw new \RuntimeException('Source directory has uncommitted changes'); diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index a9953db0d..768280845 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -48,7 +48,6 @@ class HgDownloader extends VcsDownloader */ protected function enforceCleanDirectory($path) { - $output = array(); $this->process->execute(sprintf('cd %s && hg st', escapeshellarg($path)), $output); if (trim($output)) { throw new \RuntimeException('Source directory has uncommitted changes'); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 8af2ce48e..c24411310 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -100,4 +100,4 @@ abstract class VcsDownloader implements DownloaderInterface * @throws \RuntimeException if the directory is not clean */ abstract protected function enforceCleanDirectory($path); -} \ No newline at end of file +} From f59ca1e2f8ad847a6392db802ff95f2e7aff4e92 Mon Sep 17 00:00:00 2001 From: Leszek Prabucki Date: Mon, 23 Jan 2012 09:21:36 +0100 Subject: [PATCH 05/62] Made fixes after review. --- src/Composer/DependencyResolver/Rule.php | 2 +- .../Test/DependencyResolver/RuleSetIteratorTest.php | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Composer/DependencyResolver/Rule.php b/src/Composer/DependencyResolver/Rule.php index aab1dabab..8af24094e 100644 --- a/src/Composer/DependencyResolver/Rule.php +++ b/src/Composer/DependencyResolver/Rule.php @@ -87,7 +87,7 @@ class Rule } for ($i = 0, $n = count($this->literals); $i < $n; $i++) { - if (!($this->literals[$i]->getId() === $rule->literals[$i]->getId())) { + if ($this->literals[$i]->getId() !== $rule->literals[$i]->getId()) { return false; } } diff --git a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php index d45f9a561..6340a7f68 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php @@ -39,8 +39,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase $ruleSetIterator = new RuleSetIterator($this->rules); $result = array(); - foreach ($ruleSetIterator as $rule) - { + foreach ($ruleSetIterator as $rule) { $result[] = $rule; } @@ -58,8 +57,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase $ruleSetIterator = new RuleSetIterator($this->rules); $result = array(); - foreach ($ruleSetIterator as $key => $rule) - { + foreach ($ruleSetIterator as $key => $rule) { $result[] = $key; } From 34bbfb70d2d662ef2022c98dca2d3eef709d2680 Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Tue, 3 Jan 2012 17:57:31 -0700 Subject: [PATCH 06/62] Initial version of InitCommand This command allows a user to create a basic composer.json definition easily. The idea is to create a command similar to the `npm init` command. This version only has `name` and `description` support. Additional fields will be added such as `require`, `authors`, `version`, etc. --- src/Composer/Command/Helper/DialogHelper.php | 23 ++++ src/Composer/Command/InitCommand.php | 126 +++++++++++++++++++ src/Composer/Console/Application.php | 1 + 3 files changed, 150 insertions(+) create mode 100644 src/Composer/Command/Helper/DialogHelper.php create mode 100644 src/Composer/Command/InitCommand.php diff --git a/src/Composer/Command/Helper/DialogHelper.php b/src/Composer/Command/Helper/DialogHelper.php new file mode 100644 index 000000000..a0c034116 --- /dev/null +++ b/src/Composer/Command/Helper/DialogHelper.php @@ -0,0 +1,23 @@ +writeln(array( + '', + $this->getHelperSet()->get('formatter')->formatBlock($text, $style, true), + '', + )); + } + + public function getQuestion($question, $default, $sep = ':') + { + return $default ? sprintf('%s [%s]%s ', $question, $default, $sep) : sprintf('%s%s ', $question, $sep); + } +} \ No newline at end of file diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php new file mode 100644 index 000000000..758778b6b --- /dev/null +++ b/src/Composer/Command/InitCommand.php @@ -0,0 +1,126 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Command; + +use Composer\Json\JsonFile; +use Composer\Command\Helper\DialogHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + + +if (!defined('JSON_PRETTY_PRINT')) { + define('JSON_PRETTY_PRINT', 128); +} + +/** + * @author Justin Rainbow + */ +class InitCommand extends Command +{ + protected function configure() + { + $this + ->setName('init') + ->setDescription('Creates a basic composer.json file in current directory.') + ->setDefinition(array( + new InputOption('name', null, InputOption::VALUE_NONE, 'Name of the package'), + new InputOption('description', null, InputOption::VALUE_NONE, 'Description of package'), + // new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'), + new InputOption('homepage', null, InputOption::VALUE_NONE, 'Homepage of package'), + )) + ->setHelp(<<install command reads the composer.json file from the +current directory, processes it, and downloads and installs all the +libraries and dependencies outlined in that file. + +php composer.phar install + +EOT + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $dialog = $this->getDialogHelper(); + + $options = array_filter(array_intersect_key($input->getOptions(), array_flip(array('name','description')))); + + $file = new JsonFile("composer.json"); + + $indentSize = 2; + $lines = array(); + + foreach ($options as $key => $value) { + $lines[] = sprintf('%s%s: %s', str_repeat(' ', $indentSize), json_encode($key), json_encode($value)); + } + + $json = "{\n" . implode(",\n", $lines) . "\n}\n"; + + if ($input->isInteractive()) { + $output->writeln(array( + '', + $json, + '' + )); + if (!$dialog->askConfirmation($output, $dialog->getQuestion('Do you confirm generation', 'yes', '?'), true)) { + $output->writeln('Command aborted'); + + return 1; + } + } + + $file->write($options); + } + + protected function interact(InputInterface $input, OutputInterface $output) + { + $dialog = $this->getDialogHelper(); + $dialog->writeSection($output, 'Welcome to the Composer config generator'); + + // namespace + $output->writeln(array( + '', + 'This command will guide you through creating your composer.json config.', + '', + )); + + $cwd = realpath("."); + + $name = $input->getOption('name') ?: basename($cwd); + $name = $dialog->ask( + $output, + $dialog->getQuestion('Package name', $name), + $name + ); + $input->setOption('name', $name); + + $description = $input->getOption('description') ?: false; + $description = $dialog->ask( + $output, + $dialog->getQuestion('Description', $description) + ); + $input->setOption('description', $description); + } + + protected function getDialogHelper() + { + $dialog = $this->getHelperSet()->get('dialog'); + if (!$dialog || get_class($dialog) !== 'Composer\Command\Helper\DialogHelper') { + $this->getHelperSet()->set($dialog = new DialogHelper()); + } + + return $dialog; + } +} \ No newline at end of file diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 6d1d6ba0b..3b3979770 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -104,6 +104,7 @@ class Application extends BaseApplication { $this->add(new Command\AboutCommand()); $this->add(new Command\DependsCommand()); + $this->add(new Command\InitCommand()); $this->add(new Command\InstallCommand()); $this->add(new Command\UpdateCommand()); $this->add(new Command\SearchCommand()); From 10ca974f33f662a4fecd55f4ed3d84889797e84a Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Tue, 3 Jan 2012 18:03:09 -0700 Subject: [PATCH 07/62] Quick change to the help text for InitCommand --- src/Composer/Command/InitCommand.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 758778b6b..aedfd1241 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -40,11 +40,10 @@ class InitCommand extends Command new InputOption('homepage', null, InputOption::VALUE_NONE, 'Homepage of package'), )) ->setHelp(<<install command reads the composer.json file from the -current directory, processes it, and downloads and installs all the -libraries and dependencies outlined in that file. +The init command creates a basic composer.json file +in the current directory. -php composer.phar install +php composer.phar init EOT ) From 5933f34d6f244aec277d3bb35c72eca903f32445 Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 12:47:48 -0700 Subject: [PATCH 08/62] Adding a JSON string formatter to the JsonFile class --- src/Composer/Json/JsonFormatter.php | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/Composer/Json/JsonFormatter.php diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php new file mode 100644 index 000000000..01e83ea06 --- /dev/null +++ b/src/Composer/Json/JsonFormatter.php @@ -0,0 +1,88 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +/** + * Format JSON output + * + * @author Justin Rainbow + */ +class JsonFormatter +{ + private $indent = ' '; + + private $level = 1; + + /** + * Indents a flat JSON string to make it more human-readable + * + * Original code for this function can be found at: + * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ + * + * @param string $json The original JSON string to process + * @return string Indented version of the original JSON string + */ + public function format($json) + { + if (!is_string($json)) { + $json = json_encode($json); + } + + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = $this->indent; + $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; + } else if (($char == '}' || $char == ']') && $outOfQuotes) { + // If this character is the end of an element, + // output a new line and indent the next line + $result .= $newLine; + $pos --; + for ($j=0; $j<$pos; $j++) { + $result .= $indentStr; + } + } + + // 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; + } +} \ No newline at end of file From 2fbd9490b1d9d111550edcabc7561a40b5a30336 Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 12:48:05 -0700 Subject: [PATCH 09/62] Adding a requirement definition stage to init command --- src/Composer/Command/InitCommand.php | 112 +++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 14 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index aedfd1241..829f5d5d5 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -18,11 +18,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; - -if (!defined('JSON_PRETTY_PRINT')) { - define('JSON_PRETTY_PRINT', 128); -} - /** * @author Justin Rainbow */ @@ -38,6 +33,7 @@ class InitCommand extends Command new InputOption('description', null, InputOption::VALUE_NONE, 'Description of package'), // new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'), new InputOption('homepage', null, InputOption::VALUE_NONE, 'Homepage of package'), + new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'An array required packages'), )) ->setHelp(<<init command creates a basic composer.json file @@ -54,18 +50,13 @@ EOT { $dialog = $this->getDialogHelper(); - $options = array_filter(array_intersect_key($input->getOptions(), array_flip(array('name','description')))); - - $file = new JsonFile("composer.json"); + $options = array_filter(array_intersect_key($input->getOptions(), array_flip(array('name','description','require')))); - $indentSize = 2; - $lines = array(); + $options['require'] = $this->formatRequirements($options['require']); - foreach ($options as $key => $value) { - $lines[] = sprintf('%s%s: %s', str_repeat(' ', $indentSize), json_encode($key), json_encode($value)); - } + $file = new JsonFile('composer.json'); - $json = "{\n" . implode(",\n", $lines) . "\n}\n"; + $json = $file->encode($options); if ($input->isInteractive()) { $output->writeln(array( @@ -111,6 +102,18 @@ EOT $dialog->getQuestion('Description', $description) ); $input->setOption('description', $description); + + $output->writeln(array( + '', + 'Define your dependencies.', + '' + )); + + if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dependencies interactively', 'yes', '?'), true)) { + $requirements = $this->determineRequirements($input, $output); + + $input->setOption('require', $requirements); + } } protected function getDialogHelper() @@ -122,4 +125,85 @@ EOT return $dialog; } + + protected function findPackages($name) + { + $composer = $this->getComposer(); + + $packages = array(); + + // create local repo, this contains all packages that are installed in the local project + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + + $token = strtolower($name); + foreach ($composer->getRepositoryManager()->getRepositories() as $repository) { + foreach ($repository->getPackages() as $package) { + if (false === ($pos = strpos($package->getName(), $token))) { + continue; + } + + $packages[] = $package; + } + } + + return $packages; + } + + protected function determineRequirements(InputInterface $input, OutputInterface $output) + { + $dialog = $this->getDialogHelper(); + $prompt = $dialog->getQuestion('Search for a package', false, ':'); + + $requires = $input->getOption('require') ?: array(); + + while (null !== $package = $dialog->ask($output, $prompt)) { + $matches = $this->findPackages($package); + + if (count($matches)) { + $output->writeln(array( + '', + sprintf('Found %s packages matching %s', count($matches), $package), + '' + )); + + foreach ($matches as $position => $package) { + $output->writeln(sprintf(' %5s %s %s', "[$position]", $package->getPrettyName(), $package->getPrettyVersion())); + } + + $output->writeln(''); + + $validator = function ($selection) use ($matches) { + if ('' === $selection) { + return false; + } + + if (!isset($matches[(int) $selection])) { + throw new \Exception('Not a valid selection'); + } + + return $matches[(int) $selection]; + }; + + $package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add', false, ':'), $validator, 3); + + if (false !== $package) { + $requires[] = sprintf('%s %s', $package->getName(), $package->getPrettyVersion()); + } + } + } + + return $requires; + } + + protected function formatRequirements(array $requirements) + { + $requires = array(); + foreach ($requirements as $requirement) { + list($packageName, $packageVersion) = explode(" ", $requirement); + + $requires[$packageName] = $packageVersion; + } + + return empty($requires) ? new \stdClass : $requires; + } } \ No newline at end of file From 42d55bf5106f70d4636cc2a6122b487b9a9ffdb9 Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 12:50:42 -0700 Subject: [PATCH 10/62] Removing the JsonFormatter as it is part of the JsonFile now --- src/Composer/Json/JsonFormatter.php | 88 ----------------------------- 1 file changed, 88 deletions(-) delete mode 100644 src/Composer/Json/JsonFormatter.php diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php deleted file mode 100644 index 01e83ea06..000000000 --- a/src/Composer/Json/JsonFormatter.php +++ /dev/null @@ -1,88 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Json; - -/** - * Format JSON output - * - * @author Justin Rainbow - */ -class JsonFormatter -{ - private $indent = ' '; - - private $level = 1; - - /** - * Indents a flat JSON string to make it more human-readable - * - * Original code for this function can be found at: - * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ - * - * @param string $json The original JSON string to process - * @return string Indented version of the original JSON string - */ - public function format($json) - { - if (!is_string($json)) { - $json = json_encode($json); - } - - $result = ''; - $pos = 0; - $strLen = strlen($json); - $indentStr = $this->indent; - $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; - } else if (($char == '}' || $char == ']') && $outOfQuotes) { - // If this character is the end of an element, - // output a new line and indent the next line - $result .= $newLine; - $pos --; - for ($j=0; $j<$pos; $j++) { - $result .= $indentStr; - } - } - - // 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; - } -} \ No newline at end of file From 6ff7694de1380c3e222b5a9fa63641e8134ed45d Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 13:37:01 -0700 Subject: [PATCH 11/62] Fixing error when no requirements are defined --- src/Composer/Command/InitCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 829f5d5d5..0582a6fa8 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -52,7 +52,7 @@ EOT $options = array_filter(array_intersect_key($input->getOptions(), array_flip(array('name','description','require')))); - $options['require'] = $this->formatRequirements($options['require']); + $options['require'] = $this->formatRequirements(isset($options['require']) ? $options['require'] : array()); $file = new JsonFile('composer.json'); From cd9c86c70c0712d7ca15efc8968732a12fe44629 Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 15:01:49 -0700 Subject: [PATCH 12/62] Adding author defaults to init command --- src/Composer/Command/InitCommand.php | 95 ++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 0582a6fa8..2acaa2d47 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -17,12 +17,33 @@ use Composer\Command\Helper\DialogHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\ExecutableFinder; /** * @author Justin Rainbow */ class InitCommand extends Command { + private $gitConfig; + + public function parseAuthorString($author) + { + if (preg_match('/^(?P[- \.,a-z0-9]+) <(?P.+?)>$/i', $author, $match)) { + if ($match['email'] === filter_var($match['email'], FILTER_VALIDATE_EMAIL)) { + return array( + 'name' => trim($match['name']), + 'email' => $match['email'] + ); + } + } + + throw new \InvalidArgumentException( + 'Invalid author string. Must be in the format:'. + ' John Smith ' + ); + } + protected function configure() { $this @@ -31,6 +52,7 @@ class InitCommand extends Command ->setDefinition(array( new InputOption('name', null, InputOption::VALUE_NONE, 'Name of the package'), new InputOption('description', null, InputOption::VALUE_NONE, 'Description of package'), + new InputOption('author', null, InputOption::VALUE_NONE, 'Author name of package'), // new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'), new InputOption('homepage', null, InputOption::VALUE_NONE, 'Homepage of package'), new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'An array required packages'), @@ -50,9 +72,18 @@ EOT { $dialog = $this->getDialogHelper(); - $options = array_filter(array_intersect_key($input->getOptions(), array_flip(array('name','description','require')))); + $whitelist = array('name', 'description', 'author', 'require'); - $options['require'] = $this->formatRequirements(isset($options['require']) ? $options['require'] : array()); + $options = array_filter(array_intersect_key($input->getOptions(), array_flip($whitelist))); + + if (isset($options['author'])) { + $options['authors'] = $this->formatAuthors($options['author']); + unset($options['author']); + } + + $options['require'] = isset($options['require']) ? + $this->formatRequirements($options['require']) : + new \stdClass; $file = new JsonFile('composer.json'); @@ -76,6 +107,8 @@ EOT protected function interact(InputInterface $input, OutputInterface $output) { + $git = $this->getGitConfig(); + $dialog = $this->getDialogHelper(); $dialog->writeSection($output, 'Welcome to the Composer config generator'); @@ -88,7 +121,13 @@ EOT $cwd = realpath("."); - $name = $input->getOption('name') ?: basename($cwd); + if (false === $name = $input->getOption('name')) { + $name = basename($cwd); + if (isset($git['github.user'])) { + $name = $git['github.user'] . '/' . $name; + } + } + $name = $dialog->ask( $output, $dialog->getQuestion('Package name', $name), @@ -103,17 +142,39 @@ EOT ); $input->setOption('description', $description); + if (false === $author = $input->getOption('author')) { + if (isset($git['user.name']) && isset($git['user.email'])) { + $author = sprintf('%s <%s>', $git['user.name'], $git['user.email']); + } + } + + $self = $this; + $author = $dialog->askAndValidate( + $output, + $dialog->getQuestion('Author', $author), + function ($value) use ($self, $author) { + if (null === $value) { + return $author; + } + + $author = $self->parseAuthorString($value); + + return sprintf('%s <%s>', $author['name'], $author['email']); + } + ); + $input->setOption('author', $author); + $output->writeln(array( '', 'Define your dependencies.', '' )); + $requirements = array(); if ($dialog->askConfirmation($output, $dialog->getQuestion('Would you like to define your dependencies interactively', 'yes', '?'), true)) { $requirements = $this->determineRequirements($input, $output); - - $input->setOption('require', $requirements); } + $input->setOption('require', $requirements); } protected function getDialogHelper() @@ -195,6 +256,11 @@ EOT return $requires; } + protected function formatAuthors($author) + { + return array($this->parseAuthorString($author)); + } + protected function formatRequirements(array $requirements) { $requires = array(); @@ -206,4 +272,23 @@ EOT return empty($requires) ? new \stdClass : $requires; } + + protected function getGitConfig() + { + if (null !== $this->gitConfig) { + return $this->gitConfig; + } + + $finder = new ExecutableFinder(); + $gitBin = $finder->find('git'); + + $cmd = new Process(sprintf('%s config -l', $gitBin)); + $cmd->run(); + + if ($cmd->isSuccessful()) { + return $this->gitConfig = parse_ini_string($cmd->getOutput(), false, INI_SCANNER_RAW); + } + + return $this->gitConfig = array(); + } } \ No newline at end of file From 950bbcbed90c7872e837b1702ee10d46a7fd2f49 Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 15:27:19 -0700 Subject: [PATCH 13/62] Init now asks user if they want to ignore the vendor dir --- src/Composer/Command/InitCommand.php | 67 ++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 2acaa2d47..e5cbaa34e 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -103,6 +103,22 @@ EOT } $file->write($options); + + if ($input->isInteractive()) { + $ignoreFile = realpath('.gitignore'); + + if (false === $ignoreFile) { + $ignoreFile = realpath('.') . '/.gitignore'; + } + + if (!$this->hasVendorIgnore($ignoreFile)) { + $question = 'Would you like the vendor directory added to your .gitignore [yes]?'; + + if ($dialog->askConfirmation($output, $question, true)) { + $this->addVendorIgnore($ignoreFile); + } + } + } } protected function interact(InputInterface $input, OutputInterface $output) @@ -291,4 +307,55 @@ EOT return $this->gitConfig = array(); } + + /** + * Checks the local .gitignore file for the Composer vendor directory. + * + * Tested patterns include: + * "/$vendor" + * "$vendor" + * "$vendor/" + * "/$vendor/" + * "/$vendor/*" + * "$vendor/*" + * + * @param string $ignoreFile + * @param string $vendor + * + * @return Boolean + */ + protected function hasVendorIgnore($ignoreFile, $vendor = 'vendor') + { + if (!file_exists($ignoreFile)) { + return false; + } + + $pattern = sprintf( + '~^/?%s(/|/\*)?$~', + preg_quote($vendor, '~') + ); + + $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES); + foreach ($lines as $line) { + if (preg_match($pattern, $line)) { + return true; + } + } + + return false; + } + + protected function addVendorIgnore($ignoreFile, $vendor = 'vendor') + { + $contents = ""; + if (file_exists($ignoreFile)) { + $contents = file_get_contents($ignoreFile); + + if ("\n" !== substr($contents, 0, -1)) { + $contents .= "\n"; + } + } + + file_put_contents($ignoreFile, $contents . $vendor); + } } \ No newline at end of file From 9ec641659e831df23937ae3b3657c8a24874efad Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 15:33:06 -0700 Subject: [PATCH 14/62] Validating package name input for init command --- src/Composer/Command/InitCommand.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index e5cbaa34e..749e7ab00 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -141,13 +141,28 @@ EOT $name = basename($cwd); if (isset($git['github.user'])) { $name = $git['github.user'] . '/' . $name; + } else { + // package names must be in the format foo/bar + $name = $name . '/' . $name; } } - $name = $dialog->ask( + $name = $dialog->askAndValidate( $output, $dialog->getQuestion('Package name', $name), - $name + function ($value) use ($name) { + if (null === $value) { + return $name; + } + + if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}i', $value)) { + throw new \InvalidArgumentException( + 'The package name '.$value.' is invalid, it should have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+' + ); + } + + return $value; + } ); $input->setOption('name', $name); From 7a0a1788e38b6b69ed4104656b2d61e53496d36f Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 15:40:30 -0700 Subject: [PATCH 15/62] Moved DialogHelper into the base Application --- src/Composer/Command/InitCommand.php | 17 +++-------------- src/Composer/Console/Application.php | 13 +++++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 749e7ab00..d20fdedaa 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -13,7 +13,6 @@ namespace Composer\Command; use Composer\Json\JsonFile; -use Composer\Command\Helper\DialogHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -70,7 +69,7 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $dialog = $this->getDialogHelper(); + $dialog = $this->getHelperSet()->get('dialog'); $whitelist = array('name', 'description', 'author', 'require'); @@ -125,7 +124,7 @@ EOT { $git = $this->getGitConfig(); - $dialog = $this->getDialogHelper(); + $dialog = $this->getHelperSet()->get('dialog'); $dialog->writeSection($output, 'Welcome to the Composer config generator'); // namespace @@ -208,16 +207,6 @@ EOT $input->setOption('require', $requirements); } - protected function getDialogHelper() - { - $dialog = $this->getHelperSet()->get('dialog'); - if (!$dialog || get_class($dialog) !== 'Composer\Command\Helper\DialogHelper') { - $this->getHelperSet()->set($dialog = new DialogHelper()); - } - - return $dialog; - } - protected function findPackages($name) { $composer = $this->getComposer(); @@ -243,7 +232,7 @@ EOT protected function determineRequirements(InputInterface $input, OutputInterface $output) { - $dialog = $this->getDialogHelper(); + $dialog = $this->getHelperSet()->get('dialog'); $prompt = $dialog->getQuestion('Search for a package', false, ':'); $requires = $input->getOption('require') ?: array(); diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 3b3979770..fe6b6cb13 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -20,6 +20,7 @@ use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Finder\Finder; use Composer\Command; +use Composer\Command\Helper\DialogHelper; use Composer\Composer; use Composer\Factory; use Composer\IO\IOInterface; @@ -115,4 +116,16 @@ class Application extends BaseApplication $this->add(new Command\SelfUpdateCommand()); } } + + /** + * {@inheritDoc} + */ + protected function getDefaultHelperSet() + { + $helperSet = parent::getDefaultHelperSet(); + + $helperSet->set(new DialogHelper()); + + return $helperSet; + } } From 3a5d09a8b0b22adb1efa78237114cd503ec4eedc Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 15:55:12 -0700 Subject: [PATCH 16/62] Removing the 'writeSection' method from 'DialogHelper' --- src/Composer/Command/Helper/DialogHelper.php | 34 ++++++++++++++------ src/Composer/Command/InitCommand.php | 7 +++- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/Composer/Command/Helper/DialogHelper.php b/src/Composer/Command/Helper/DialogHelper.php index a0c034116..674b03800 100644 --- a/src/Composer/Command/Helper/DialogHelper.php +++ b/src/Composer/Command/Helper/DialogHelper.php @@ -1,5 +1,15 @@ + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Composer\Command\Helper; use Symfony\Component\Console\Helper\DialogHelper as BaseDialogHelper; @@ -7,17 +17,21 @@ use Symfony\Component\Console\Output\OutputInterface; class DialogHelper extends BaseDialogHelper { - public function writeSection(OutputInterface $output, $text, $style = 'bg=blue;fg=white') - { - $output->writeln(array( - '', - $this->getHelperSet()->get('formatter')->formatBlock($text, $style, true), - '', - )); - } - + /** + * Build text for asking a question. For example: + * + * "Do you want to continue [yes]:" + * + * @param string $question The question you want to ask + * @param mixed $default Default value to add to message, if false no default will be shown + * @param string $sep Separation char for between message and user input + * + * @return string + */ public function getQuestion($question, $default, $sep = ':') { - return $default ? sprintf('%s [%s]%s ', $question, $default, $sep) : sprintf('%s%s ', $question, $sep); + return $default ? + sprintf('%s [%s]%s ', $question, $default, $sep) : + sprintf('%s%s ', $question, $sep); } } \ No newline at end of file diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index d20fdedaa..93023d2ad 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -125,7 +125,12 @@ EOT $git = $this->getGitConfig(); $dialog = $this->getHelperSet()->get('dialog'); - $dialog->writeSection($output, 'Welcome to the Composer config generator'); + $formatter = $this->getHelperSet()->get('formatter'); + $output->writeln(array( + '', + $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true), + '' + )); // namespace $output->writeln(array( From 604f2836e30cb7245132d6e3835e62db2d2a9c9c Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 12:47:48 -0700 Subject: [PATCH 17/62] Adding a JSON string formatter to the JsonFile class --- src/Composer/Json/JsonFormatter.php | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/Composer/Json/JsonFormatter.php diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php new file mode 100644 index 000000000..01e83ea06 --- /dev/null +++ b/src/Composer/Json/JsonFormatter.php @@ -0,0 +1,88 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Json; + +/** + * Format JSON output + * + * @author Justin Rainbow + */ +class JsonFormatter +{ + private $indent = ' '; + + private $level = 1; + + /** + * Indents a flat JSON string to make it more human-readable + * + * Original code for this function can be found at: + * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ + * + * @param string $json The original JSON string to process + * @return string Indented version of the original JSON string + */ + public function format($json) + { + if (!is_string($json)) { + $json = json_encode($json); + } + + $result = ''; + $pos = 0; + $strLen = strlen($json); + $indentStr = $this->indent; + $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; + } else if (($char == '}' || $char == ']') && $outOfQuotes) { + // If this character is the end of an element, + // output a new line and indent the next line + $result .= $newLine; + $pos --; + for ($j=0; $j<$pos; $j++) { + $result .= $indentStr; + } + } + + // 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; + } +} \ No newline at end of file From 24d85a48f07e760795cc705b8ff2455dbe068cc3 Mon Sep 17 00:00:00 2001 From: Justin Rainbow Date: Sun, 8 Jan 2012 12:50:42 -0700 Subject: [PATCH 18/62] Removing the JsonFormatter as it is part of the JsonFile now --- src/Composer/Json/JsonFormatter.php | 88 ----------------------------- 1 file changed, 88 deletions(-) delete mode 100644 src/Composer/Json/JsonFormatter.php diff --git a/src/Composer/Json/JsonFormatter.php b/src/Composer/Json/JsonFormatter.php deleted file mode 100644 index 01e83ea06..000000000 --- a/src/Composer/Json/JsonFormatter.php +++ /dev/null @@ -1,88 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Json; - -/** - * Format JSON output - * - * @author Justin Rainbow - */ -class JsonFormatter -{ - private $indent = ' '; - - private $level = 1; - - /** - * Indents a flat JSON string to make it more human-readable - * - * Original code for this function can be found at: - * http://recursive-design.com/blog/2008/03/11/format-json-with-php/ - * - * @param string $json The original JSON string to process - * @return string Indented version of the original JSON string - */ - public function format($json) - { - if (!is_string($json)) { - $json = json_encode($json); - } - - $result = ''; - $pos = 0; - $strLen = strlen($json); - $indentStr = $this->indent; - $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; - } else if (($char == '}' || $char == ']') && $outOfQuotes) { - // If this character is the end of an element, - // output a new line and indent the next line - $result .= $newLine; - $pos --; - for ($j=0; $j<$pos; $j++) { - $result .= $indentStr; - } - } - - // 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; - } -} \ No newline at end of file From f5d90e134065ee6ff0efcf69edcd28c2cef48f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Mon, 13 Feb 2012 17:40:54 +0100 Subject: [PATCH 19/62] Download Dist Package when the info is defined in root composer file --- src/Composer/Downloader/FileDownloader.php | 112 ++++++++++++++------- src/Composer/Repository/Vcs/VcsDriver.php | 2 +- 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index ae2b199b2..ef6c13cc9 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -26,6 +26,10 @@ abstract class FileDownloader implements DownloaderInterface { protected $io; private $bytesMax; + private $firstCall; + private $url; + private $fileUrl; + private $fileName; /** * Constructor. @@ -50,6 +54,10 @@ abstract class FileDownloader implements DownloaderInterface */ public function download(PackageInterface $package, $path) { + $this->firstCall = true; + $this->url = $package->getSourceUrl(); + $this->fileUrl = $package->getDistUrl(); + // init the progress bar $this->bytesMax = 0; @@ -66,6 +74,7 @@ abstract class FileDownloader implements DownloaderInterface } $fileName = rtrim($path.'/'.md5(time().rand()).'.'.pathinfo($url, PATHINFO_EXTENSION), '.'); + $this->fileName = $fileName; $this->io->write(" - Package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); @@ -78,35 +87,8 @@ abstract class FileDownloader implements DownloaderInterface } } - // Handle system proxy - $params = array('http' => array()); - - if (isset($_SERVER['HTTP_PROXY'])) { - // http(s):// is not supported in proxy - $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']); - - if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { - throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); - } - - $params['http'] = array( - 'proxy' => $proxy, - 'request_fulluri' => true, - ); - } - - if ($this->io->hasAuthorization($package->getSourceUrl())) { - $auth = $this->io->getAuthorization($package->getSourceUrl()); - $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $params['http'] = array_merge($params['http'], array('header' => "Authorization: Basic $authStr\r\n")); - } - - $ctx = stream_context_create($params); - stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); - - $this->io->overwrite(" Downloading: connection...", false); - @copy($url, $fileName, $ctx); - $this->io->overwrite(" Downloading"); + $this->copy($this->url, $this->fileName, $this->fileUrl); + $this->io->write(''); if (!file_exists($fileName)) { throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' @@ -170,12 +152,39 @@ abstract class FileDownloader implements DownloaderInterface protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) { switch ($notificationCode) { - case STREAM_NOTIFY_AUTH_REQUIRED: - throw new \LogicException("Authorization is required"); - break; - - case STREAM_NOTIFY_FAILURE: - throw new \LogicException("File not found"); + case STREAM_NOTIFY_AUTH_REQUIRED: + case STREAM_NOTIFY_FAILURE: + // for private repository returning 404 error when the authorization is incorrect + $auth = $this->io->getAuthorization($this->url); + $ps = $this->firstCall && 404 === $messageCode + && null === $this->io->getLastUsername() + && null === $auth['username']; + + if (404 === $messageCode && !$this->firstCall) { + throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found"); + } + + $this->firstCall = false; + + // get authorization informations + if (401 === $messageCode || $ps) { + if (!$this->io->isInteractive()) { + $mess = "The '" . $this->fileUrl . "' URL not found"; + + if (401 === $code || $ps) { + $mess = "The '" . $this->fileUrl . "' URL required the authorization.\nYou must be used the interactive console"; + } + + throw new \RuntimeException($mess); + } + + $this->io->overwrite(' Authorization required:'); + $username = $this->io->ask(' Username: '); + $password = $this->io->askAndHideAnswer(' Password: '); + $this->io->setAuthorization($this->url, $username, $password); + + $this->copy($this->url, $this->fileName, $this->fileUrl); + } break; case STREAM_NOTIFY_FILE_SIZE_IS: @@ -203,6 +212,39 @@ abstract class FileDownloader implements DownloaderInterface } } + protected function copy($url, $fileName, $fileUrl) + { + // Handle system proxy + $params = array('http' => array()); + + if (isset($_SERVER['HTTP_PROXY'])) { + // http(s):// is not supported in proxy + $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']); + + if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { + throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); + } + + $params['http'] = array( + 'proxy' => $proxy, + 'request_fulluri' => true, + ); + } + + if ($this->io->hasAuthorization($url)) { + $auth = $this->io->getAuthorization($url); + $authStr = base64_encode($auth['username'] . ':' . $auth['password']); + $params['http'] = array_merge($params['http'], array('header' => "Authorization: Basic $authStr\r\n")); + } + + $ctx = stream_context_create($params); + stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); + + $this->io->overwrite(" Downloading: connection...", false); + @copy($fileUrl, $fileName, $ctx); + $this->io->overwrite(" Downloading", false); + } + /** * Extract file to directory * diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 6addf26e7..5a25fc1be 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -136,7 +136,7 @@ abstract class VcsDriver throw new \RuntimeException($mess); } - $this->io->write("Authorization for " . $this->contentUrl . ":"); + $this->io->write(array('', "Authorization for " . $this->contentUrl . ":")); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->url, $username, $password); From 9638247e4448470163693482c9bea058cc4fcd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Tue, 14 Feb 2012 11:25:00 +0100 Subject: [PATCH 20/62] Move to Util Class --- src/Composer/Downloader/FileDownloader.php | 123 +--------------- src/Composer/Util/RemoteFilesystem.php | 157 +++++++++++++++++++++ 2 files changed, 160 insertions(+), 120 deletions(-) create mode 100644 src/Composer/Util/RemoteFilesystem.php diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index ef6c13cc9..dad74fbc4 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -11,6 +11,7 @@ namespace Composer\Downloader; +use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; @@ -25,11 +26,6 @@ use Composer\Util\Filesystem; abstract class FileDownloader implements DownloaderInterface { protected $io; - private $bytesMax; - private $firstCall; - private $url; - private $fileUrl; - private $fileName; /** * Constructor. @@ -54,13 +50,6 @@ abstract class FileDownloader implements DownloaderInterface */ public function download(PackageInterface $package, $path) { - $this->firstCall = true; - $this->url = $package->getSourceUrl(); - $this->fileUrl = $package->getDistUrl(); - - // init the progress bar - $this->bytesMax = 0; - $url = $package->getDistUrl(); $checksum = $package->getDistSha1Checksum(); @@ -74,7 +63,6 @@ abstract class FileDownloader implements DownloaderInterface } $fileName = rtrim($path.'/'.md5(time().rand()).'.'.pathinfo($url, PATHINFO_EXTENSION), '.'); - $this->fileName = $fileName; $this->io->write(" - Package " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); @@ -87,7 +75,8 @@ abstract class FileDownloader implements DownloaderInterface } } - $this->copy($this->url, $this->fileName, $this->fileUrl); + $rfs = new RemoteFilesystem($this->io); + $rfs->copy($package->getSourceUrl(), $fileName, $url); $this->io->write(''); if (!file_exists($fileName)) { @@ -139,112 +128,6 @@ abstract class FileDownloader implements DownloaderInterface $fs->removeDirectory($path); } - /** - * Get notification action. - * - * @param integer $notificationCode The notification code - * @param integer $severity The severity level - * @param string $message The message - * @param integer $messageCode The message code - * @param integer $bytesTransferred The loaded size - * @param integer $bytesMax The total size - */ - protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) - { - switch ($notificationCode) { - case STREAM_NOTIFY_AUTH_REQUIRED: - case STREAM_NOTIFY_FAILURE: - // for private repository returning 404 error when the authorization is incorrect - $auth = $this->io->getAuthorization($this->url); - $ps = $this->firstCall && 404 === $messageCode - && null === $this->io->getLastUsername() - && null === $auth['username']; - - if (404 === $messageCode && !$this->firstCall) { - throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found"); - } - - $this->firstCall = false; - - // get authorization informations - if (401 === $messageCode || $ps) { - if (!$this->io->isInteractive()) { - $mess = "The '" . $this->fileUrl . "' URL not found"; - - if (401 === $code || $ps) { - $mess = "The '" . $this->fileUrl . "' URL required the authorization.\nYou must be used the interactive console"; - } - - throw new \RuntimeException($mess); - } - - $this->io->overwrite(' Authorization required:'); - $username = $this->io->ask(' Username: '); - $password = $this->io->askAndHideAnswer(' Password: '); - $this->io->setAuthorization($this->url, $username, $password); - - $this->copy($this->url, $this->fileName, $this->fileUrl); - } - break; - - case STREAM_NOTIFY_FILE_SIZE_IS: - if ($this->bytesMax < $bytesMax) { - $this->bytesMax = $bytesMax; - } - break; - - case STREAM_NOTIFY_PROGRESS: - if ($this->bytesMax > 0) { - $progression = 0; - - if ($this->bytesMax > 0) { - $progression = round($bytesTransferred / $this->bytesMax * 100); - } - - if (0 === $progression % 5) { - $this->io->overwrite(" Downloading: $progression%", false); - } - } - break; - - default: - break; - } - } - - protected function copy($url, $fileName, $fileUrl) - { - // Handle system proxy - $params = array('http' => array()); - - if (isset($_SERVER['HTTP_PROXY'])) { - // http(s):// is not supported in proxy - $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']); - - if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { - throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); - } - - $params['http'] = array( - 'proxy' => $proxy, - 'request_fulluri' => true, - ); - } - - if ($this->io->hasAuthorization($url)) { - $auth = $this->io->getAuthorization($url); - $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $params['http'] = array_merge($params['http'], array('header' => "Authorization: Basic $authStr\r\n")); - } - - $ctx = stream_context_create($params); - stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); - - $this->io->overwrite(" Downloading: connection...", false); - @copy($fileUrl, $fileName, $ctx); - $this->io->overwrite(" Downloading", false); - } - /** * Extract file to directory * diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php new file mode 100644 index 000000000..d24d0a4ea --- /dev/null +++ b/src/Composer/Util/RemoteFilesystem.php @@ -0,0 +1,157 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +use Composer\IO\IOInterface; + +/** + * @author François Pluchino + */ +class RemoteFilesystem +{ + protected $io; + private $bytesMax; + private $originUrl; + private $fileUrl; + private $fileName; + + /** + * Constructor. + * + * @param IOInterface $io The IO instance + */ + public function __construct(IOInterface $io) + { + $this->io = $io; + } + + /** + * Copy the remote file in local. + * + * @param string $originUrl The origin URL + * @param string $fileName The local filename + * @param string $fileUrl The file URL + * + * @throws \RuntimeException When opensll extension is disabled + */ + public function copy($originUrl, $fileName, $fileUrl) + { + $this->firstCall = true; + $this->originUrl = $originUrl; + $this->fileName = $fileName; + $this->fileUrl = $fileUrl; + $this->bytesMax = 0; + + // Handle system proxy + $params = array('http' => array()); + + if (isset($_SERVER['HTTP_PROXY'])) { + // http(s):// is not supported in proxy + $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']); + + if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { + throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); + } + + $params['http'] = array( + 'proxy' => $proxy, + 'request_fulluri' => true, + ); + } + + if ($this->io->hasAuthorization($originUrl)) { + $auth = $this->io->getAuthorization($originUrl); + $authStr = base64_encode($auth['username'] . ':' . $auth['password']); + $params['http'] = array_merge($params['http'], array('header' => "Authorization: Basic $authStr\r\n")); + } + + $ctx = stream_context_create($params); + stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); + + $this->io->overwrite(" Downloading: connection...", false); + @copy($fileUrl, $fileName, $ctx); + $this->io->overwrite(" Downloading", false); + } + + /** + * Get notification action. + * + * @param integer $notificationCode The notification code + * @param integer $severity The severity level + * @param string $message The message + * @param integer $messageCode The message code + * @param integer $bytesTransferred The loaded size + * @param integer $bytesMax The total size + */ + protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) + { + switch ($notificationCode) { + case STREAM_NOTIFY_AUTH_REQUIRED: + case STREAM_NOTIFY_FAILURE: + // for private repository returning 404 error when the authorization is incorrect + $auth = $this->io->getAuthorization($this->originUrl); + $ps = $this->firstCall && 404 === $messageCode + && null === $auth['username']; + + if (404 === $messageCode && !$this->firstCall) { + throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found"); + } + + $this->firstCall = false; + + // get authorization informations + if (401 === $messageCode || $ps) { + if (!$this->io->isInteractive()) { + $mess = "The '" . $this->fileUrl . "' URL not found"; + + if (401 === $code || $ps) { + $mess = "The '" . $this->fileUrl . "' URL required the authorization.\nYou must be used the interactive console"; + } + + throw new \RuntimeException($mess); + } + + $this->io->overwrite(' Authorization required:'); + $username = $this->io->ask(' Username: '); + $password = $this->io->askAndHideAnswer(' Password: '); + $this->io->setAuthorization($this->originUrl, $username, $password); + + $this->copy($this->originUrl, $this->fileName, $this->fileUrl); + } + break; + + case STREAM_NOTIFY_FILE_SIZE_IS: + if ($this->bytesMax < $bytesMax) { + $this->bytesMax = $bytesMax; + } + break; + + case STREAM_NOTIFY_PROGRESS: + if ($this->bytesMax > 0) { + $progression = 0; + + if ($this->bytesMax > 0) { + $progression = round($bytesTransferred / $this->bytesMax * 100); + } + + if (0 === $progression % 5) { + $this->io->overwrite(" Downloading: $progression%", false); + } + } + break; + + default: + break; + } + } +} \ No newline at end of file From ff0f833b3e8da0a2737feb0035fb36b7abc5379e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haso=C5=88?= Date: Wed, 15 Feb 2012 11:59:39 +0100 Subject: [PATCH 21/62] Added support for JSON_UNESCAPED_UNICODE and fixed parsing string --- src/Composer/Json/JsonFile.php | 54 +++++++++++++++++------ tests/Composer/Test/Json/JsonFileTest.php | 20 +++++++++ 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 7d4ab8cb6..ecd286d78 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -78,8 +78,9 @@ class JsonFile * * @param array $hash writes hash into json file * @param Boolean $prettyPrint If true, output is pretty-printed + * @param Boolean $unescapeUnicode If true, unicode chars in output are unescaped */ - public function write(array $hash, $prettyPrint = true) + public function write(array $hash, $prettyPrint = true, $unescapeUnicode = true) { $dir = dirname($this->path); if (!is_dir($dir)) { @@ -94,7 +95,7 @@ class JsonFile ); } } - file_put_contents($this->path, static::encode($hash, $prettyPrint)); + file_put_contents($this->path, static::encode($hash, $prettyPrint, $unescapeUnicode)); } /** @@ -105,17 +106,23 @@ class JsonFile * * @param array $hash Data to encode into a formatted JSON string * @param Boolean $prettyPrint If true, output is pretty-printed + * @param Boolean $unescapeUnicode If true, unicode chars in output are unescaped * @return string Indented version of the original JSON string */ - static public function encode(array $hash, $prettyPrint = true) + static public function encode(array $hash, $prettyPrint = true, $unescapeUnicode = true) { - if ($prettyPrint && defined('JSON_PRETTY_PRINT')) { - return json_encode($hash, JSON_PRETTY_PRINT); + if (version_compare(PHP_VERSION, '5.4', '>=')) { + $options = $prettyPrint ? JSON_PRETTY_PRINT : 0; + if ($unescapeUnicode) { + $options |= JSON_UNESCAPED_UNICODE; + } + + return json_encode($hash, $options); } $json = json_encode($hash); - if (!$prettyPrint) { + if (!$prettyPrint && !$unescapeUnicode) { return $json; } @@ -124,21 +131,43 @@ class JsonFile $strLen = strlen($json); $indentStr = ' '; $newLine = "\n"; - $prevChar = ''; $outOfQuotes = true; + $buffer = ''; + $noescape = 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 || '\\\\' === substr($json, $i-2, 2))) { + if ('"' === $char && $noescape) { $outOfQuotes = !$outOfQuotes; - } elseif (':' === $char && $outOfQuotes) { + } + + if (!$outOfQuotes) { + $buffer .= $char; + $noescape = '\\' === $char ? !$noescape : true; + continue; + } elseif ('' !== $buffer) { + if ($unescapeUnicode && function_exists('mb_convert_encoding')) { + // http://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha + $result .= preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function($match) { + return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE'); + }, $buffer.$char); + } else { + $result .= $buffer.$char; + } + + $buffer = ''; + continue; + } + + if (':' === $char) { // Add a space after the : character $char .= ' '; - } elseif (('}' === $char || ']' === $char) && $outOfQuotes) { + } elseif (('}' === $char || ']' === $char)) { $pos--; + $prevChar = substr($json, $i - 1, 1); if ('{' !== $prevChar && '[' !== $prevChar) { // If this character is the end of an element, @@ -153,12 +182,11 @@ class JsonFile } } - // 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) { + if (',' === $char || '{' === $char || '[' === $char) { $result .= $newLine; if ('{' === $char || '[' === $char) { @@ -169,8 +197,6 @@ class JsonFile $result .= $indentStr; } } - - $prevChar = $char; } return $result; diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 4ebf7b0c5..4ed016fa6 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -121,6 +121,26 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $this->assertJsonFormat($json, $data); } + public function testEscape() + { + $data = array("Metadata\\\"" => 'src/'); + $json = '{ + "Metadata\\\\\\"": "src\/" +}'; + + $this->assertJsonFormat($json, $data); + } + + public function testUnicode() + { + $data = array("Žluťoučký \" kůň" => "úpěl ďábelské ódy za €"); + $json = '{ + "Žluťoučký \" kůň": "úpěl ďábelské ódy za €" +}'; + + $this->assertJsonFormat($json, $data); + } + private function expectParseException($text, $json) { try { From 953f0992f71842b7453a1adf6ee86a4b35e7e0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Wed, 15 Feb 2012 13:11:29 +0100 Subject: [PATCH 22/62] Merge callback method of FileDownloader and VcsDriver Required corrections added --- src/Composer/Downloader/FileDownloader.php | 4 +- src/Composer/Repository/Vcs/VcsDriver.php | 87 +---------------- src/Composer/Util/RemoteFilesystem.php | 106 +++++++++++++++++---- 3 files changed, 91 insertions(+), 106 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index dad74fbc4..ef9ae8e6e 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -11,10 +11,10 @@ namespace Composer\Downloader; -use Composer\Util\RemoteFilesystem; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; +use Composer\Util\RemoteFilesystem; /** * Base downloader for file packages @@ -76,7 +76,7 @@ abstract class FileDownloader implements DownloaderInterface } $rfs = new RemoteFilesystem($this->io); - $rfs->copy($package->getSourceUrl(), $fileName, $url); + $rfs->copy($package->getSourceUrl(), $url, $fileName); $this->io->write(''); if (!file_exists($fileName)) { diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index 5a25fc1be..7008f41be 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -14,6 +14,7 @@ namespace Composer\Repository\Vcs; use Composer\IO\IOInterface; use Composer\Util\ProcessExecutor; +use Composer\Util\RemoteFilesystem; /** * A driver implementation for driver with authorization interaction. @@ -25,9 +26,6 @@ abstract class VcsDriver protected $url; protected $io; protected $process; - private $firstCall; - private $contentUrl; - private $content; /** * Constructor. @@ -41,7 +39,6 @@ abstract class VcsDriver $this->url = $url; $this->io = $io; $this->process = $process ?: new ProcessExecutor; - $this->firstCall = true; } /** @@ -68,85 +65,7 @@ abstract class VcsDriver */ protected function getContents($url) { - $this->contentUrl = $url; - $auth = $this->io->getAuthorization($this->url); - $params = array(); - - // add authorization to curl options - if ($this->io->hasAuthorization($this->url)) { - $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $params['http'] = array('header' => "Authorization: Basic $authStr\r\n"); - } else if (null !== $this->io->getLastUsername()) { - $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); - $params['http'] = array('header' => "Authorization: Basic $authStr\r\n"); - $this->io->setAuthorization($this->url, $this->io->getLastUsername(), $this->io->getLastPassword()); - } - - $ctx = stream_context_create($params); - stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); - - $content = @file_get_contents($url, false, $ctx); - - // content get after authorization - if (false === $content) { - $content = $this->content; - $this->content = null; - $this->contentUrl = null; - } - - return $content; - } - - /** - * Get notification action. - * - * @param integer $notificationCode The notification code - * @param integer $severity The severity level - * @param string $message The message - * @param integer $messageCode The message code - * @param integer $bytesTransferred The loaded size - * @param integer $bytesMax The total size - */ - protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) - { - switch ($notificationCode) { - case STREAM_NOTIFY_AUTH_REQUIRED: - case STREAM_NOTIFY_FAILURE: - // for private repository returning 404 error when the authorization is incorrect - $auth = $this->io->getAuthorization($this->url); - $ps = $this->firstCall && 404 === $messageCode - && null === $this->io->getLastUsername() - && null === $auth['username']; - - if (404 === $messageCode && !$this->firstCall) { - throw new \RuntimeException("The '" . $this->contentUrl . "' URL not found"); - } - - $this->firstCall = false; - - // get authorization informations - if (401 === $messageCode || $ps) { - if (!$this->io->isInteractive()) { - $mess = "The '" . $this->contentUrl . "' URL not found"; - - if (401 === $code || $ps) { - $mess = "The '" . $this->contentUrl . "' URL required the authorization.\nYou must be used the interactive console"; - } - - throw new \RuntimeException($mess); - } - - $this->io->write(array('', "Authorization for " . $this->contentUrl . ":")); - $username = $this->io->ask(' Username: '); - $password = $this->io->askAndHideAnswer(' Password: '); - $this->io->setAuthorization($this->url, $username, $password); - - $this->content = $this->getContents($this->contentUrl); - } - break; - - default: - break; - } + $rfs = new RemoteFilesystem($this->io); + return $rfs->getContents($this->url, $url, false); } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index d24d0a4ea..8fdc47b23 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -19,11 +19,14 @@ use Composer\IO\IOInterface; */ class RemoteFilesystem { - protected $io; + private $io; + private $firstCall; private $bytesMax; private $originUrl; private $fileUrl; private $fileName; + private $content; + private $progess; /** * Constructor. @@ -38,20 +41,52 @@ class RemoteFilesystem /** * Copy the remote file in local. * - * @param string $originUrl The origin URL - * @param string $fileName The local filename - * @param string $fileUrl The file URL + * @param string $originUrl The orgin URL + * @param string $fileUrl The file URL + * @param string $fileName the local filename + * @param boolean $progess Display the progression + */ + public function copy($originUrl, $fileUrl, $fileName, $progess = true) + { + $this->get($originUrl, $fileUrl, $fileName, $progess); + } + + /** + * Get the content. + * + * @param string $originUrl The orgin URL + * @param string $fileUrl The file URL + * @param boolean $progess Display the progression + * + * @return false|string The content + */ + public function getContents($originUrl, $fileUrl, $progess = true) + { + $this->get($originUrl, $fileUrl, null, $progess); + + return $this->content; + } + + /** + * Get file content or copy action. + * + * @param string $originUrl The orgin URL + * @param string $fileUrl The file URL + * @param string $fileName the local filename + * @param boolean $progess Display the progression * - * @throws \RuntimeException When opensll extension is disabled + * @throws \RuntimeException When the openssl extension is disabled */ - public function copy($originUrl, $fileName, $fileUrl) + protected function get($originUrl, $fileUrl, $fileName = null, $progess = true) { $this->firstCall = true; - $this->originUrl = $originUrl; - $this->fileName = $fileName; - $this->fileUrl = $fileUrl; $this->bytesMax = 0; - + $this->content = null; + $this->originUrl = $originUrl; + $this->fileUrl = $fileUrl; + $this->fileName = $fileName; + $this->progress = $progess; + // Handle system proxy $params = array('http' => array()); @@ -69,18 +104,35 @@ class RemoteFilesystem ); } + // add authorization in context if ($this->io->hasAuthorization($originUrl)) { $auth = $this->io->getAuthorization($originUrl); $authStr = base64_encode($auth['username'] . ':' . $auth['password']); $params['http'] = array_merge($params['http'], array('header' => "Authorization: Basic $authStr\r\n")); + + } else if (null !== $this->io->getLastUsername()) { + $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); + $params['http'] = array('header' => "Authorization: Basic $authStr\r\n"); + $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword()); } $ctx = stream_context_create($params); stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet'))); - $this->io->overwrite(" Downloading: connection...", false); - @copy($fileUrl, $fileName, $ctx); - $this->io->overwrite(" Downloading", false); + if ($this->progress) { + $this->io->overwrite(" Downloading: connection...", false); + } + + if (null !== $fileName) { + @copy($fileUrl, $fileName, $ctx); + + } else { + $this->content = @file_get_contents($fileUrl, false, $ctx); + } + + if ($this->progress) { + $this->io->overwrite(" Downloading", false); + } } /** @@ -100,8 +152,7 @@ class RemoteFilesystem case STREAM_NOTIFY_FAILURE: // for private repository returning 404 error when the authorization is incorrect $auth = $this->io->getAuthorization($this->originUrl); - $ps = $this->firstCall && 404 === $messageCode - && null === $auth['username']; + $ps = $this->firstCall && 404 === $messageCode && null === $auth['username']; if (404 === $messageCode && !$this->firstCall) { throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found"); @@ -112,21 +163,21 @@ class RemoteFilesystem // get authorization informations if (401 === $messageCode || $ps) { if (!$this->io->isInteractive()) { - $mess = "The '" . $this->fileUrl . "' URL not found"; + $mess = "The '" . $this->fileUrl . "' URL was not found"; if (401 === $code || $ps) { - $mess = "The '" . $this->fileUrl . "' URL required the authorization.\nYou must be used the interactive console"; + $mess = "The '" . $this->fileUrl . "' URL required the authorization.\nYou must be using the interactive console"; } throw new \RuntimeException($mess); } - $this->io->overwrite(' Authorization required:'); + $this->io->overwrite(' Authorization required (' .$this->getHostname($this->fileUrl).'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->originUrl, $username, $password); - $this->copy($this->originUrl, $this->fileName, $this->fileUrl); + $this->content = $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progess); } break; @@ -137,7 +188,7 @@ class RemoteFilesystem break; case STREAM_NOTIFY_PROGRESS: - if ($this->bytesMax > 0) { + if ($this->bytesMax > 0 && $this->progress) { $progression = 0; if ($this->bytesMax > 0) { @@ -154,4 +205,19 @@ class RemoteFilesystem break; } } + + /** + * Get the hostname. + * + * @param string $url The file URL + * + * @return string The hostname + */ + protected function getHostname($url) + { + $host = substr($url, strpos($url, '://') + 3); + $host = substr($host, 0, strpos($host, '/')); + + return $host; + } } \ No newline at end of file From bc232af018e1dc688de8bc106ea8d9a09621ac33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Wed, 15 Feb 2012 14:16:42 +0100 Subject: [PATCH 23/62] Bug fix display downloading info --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 8fdc47b23..54aca9356 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -177,7 +177,7 @@ class RemoteFilesystem $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->originUrl, $username, $password); - $this->content = $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progess); + $this->content = $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress); } break; From 7d5ba2d756204ce997c78ff1b28d68f852d8593d Mon Sep 17 00:00:00 2001 From: MattKetmo Date: Thu, 16 Feb 2012 17:26:42 +0100 Subject: [PATCH 24/62] [DownloadFile] Renamed wrapper directory after extract This fixes error when the extracted directory (from an archive) contains a folder with the same name. Example: $ mv test/test test mv: test/test and test/test are identical --- src/Composer/Downloader/FileDownloader.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 168705b5a..8ae89c93c 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -111,6 +111,13 @@ abstract class FileDownloader implements DownloaderInterface $contentDir = glob($path . '/*'); if (1 === count($contentDir)) { $contentDir = $contentDir[0]; + + // Rename the content directory to avoid error when moving up + // a child folder with the same name + $temporaryName = md5(time().rand()); + rename($contentDir, $temporaryName); + $contentDir = $temporaryName; + foreach (array_merge(glob($contentDir . '/.*'), glob($contentDir . '/*')) as $file) { if (trim(basename($file), '.')) { rename($file, $path . '/' . basename($file)); From 01cb2c5dd77c82d1253cd4cb0476ae5f7f2de2f8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 18:58:25 +0100 Subject: [PATCH 25/62] Fix typo --- src/Composer/Command/SelfUpdateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index febc78295..426fc6882 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -42,7 +42,7 @@ EOT { $ctx = StreamContextFactory::getContext(); - $latest = trim(file_get_contents('http://getcomposer.org/version'), false, $ctx); + $latest = trim(file_get_contents('http://getcomposer.org/version', false, $ctx)); if (Composer::VERSION !== $latest) { $output->writeln(sprintf("Updating to version %s.", $latest)); From 33fcb959eadb55af91dcb69fb837ae7dc3a44f11 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 20:28:02 +0100 Subject: [PATCH 26/62] Refactor JsonFile::encode to match json_encode --- src/Composer/Json/JsonFile.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index ecd286d78..2af32d32f 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -16,6 +16,16 @@ use Composer\Repository\RepositoryManager; use Composer\Composer; use Composer\Util\StreamContextFactory; +if (!defined('JSON_UNESCAPED_SLASHES')) { + define('JSON_UNESCAPED_SLASHES', 64); +} +if (!defined('JSON_PRETTY_PRINT')) { + define('JSON_PRETTY_PRINT', 128); +} +if (!defined('JSON_UNESCAPED_UNICODE')) { + define('JSON_UNESCAPED_UNICODE', 256); +} + /** * Reads/writes json files. * @@ -77,10 +87,9 @@ class JsonFile * Writes json file. * * @param array $hash writes hash into json file - * @param Boolean $prettyPrint If true, output is pretty-printed - * @param Boolean $unescapeUnicode If true, unicode chars in output are unescaped + * @param int $options json_encode options */ - public function write(array $hash, $prettyPrint = true, $unescapeUnicode = true) + public function write(array $hash, $options = 448) { $dir = dirname($this->path); if (!is_dir($dir)) { @@ -95,7 +104,7 @@ class JsonFile ); } } - file_put_contents($this->path, static::encode($hash, $prettyPrint, $unescapeUnicode)); + file_put_contents($this->path, static::encode($hash, $options)); } /** @@ -105,23 +114,20 @@ class JsonFile * 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 - * @param Boolean $unescapeUnicode If true, unicode chars in output are unescaped - * @return string Indented version of the original JSON string + * @param int $options json_encode options + * @return string Encoded json */ - static public function encode(array $hash, $prettyPrint = true, $unescapeUnicode = true) + static public function encode(array $hash, $options = 448) { if (version_compare(PHP_VERSION, '5.4', '>=')) { - $options = $prettyPrint ? JSON_PRETTY_PRINT : 0; - if ($unescapeUnicode) { - $options |= JSON_UNESCAPED_UNICODE; - } - return json_encode($hash, $options); } $json = json_encode($hash); + $prettyPrint = (Boolean) ($options & JSON_PRETTY_PRINT); + $unescapeUnicode = (Boolean) ($options & JSON_UNESCAPED_UNICODE); + if (!$prettyPrint && !$unescapeUnicode) { return $json; } From d1d9c715c9aa9167cb04ecef1bbb1595221bbd49 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 20:37:05 +0100 Subject: [PATCH 27/62] Add newline at the end of formatted jsons --- src/Composer/Json/JsonFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 2af32d32f..307139a6f 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -104,7 +104,7 @@ class JsonFile ); } } - file_put_contents($this->path, static::encode($hash, $options)); + file_put_contents($this->path, static::encode($hash, $options). ($options & JSON_PRETTY_PRINT ? "\n" : '')); } /** From 23aade21f0072fb418f1deeda597d96eff330570 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 20:38:43 +0100 Subject: [PATCH 28/62] Show empty default value when one is specified --- src/Composer/Command/Helper/DialogHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/Helper/DialogHelper.php b/src/Composer/Command/Helper/DialogHelper.php index 674b03800..d08ac1946 100644 --- a/src/Composer/Command/Helper/DialogHelper.php +++ b/src/Composer/Command/Helper/DialogHelper.php @@ -28,9 +28,9 @@ class DialogHelper extends BaseDialogHelper * * @return string */ - public function getQuestion($question, $default, $sep = ':') + public function getQuestion($question, $default = null, $sep = ':') { - return $default ? + return $default !== null ? sprintf('%s [%s]%s ', $question, $default, $sep) : sprintf('%s%s ', $question, $sep); } From f48cfb4fdfa349b981a5cd77eb8d30b6561957cc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 20:39:59 +0100 Subject: [PATCH 29/62] Avoid requirement on composer.json to search for requirements --- src/Composer/Command/InitCommand.php | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 93023d2ad..ef075f9f7 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -13,6 +13,9 @@ namespace Composer\Command; use Composer\Json\JsonFile; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\ComposerRepository; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -21,10 +24,12 @@ use Symfony\Component\Process\ExecutableFinder; /** * @author Justin Rainbow + * @author Jordi Boggiano */ class InitCommand extends Command { private $gitConfig; + private $repos; public function parseAuthorString($author) { @@ -214,22 +219,23 @@ EOT protected function findPackages($name) { - $composer = $this->getComposer(); - $packages = array(); - // create local repo, this contains all packages that are installed in the local project - $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + // init repos + if (!$this->repos) { + $this->repos = new CompositeRepository(array( + new PlatformRepository, + new ComposerRepository(array('url' => 'http://packagist.org')) + )); + } $token = strtolower($name); - foreach ($composer->getRepositoryManager()->getRepositories() as $repository) { - foreach ($repository->getPackages() as $package) { - if (false === ($pos = strpos($package->getName(), $token))) { - continue; - } - - $packages[] = $package; + foreach ($this->repos->getPackages() as $package) { + if (false === ($pos = strpos($package->getName(), $token))) { + continue; } + + $packages[] = $package; } return $packages; From 9a2204cd74d3aaac464ab6d636503f512816b1da Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 20:40:57 +0100 Subject: [PATCH 30/62] Improve username detection and other minor fixes --- src/Composer/Command/InitCommand.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index ef075f9f7..56eb2e465 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -43,8 +43,8 @@ class InitCommand extends Command } throw new \InvalidArgumentException( - 'Invalid author string. Must be in the format:'. - ' John Smith ' + 'Invalid author string. Must be in the format: '. + 'John Smith ' ); } @@ -150,6 +150,10 @@ EOT $name = basename($cwd); if (isset($git['github.user'])) { $name = $git['github.user'] . '/' . $name; + } elseif (!empty($_SERVER['USERNAME'])) { + $name = $_SERVER['USERNAME'] . '/' . $name; + } elseif (get_current_user()) { + $name = get_current_user() . '/' . $name; } else { // package names must be in the format foo/bar $name = $name . '/' . $name; @@ -158,7 +162,7 @@ EOT $name = $dialog->askAndValidate( $output, - $dialog->getQuestion('Package name', $name), + $dialog->getQuestion('Package name (/)', $name), function ($value) use ($name) { if (null === $value) { return $name; @@ -371,6 +375,6 @@ EOT } } - file_put_contents($ignoreFile, $contents . $vendor); + file_put_contents($ignoreFile, $contents . $vendor. "\n"); } } \ No newline at end of file From ee7b68c04975992bd06efe725b6f763889d8529c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 20:41:12 +0100 Subject: [PATCH 31/62] Allow people to manually enter requirements --- src/Composer/Command/InitCommand.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 56eb2e465..80c487935 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -273,17 +273,23 @@ EOT return false; } + if (!is_numeric($selection) && preg_match('{^\s*(\S+) +(\S.*)\s*}', $selection, $matches)) { + return $matches[1].' '.$matches[2]; + } + if (!isset($matches[(int) $selection])) { throw new \Exception('Not a valid selection'); } - return $matches[(int) $selection]; + $package = $matches[(int) $selection]; + + return sprintf('%s %s', $package->getName(), $package->getPrettyVersion()); }; - $package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add', false, ':'), $validator, 3); + $package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or a couple if it is not listed', false, ':'), $validator, 3); if (false !== $package) { - $requires[] = sprintf('%s %s', $package->getName(), $package->getPrettyVersion()); + $requires[] = $package; } } } @@ -300,7 +306,7 @@ EOT { $requires = array(); foreach ($requirements as $requirement) { - list($packageName, $packageVersion) = explode(" ", $requirement); + list($packageName, $packageVersion) = explode(" ", $requirement, 2); $requires[$packageName] = $packageVersion; } From 1e6633b3c37bf08764c62d7beea0f469854bc549 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 16 Feb 2012 20:42:47 +0100 Subject: [PATCH 32/62] Allow search command to work without a composer.json --- src/Composer/Command/SearchCommand.php | 42 +++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Composer/Command/SearchCommand.php b/src/Composer/Command/SearchCommand.php index 4a937539d..accaf853f 100644 --- a/src/Composer/Command/SearchCommand.php +++ b/src/Composer/Command/SearchCommand.php @@ -15,6 +15,9 @@ namespace Composer\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Output\OutputInterface; +use Composer\Repository\CompositeRepository; +use Composer\Repository\PlatformRepository; +use Composer\Repository\ComposerRepository; /** * @author Robert Schönthal @@ -40,27 +43,32 @@ EOT protected function execute(InputInterface $input, OutputInterface $output) { - $composer = $this->getComposer(); - - // create local repo, this contains all packages that are installed in the local project - $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + // init repos + $platformRepo = new PlatformRepository; + if ($composer = $this->getComposer(false)) { + $localRepo = $composer->getRepositoryManager()->getLocalRepository(); + $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); + $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); + } else { + $output->writeln('No composer.json found in the current directory, showing packages from packagist.org'); + $installedRepo = $platformRepo; + $repos = new CompositeRepository(array($installedRepo, new ComposerRepository(array('url' => 'http://packagist.org')))); + } $tokens = array_map('strtolower', $input->getArgument('tokens')); - foreach ($composer->getRepositoryManager()->getRepositories() as $repository) { - foreach ($repository->getPackages() as $package) { - foreach ($tokens as $token) { - if (false === ($pos = strpos($package->getName(), $token))) { - continue; - } + foreach ($repos->getPackages() as $package) { + foreach ($tokens as $token) { + if (false === ($pos = strpos($package->getName(), $token))) { + continue; + } - $state = $localRepo->hasPackage($package) ? 'installed' : $state = 'available'; + $state = $localRepo->hasPackage($package) ? 'installed' : $state = 'available'; - $name = substr($package->getPrettyName(), 0, $pos) - . '' . substr($package->getPrettyName(), $pos, strlen($token)) . '' - . substr($package->getPrettyName(), $pos + strlen($token)); - $output->writeln($state . ': ' . $name . ' ' . $package->getPrettyVersion() . ''); - continue 2; - } + $name = substr($package->getPrettyName(), 0, $pos) + . '' . substr($package->getPrettyName(), $pos, strlen($token)) . '' + . substr($package->getPrettyName(), $pos + strlen($token)); + $output->writeln($state . ': ' . $name . ' ' . $package->getPrettyVersion() . ''); + continue 2; } } } From 2467456d3f65f91af1a8c51e3c6d28fad3a87947 Mon Sep 17 00:00:00 2001 From: Wookieb Date: Thu, 16 Feb 2012 21:43:12 +0100 Subject: [PATCH 33/62] Fix installer that create vendor and bin directory even if --dry-run parameter provided * Move directories creation from constructor to "install" and "update" method * Tests for LibraryInstaller --- src/Composer/Installer/LibraryInstaller.php | 12 ++++-- .../Test/Installer/LibraryInstallerTest.php | 38 +++++++++++++------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 0873ed63c..1f94f4071 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -53,10 +53,8 @@ class LibraryInstaller implements InstallerInterface $this->type = $type; $this->filesystem = new Filesystem(); - $this->filesystem->ensureDirectoryExists($vendorDir); - $this->filesystem->ensureDirectoryExists($binDir); - $this->vendorDir = realpath($vendorDir); - $this->binDir = realpath($binDir); + $this->vendorDir = rtrim($vendorDir, '/'); + $this->binDir = rtrim($binDir, '/'); } /** @@ -82,6 +80,9 @@ class LibraryInstaller implements InstallerInterface { $downloadPath = $this->getInstallPath($package); + $this->filesystem->ensureDirectoryExists($this->vendorDir); + $this->filesystem->ensureDirectoryExists($this->binDir); + // remove the binaries if it appears the package files are missing if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) { $this->removeBinaries($package); @@ -105,6 +106,9 @@ class LibraryInstaller implements InstallerInterface $downloadPath = $this->getInstallPath($initial); + $this->filesystem->ensureDirectoryExists($this->vendorDir); + $this->filesystem->ensureDirectoryExists($this->binDir); + $this->removeBinaries($initial); $this->downloadManager->update($initial, $target, $downloadPath); $this->installBinaries($target); diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 561143db2..3399345f0 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -22,22 +22,22 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase private $binDir; private $dm; private $repository; - private $library; private $io; + private $fs; protected function setUp() { - $fs = new Filesystem; + $this->fs = new Filesystem; $this->vendorDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'composer-test-vendor'; if (is_dir($this->vendorDir)) { - $fs->removeDirectory($this->vendorDir); + $this->fs->removeDirectory($this->vendorDir); } mkdir($this->vendorDir); $this->binDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'composer-test-bin'; if (is_dir($this->binDir)) { - $fs->removeDirectory($this->binDir); + $this->fs->removeDirectory($this->binDir); } mkdir($this->binDir); @@ -52,16 +52,20 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase ->getMock(); } - public function testInstallerCreation() + public function testInstallerCreationShouldNotCreateVendorDirectory() { - $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); - $this->assertTrue(is_dir($this->vendorDir)); + $this->fs->removeDirectory($this->vendorDir); + + new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); + $this->assertFileNotExists($this->vendorDir); + } - $file = sys_get_temp_dir().'/file'; - touch($file); + public function testInstallerCreationShouldNotCreateBinDirectory() + { + $this->fs->removeDirectory($this->binDir); - $this->setExpectedException('RuntimeException'); - $library = new LibraryInstaller($file, $this->binDir, $this->dm, $this->repository, $this->io); + new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); + $this->assertFileNotExists($this->binDir); } public function testIsInstalled() @@ -79,6 +83,10 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase $this->assertFalse($library->isInstalled($package)); } + /** + * @depends testInstallerCreationShouldNotCreateVendorDirectory + * @depends testInstallerCreationShouldNotCreateBinDirectory + */ public function testInstall() { $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); @@ -100,8 +108,14 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase ->with($package); $library->install($package); + $this->assertFileExists($this->vendorDir, 'Vendor dir should be created'); + $this->assertFileExists($this->binDir, 'Bin dir should be created'); } + /** + * @depends testInstallerCreationShouldNotCreateVendorDirectory + * @depends testInstallerCreationShouldNotCreateBinDirectory + */ public function testUpdate() { $library = new LibraryInstaller($this->vendorDir, $this->binDir, $this->dm, $this->repository, $this->io); @@ -135,6 +149,8 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase ->with($target); $library->update($initial, $target); + $this->assertFileExists($this->vendorDir, 'Vendor dir should be created'); + $this->assertFileExists($this->binDir, 'Bin dir should be created'); $this->setExpectedException('InvalidArgumentException'); From dbfbbab904bc549bc876e71f8c5e7b3b0e0ade6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Thu, 16 Feb 2012 23:41:26 +0100 Subject: [PATCH 34/62] Add changes requested --- src/Composer/Util/RemoteFilesystem.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 036c0b2fa..2d4be57d6 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -58,7 +58,7 @@ class RemoteFilesystem * @param string $fileUrl The file URL * @param boolean $progess Display the progression * - * @return false|string The content + * @return string The content */ public function getContents($originUrl, $fileUrl, $progess = true) { @@ -75,7 +75,7 @@ class RemoteFilesystem * @param string $fileName the local filename * @param boolean $progess Display the progression * - * @throws \RuntimeException When the openssl extension is disabled + * @throws \RuntimeException When the file could not be downloaded */ protected function get($originUrl, $fileUrl, $fileName = null, $progess = true) { @@ -92,8 +92,7 @@ class RemoteFilesystem if ($this->io->hasAuthorization($originUrl)) { $auth = $this->io->getAuthorization($originUrl); $authStr = base64_encode($auth['username'] . ':' . $auth['password']); - $options['http']['header'] = "Authorization: Basic $authStr\r\n"; - + $options['http']['header'] = "Authorization: Basic $authStr\r\n"; } else if (null !== $this->io->getLastUsername()) { $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); @@ -107,15 +106,19 @@ class RemoteFilesystem } if (null !== $fileName) { - @copy($fileUrl, $fileName, $ctx); - + $result = @copy($fileUrl, $fileName, $ctx); } else { - $this->content = @file_get_contents($fileUrl, false, $ctx); + $result = @file_get_contents($fileUrl, false, $ctx); + $this->content = $result; } if ($this->progress) { $this->io->overwrite(" Downloading", false); } + + if (false === $result) { + throw new \RuntimeException("the '$fileUrl' file could not be downloaded"); + } } /** From ea05f678fd58b64b86bffcb50a58580899831769 Mon Sep 17 00:00:00 2001 From: Logan Aube Date: Thu, 16 Feb 2012 19:42:19 -0500 Subject: [PATCH 35/62] Exception mentioned PEAR repository when should be VCS, was misleading --- src/Composer/Repository/VcsRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 7a1dcc022..1d6e82ab3 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -20,7 +20,7 @@ class VcsRepository extends ArrayRepository public function __construct(array $config, IOInterface $io, array $drivers = null) { if (!filter_var($config['url'], FILTER_VALIDATE_URL)) { - throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']); + throw new \UnexpectedValueException('Invalid url given for VCS repository: '.$config['url']); } $this->drivers = $drivers ?: array( From e621495c4d6be6d53a7920987f95de658f576289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haso=C5=88?= Date: Fri, 17 Feb 2012 10:06:59 +0100 Subject: [PATCH 36/62] Added support for JSON_UNESCAPED_SLASHES --- src/Composer/Json/JsonFile.php | 24 +++++++++++-------- tests/Composer/Test/Json/JsonFileTest.php | 28 +++++++++++++++++++---- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 307139a6f..0fc0105b3 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -113,22 +113,23 @@ class JsonFile * 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 mixed $data Data to encode into a formatted JSON string * @param int $options json_encode options * @return string Encoded json */ - static public function encode(array $hash, $options = 448) + static public function encode($data, $options = 448) { if (version_compare(PHP_VERSION, '5.4', '>=')) { - return json_encode($hash, $options); + return json_encode($data, $options); } - $json = json_encode($hash); + $json = json_encode($data); $prettyPrint = (Boolean) ($options & JSON_PRETTY_PRINT); $unescapeUnicode = (Boolean) ($options & JSON_UNESCAPED_UNICODE); + $unescapeSlashes = (Boolean) ($options & JSON_UNESCAPED_SLASHES); - if (!$prettyPrint && !$unescapeUnicode) { + if (!$prettyPrint && !$unescapeUnicode && !$unescapeSlashes) { return $json; } @@ -155,15 +156,18 @@ class JsonFile $noescape = '\\' === $char ? !$noescape : true; continue; } elseif ('' !== $buffer) { + if ($unescapeSlashes) { + $buffer = str_replace('\\/', '/', $buffer); + } + if ($unescapeUnicode && function_exists('mb_convert_encoding')) { // http://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha - $result .= preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function($match) { + $buffer = preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function($match) { return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE'); - }, $buffer.$char); - } else { - $result .= $buffer.$char; + }, $buffer); } + $result .= $buffer.$char; $buffer = ''; continue; } @@ -213,7 +217,7 @@ class JsonFile * * @param string $json json string * - * @return array + * @return mixed */ static public function parseJson($json) { diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 4ed016fa6..00711bb11 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -107,7 +107,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase { $data = array('name' => 'composer/composer'); $json = '{ - "name": "composer\/composer" + "name": "composer/composer" }'; $this->assertJsonFormat($json, $data); } @@ -116,7 +116,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase { $data = array('Metadata\\' => 'src/'); $json = '{ - "Metadata\\\\": "src\/" + "Metadata\\\\": "src/" }'; $this->assertJsonFormat($json, $data); } @@ -125,7 +125,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase { $data = array("Metadata\\\"" => 'src/'); $json = '{ - "Metadata\\\\\\"": "src\/" + "Metadata\\\\\\"": "src/" }'; $this->assertJsonFormat($json, $data); @@ -141,6 +141,20 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $this->assertJsonFormat($json, $data); } + public function testEscapedSlashes() + { + $data = "\\/fooƌ"; + + $this->assertJsonFormat('"\\\\\\/fooƌ"', $data, JSON_UNESCAPED_UNICODE); + } + + public function testEscapedUnicode() + { + $data = "ƌ"; + + $this->assertJsonFormat('"\\u018c"', $data, 0); + } + private function expectParseException($text, $json) { try { @@ -151,11 +165,15 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase } } - private function assertJsonFormat($json, $data) + private function assertJsonFormat($json, $data, $options = null) { $file = new JsonFile('composer.json'); - $this->assertEquals($json, $file->encode($data)); + if (null === $options) { + $this->assertEquals($json, $file->encode($data)); + } else { + $this->assertEquals($json, $file->encode($data, $options)); + } } } From 89b0c8b2e77818a6ceb8380c641745affd333989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=E7ois=20Pluchino?= Date: Fri, 17 Feb 2012 10:28:33 +0100 Subject: [PATCH 37/62] Restore the original file mode --- bin/compile | 0 bin/composer | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 bin/compile mode change 100644 => 100755 bin/composer diff --git a/bin/compile b/bin/compile old mode 100644 new mode 100755 diff --git a/bin/composer b/bin/composer old mode 100644 new mode 100755 From db03b7bbbdb61f2de8b7485b24f8c993fd111ba0 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 11:50:36 +0100 Subject: [PATCH 38/62] Fix line endings --- src/Composer/Downloader/FileDownloader.php | 1 + src/Composer/Util/RemoteFilesystem.php | 36 +++++++++++----------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index b830986cd..6a4d68103 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -1,4 +1,5 @@ firstCall = true; $this->bytesMax = 0; - $this->content = null; - $this->originUrl = $originUrl; - $this->fileUrl = $fileUrl; + $this->content = null; + $this->originUrl = $originUrl; + $this->fileUrl = $fileUrl; $this->fileName = $fileName; - $this->progress = $progess; - + $this->progress = $progess; + // add authorization in context $options = array(); - if ($this->io->hasAuthorization($originUrl)) { + if ($this->io->hasAuthorization($originUrl)) { $auth = $this->io->getAuthorization($originUrl); $authStr = base64_encode($auth['username'] . ':' . $auth['password']); $options['http']['header'] = "Authorization: Basic $authStr\r\n"; - } else if (null !== $this->io->getLastUsername()) { - $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); - $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); - $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword()); - } - - $ctx = StreamContextFactory::getContext($options, array('notification' => array($this, 'callbackGet'))); - + } else if (null !== $this->io->getLastUsername()) { + $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); + $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); + $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword()); + } + + $ctx = StreamContextFactory::getContext($options, array('notification' => array($this, 'callbackGet'))); + if ($this->progress) { $this->io->overwrite(" Downloading: connection...", false); } @@ -110,8 +110,8 @@ class RemoteFilesystem } else { $result = @file_get_contents($fileUrl, false, $ctx); $this->content = $result; - } - + } + if ($this->progress) { $this->io->overwrite(" Downloading", false); } From 844c43092f69fb8cfa66c5c1f94bb65c750020a7 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 11:53:09 +0100 Subject: [PATCH 39/62] Fix re-entrant behavior --- src/Composer/Util/RemoteFilesystem.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 47368955c..0ce391ace 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -74,12 +74,13 @@ class RemoteFilesystem * @param string $fileUrl The file URL * @param string $fileName the local filename * @param boolean $progess Display the progression + * @param boolean $firstCall Whether this is the first attempt at fetching this resource * * @throws \RuntimeException When the file could not be downloaded */ - protected function get($originUrl, $fileUrl, $fileName = null, $progess = true) + protected function get($originUrl, $fileUrl, $fileName = null, $progess = true, $firstCall = true) { - $this->firstCall = true; + $this->firstCall = $firstCall; $this->bytesMax = 0; $this->content = null; $this->originUrl = $originUrl; @@ -163,7 +164,7 @@ class RemoteFilesystem $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->originUrl, $username, $password); - $this->content = $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress); + $this->content = $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress, false); } break; From 8b7a64ab28cf81cbcb79ba9c8238c73de064a99d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 11:53:38 +0100 Subject: [PATCH 40/62] Limit progress output, mitigates #283 --- src/Composer/Util/RemoteFilesystem.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 0ce391ace..0f245b81b 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -27,6 +27,7 @@ class RemoteFilesystem private $fileName; private $content; private $progess; + private $lastProgress; /** * Constructor. @@ -87,6 +88,7 @@ class RemoteFilesystem $this->fileUrl = $fileUrl; $this->fileName = $fileName; $this->progress = $progess; + $this->lastProgress = null; // add authorization in context $options = array(); @@ -182,7 +184,8 @@ class RemoteFilesystem $progression = round($bytesTransferred / $this->bytesMax * 100); } - if (0 === $progression % 5) { + if ((0 === $progression % 5) && $progression !== $this->lastProgress) { + $this->lastProgress = $progression; $this->io->overwrite(" Downloading: $progression%", false); } } From 60fddb2c09b40a767e997ca34a4801fafaa649e2 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 12:13:56 +0100 Subject: [PATCH 41/62] Use parse_url instead of manual parsing --- src/Composer/Util/RemoteFilesystem.php | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 0f245b81b..eedba8e64 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -96,7 +96,7 @@ class RemoteFilesystem $auth = $this->io->getAuthorization($originUrl); $authStr = base64_encode($auth['username'] . ':' . $auth['password']); $options['http']['header'] = "Authorization: Basic $authStr\r\n"; - } else if (null !== $this->io->getLastUsername()) { + } elseif (null !== $this->io->getLastUsername()) { $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword()); $options['http'] = array('header' => "Authorization: Basic $authStr\r\n"); $this->io->setAuthorization($originUrl, $this->io->getLastUsername(), $this->io->getLastPassword()); @@ -161,7 +161,7 @@ class RemoteFilesystem throw new \RuntimeException($mess); } - $this->io->overwrite(' Authorization required (' .$this->getHostname($this->fileUrl).'):'); + $this->io->overwrite(' Authorization required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->originUrl, $username, $password); @@ -195,19 +195,4 @@ class RemoteFilesystem break; } } - - /** - * Get the hostname. - * - * @param string $url The file URL - * - * @return string The hostname - */ - protected function getHostname($url) - { - $host = substr($url, strpos($url, '://') + 3); - $host = substr($host, 0, strpos($host, '/')); - - return $host; - } } \ No newline at end of file From 35cca1ed7f7fbef5f9da0f9f75068c9d6cbe8174 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 12:18:48 +0100 Subject: [PATCH 42/62] Do not reveal input length --- src/Composer/IO/ConsoleIO.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index 0169d2908..e1d33b5d2 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -129,11 +129,7 @@ class ConsoleIO implements IOInterface $value = rtrim(shell_exec($command)); unlink($vbscript); - for ($i = 0; $i < strlen($value); ++$i) { - $this->write('*', false); - } - - $this->write(''); + $this->write('***'); return $value; } From a8aef7e2036dfa4d834e0e410acb64e7397ccf74 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 12:19:29 +0100 Subject: [PATCH 43/62] Text tweaks --- src/Composer/Util/RemoteFilesystem.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index eedba8e64..c782ea83e 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -120,7 +120,7 @@ class RemoteFilesystem } if (false === $result) { - throw new \RuntimeException("the '$fileUrl' file could not be downloaded"); + throw new \RuntimeException("The '$fileUrl' file could not be downloaded"); } } @@ -141,7 +141,7 @@ class RemoteFilesystem case STREAM_NOTIFY_FAILURE: // for private repository returning 404 error when the authorization is incorrect $auth = $this->io->getAuthorization($this->originUrl); - $ps = $this->firstCall && 404 === $messageCode && null === $auth['username']; + $attemptAuthentication = $this->firstCall && 404 === $messageCode && null === $auth['username']; if (404 === $messageCode && !$this->firstCall) { throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found"); @@ -150,18 +150,18 @@ class RemoteFilesystem $this->firstCall = false; // get authorization informations - if (401 === $messageCode || $ps) { + if (401 === $messageCode || $attemptAuthentication) { if (!$this->io->isInteractive()) { $mess = "The '" . $this->fileUrl . "' URL was not found"; - if (401 === $code || $ps) { - $mess = "The '" . $this->fileUrl . "' URL required the authorization.\nYou must be using the interactive console"; + if (401 === $code || $attemptAuthentication) { + $mess = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console"; } throw new \RuntimeException($mess); } - $this->io->overwrite(' Authorization required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); + $this->io->overwrite(' Authentication required ('.parse_url($this->fileUrl, PHP_URL_HOST).'):'); $username = $this->io->ask(' Username: '); $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->originUrl, $username, $password); From 4d7ecdcb6916e5225a4bc73316b8868db123f0be Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 12:35:42 +0100 Subject: [PATCH 44/62] Fix authentication behavior when re-entering get() --- src/Composer/Util/RemoteFilesystem.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index c782ea83e..89325720f 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -25,7 +25,7 @@ class RemoteFilesystem private $originUrl; private $fileUrl; private $fileName; - private $content; + private $result; private $progess; private $lastProgress; @@ -46,10 +46,14 @@ class RemoteFilesystem * @param string $fileUrl The file URL * @param string $fileName the local filename * @param boolean $progess Display the progression + * + * @return Boolean true */ public function copy($originUrl, $fileUrl, $fileName, $progess = true) { $this->get($originUrl, $fileUrl, $fileName, $progess); + + return $this->result; } /** @@ -65,7 +69,7 @@ class RemoteFilesystem { $this->get($originUrl, $fileUrl, null, $progess); - return $this->content; + return $this->result; } /** @@ -83,7 +87,7 @@ class RemoteFilesystem { $this->firstCall = $firstCall; $this->bytesMax = 0; - $this->content = null; + $this->result = null; $this->originUrl = $originUrl; $this->fileUrl = $fileUrl; $this->fileName = $fileName; @@ -112,14 +116,18 @@ class RemoteFilesystem $result = @copy($fileUrl, $fileName, $ctx); } else { $result = @file_get_contents($fileUrl, false, $ctx); - $this->content = $result; + } + + // avoid overriding if content was loaded by a sub-call to get() + if (null === $this->result) { + $this->result = $result; } if ($this->progress) { $this->io->overwrite(" Downloading", false); } - if (false === $result) { + if (false === $this->result) { throw new \RuntimeException("The '$fileUrl' file could not be downloaded"); } } @@ -166,7 +174,7 @@ class RemoteFilesystem $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->originUrl, $username, $password); - $this->content = $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress, false); + $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress, false); } break; From b2076c24624b35ba9781cec050ce65130817d968 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 16:59:26 +0100 Subject: [PATCH 45/62] Re-order repos so that packagist always has the lowest priority --- src/Composer/Factory.php | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index e84750e43..ada425073 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -67,7 +67,7 @@ class Factory $binDir = getenv('COMPOSER_BIN_DIR') ?: $packageConfig['config']['bin-dir']; // initialize repository manager - $rm = $this->createRepositoryManager($io, $vendorDir); + $rm = $this->createRepositoryManager($io); // initialize download manager $dm = $this->createDownloadManager($io); @@ -75,15 +75,17 @@ class Factory // 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'))); + $this->addPackagistRepository($rm); } + $this->addLocalRepository($rm, $vendorDir); + + // load package + $loader = new Package\Loader\RootPackageLoader($rm); + $package = $loader->load($packageConfig); + // 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)); @@ -102,7 +104,6 @@ class Factory 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'); @@ -111,6 +112,16 @@ class Factory return $rm; } + protected function addLocalRepository(RepositoryManager $rm, $vendorDir) + { + $rm->setLocalRepository(new Repository\FilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json'))); + } + + protected function addPackagistRepository(RepositoryManager $rm) + { + $rm->addRepository(new Repository\ComposerRepository(array('url' => 'http://packagist.org'))); + } + protected function createDownloadManager(IOInterface $io) { $dm = new Downloader\DownloadManager(); From 70bea8fe57bd8f23ff9c3661738da663058a317d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 17 Feb 2012 17:01:45 +0100 Subject: [PATCH 46/62] Fix previous commit --- src/Composer/Factory.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index ada425073..e4c81e824 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -14,6 +14,7 @@ namespace Composer; use Composer\Json\JsonFile; use Composer\IO\IOInterface; +use Composer\Repository\RepositoryManager; /** * Creates an configured instance of composer. @@ -69,23 +70,24 @@ class Factory // initialize repository manager $rm = $this->createRepositoryManager($io); - // initialize download manager - $dm = $this->createDownloadManager($io); - - // initialize installation manager - $im = $this->createInstallationManager($rm, $dm, $vendorDir, $binDir, $io); - // load default repository unless it's explicitly disabled if (!isset($packageConfig['repositories']['packagist']) || $packageConfig['repositories']['packagist'] !== false) { $this->addPackagistRepository($rm); } + // load local repository $this->addLocalRepository($rm, $vendorDir); // load package $loader = new Package\Loader\RootPackageLoader($rm); $package = $loader->load($packageConfig); + // initialize download manager + $dm = $this->createDownloadManager($io); + + // initialize installation manager + $im = $this->createInstallationManager($rm, $dm, $vendorDir, $binDir, $io); + // 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)); @@ -101,9 +103,9 @@ class Factory return $composer; } - protected function createRepositoryManager(IOInterface $io, $vendorDir) + protected function createRepositoryManager(IOInterface $io) { - $rm = new Repository\RepositoryManager($io); + $rm = new RepositoryManager($io); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository'); From 28d3d4b1301717850eea952d7fc9363463452251 Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Sat, 18 Feb 2012 00:00:38 +0100 Subject: [PATCH 47/62] Added a NullIO --- src/Composer/IO/NullIO.php | 122 +++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/Composer/IO/NullIO.php diff --git a/src/Composer/IO/NullIO.php b/src/Composer/IO/NullIO.php new file mode 100644 index 000000000..4d854b534 --- /dev/null +++ b/src/Composer/IO/NullIO.php @@ -0,0 +1,122 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\IO; + +/** + * IOInterface that is not interactive and never writes the output + * + * @author Christophe Coevoet + */ +class NullIO implements IOInterface +{ + /** + * {@inheritDoc} + */ + public function isInteractive() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function write($messages, $newline = true) + { + } + + /** + * {@inheritDoc} + */ + public function overwrite($messages, $newline = true, $size = 80) + { + } + + /** + * {@inheritDoc} + */ + public function ask($question, $default = null) + { + return $default; + } + + /** + * {@inheritDoc} + */ + public function askConfirmation($question, $default = true) + { + return $default; + } + + /** + * {@inheritDoc} + */ + public function askAndValidate($question, $validator, $attempts = false, $default = null) + { + return $default; + } + + /** + * {@inheritDoc} + */ + public function askAndHideAnswer($question) + { + return null; + } + + /** + * {@inheritDoc} + */ + public function getLastUsername() + { + return null; + } + + /** + * {@inheritDoc} + */ + public function getLastPassword() + { + return null; + } + + /** + * {@inheritDoc} + */ + public function getAuthorizations() + { + return array(); + } + + /** + * {@inheritDoc} + */ + public function hasAuthorization($repositoryName) + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getAuthorization($repositoryName) + { + return array('username' => null, 'password' => null); + } + + /** + * {@inheritDoc} + */ + public function setAuthorization($repositoryName, $username, $password = null) + { + } +} From cb7f656fa99d704389fe356b6bf8acf2facf01b0 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sat, 18 Feb 2012 00:09:25 +0100 Subject: [PATCH 48/62] Off by one error in the solver - 1 cannot be false Fixes #133, #160, #162, #177, #289 --- src/Composer/DependencyResolver/Solver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 2fa38b55e..b865aa82e 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -1477,7 +1477,7 @@ class Solver $l1retry = false; - if (!$num && !$l1num) { + if (!$num && !--$l1num) { // all level 1 literals done break 2; } From 9ede082371fd24cb52783aa2bcfaedf9750f4120 Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Sat, 18 Feb 2012 11:36:11 +0100 Subject: [PATCH 49/62] Skip tests if mbstring is not installed --- tests/Composer/Test/Json/JsonFileTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index 00711bb11..fc205eceb 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -133,6 +133,9 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase public function testUnicode() { + if (!function_exists('mb_convert_encoding')) { + $this->markTestSkipped('Test requires the mbstring extension'); + } $data = array("Žluťoučký \" kůň" => "úpěl ďábelské ódy za €"); $json = '{ "Žluťoučký \" kůň": "úpěl ďábelské ódy za €" @@ -143,6 +146,9 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase public function testEscapedSlashes() { + if (!function_exists('mb_convert_encoding')) { + $this->markTestSkipped('Test requires the mbstring extension'); + } $data = "\\/fooƌ"; $this->assertJsonFormat('"\\\\\\/fooƌ"', $data, JSON_UNESCAPED_UNICODE); From f152fe723d557fc9a5210f0bfd6ead8d32b0e0a8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 18 Feb 2012 12:00:30 +0100 Subject: [PATCH 50/62] Allow local file system git repos & do not restrict to valid http* urls --- src/Composer/Repository/Vcs/GitDriver.php | 9 +++++++++ src/Composer/Repository/VcsRepository.php | 4 ---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 286cf762a..f3923e4f5 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -169,6 +169,15 @@ class GitDriver extends VcsDriver implements VcsDriverInterface return true; } + // local filesystem + if (preg_match('{^(file://|/|[a-z]:[\\\\/])}', $url)) { + $process = new ProcessExecutor(); + $process->execute(sprintf('cd %s && git log -1 --format=%%at', escapeshellarg($url)), $output); + if (is_numeric(trim($output))) { + return true; + } + } + if (!$deep) { return false; } diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 1d6e82ab3..a2506d2df 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -19,10 +19,6 @@ class VcsRepository extends ArrayRepository public function __construct(array $config, IOInterface $io, array $drivers = null) { - if (!filter_var($config['url'], FILTER_VALIDATE_URL)) { - throw new \UnexpectedValueException('Invalid url given for VCS repository: '.$config['url']); - } - $this->drivers = $drivers ?: array( 'Composer\Repository\Vcs\GitHubDriver', 'Composer\Repository\Vcs\GitBitbucketDriver', From fa8cb14073cb9491b8e8f65a1e9f6f72953fba5b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 18 Feb 2012 12:12:02 +0100 Subject: [PATCH 51/62] Simplify check --- src/Composer/Repository/Vcs/GitDriver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index f3923e4f5..daab73dfe 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -172,8 +172,8 @@ class GitDriver extends VcsDriver implements VcsDriverInterface // local filesystem if (preg_match('{^(file://|/|[a-z]:[\\\\/])}', $url)) { $process = new ProcessExecutor(); - $process->execute(sprintf('cd %s && git log -1 --format=%%at', escapeshellarg($url)), $output); - if (is_numeric(trim($output))) { + // check whether there is a git repo in that path + if ($process->execute(sprintf('cd %s && git show', escapeshellarg($url)), $output) === 0) { return true; } } From 3b54316e91915ccd063ca359d1b6faa9a4ce266d Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sat, 18 Feb 2012 12:33:55 +0100 Subject: [PATCH 52/62] Add a test for the off by one error in solver conflict resolution --- .../Test/DependencyResolver/SolverTest.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 7496834e6..57f835c88 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -414,6 +414,42 @@ class SolverTest extends TestCase )); } + /** + * If a replacer D replaces B and C with C not otherwise available, + * D must be installed instead of the original B. + */ + public function testUseReplacerIfNecessary() + { + $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repo->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($packageD = $this->getPackage('D', '1.0')); + $this->repo->addPackage($packageD2 = $this->getPackage('D', '1.1')); + + $packageA->setRequires(array( + new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'), + new Link('A', 'C', new VersionConstraint('>=', '1.0'), 'requires'), + )); + + $packageD->setReplaces(array( + new Link('D', 'B', new VersionConstraint('>=', '1.0'), 'replaces'), + new Link('D', 'C', new VersionConstraint('>=', '1.0'), 'replaces'), + )); + + $packageD2->setReplaces(array( + new Link('D', 'B', new VersionConstraint('>=', '1.0'), 'replaces'), + new Link('D', 'C', new VersionConstraint('>=', '1.0'), 'replaces'), + )); + + $this->reposComplete(); + + $this->request->install('A'); + + $this->checkSolverResult(array( + array('job' => 'install', 'package' => $packageD2), + array('job' => 'install', 'package' => $packageA), + )); + } + protected function reposComplete() { $this->pool->addRepository($this->repoInstalled); From 460822fb96c8bbe624bb8d336731a2b0e8093d22 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sat, 18 Feb 2012 12:37:45 +0100 Subject: [PATCH 53/62] Fix indentation of doc comment --- tests/Composer/Test/DependencyResolver/SolverTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 57f835c88..85ef6fc70 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -415,9 +415,9 @@ class SolverTest extends TestCase } /** - * If a replacer D replaces B and C with C not otherwise available, - * D must be installed instead of the original B. - */ + * If a replacer D replaces B and C with C not otherwise available, + * D must be installed instead of the original B. + */ public function testUseReplacerIfNecessary() { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); From fb69ef64c5311ca4a3a59a0bfe4e82f4d7f50e67 Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Sat, 18 Feb 2012 12:44:52 +0100 Subject: [PATCH 54/62] Fix Solver function visibility to reflect what acutally should be the public interface --- src/Composer/DependencyResolver/.Solver.php.swp | Bin 0 -> 94208 bytes src/Composer/DependencyResolver/Solver.php | 16 ++++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 src/Composer/DependencyResolver/.Solver.php.swp diff --git a/src/Composer/DependencyResolver/.Solver.php.swp b/src/Composer/DependencyResolver/.Solver.php.swp new file mode 100644 index 0000000000000000000000000000000000000000..91ad3e7d858fd419561b0834bbd41e8b882fc820 GIT binary patch literal 94208 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 Apa1{> literal 0 HcmV?d00001 diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index b865aa82e..eea6621e6 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -77,7 +77,7 @@ class Solver * that goes with the reason * @return Rule The generated rule or null if tautological */ - public function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) + protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null) { $literals = array(new Literal($package, false)); @@ -128,7 +128,7 @@ class Solver * goes with the reason * @return Rule The generated rule */ - public function createInstallRule(PackageInterface $package, $reason, $reasonData = null) + protected function createInstallRule(PackageInterface $package, $reason, $reasonData = null) { return new Rule(new Literal($package, true)); } @@ -146,7 +146,7 @@ class Solver * the reason * @return Rule The generated rule */ - public function createInstallOneOfRule(array $packages, $reason, $reasonData = null) + protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null) { if (empty($packages)) { return $this->createImpossibleRule($reason, $reasonData); @@ -172,7 +172,7 @@ class Solver * goes with the reason * @return Rule The generated rule */ - public function createRemoveRule(PackageInterface $package, $reason, $reasonData = null) + protected function createRemoveRule(PackageInterface $package, $reason, $reasonData = null) { return new Rule(array(new Literal($package, false)), $reason, $reasonData); } @@ -191,7 +191,7 @@ class Solver * goes with the reason * @return Rule The generated rule */ - public function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) + protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null) { // ignore self conflict if ($issuer === $provider) { @@ -212,7 +212,7 @@ class Solver * the reason * @return Rule An empty rule */ - public function createImpossibleRule($reason, $reasonData = null) + protected function createImpossibleRule($reason, $reasonData = null) { return new Rule(array(), $reason, $reasonData); } @@ -237,7 +237,7 @@ class Solver } } - public function addRulesForPackage(PackageInterface $package) + protected function addRulesForPackage(PackageInterface $package) { $workQueue = new \SplQueue; $workQueue->enqueue($package); @@ -571,7 +571,7 @@ class Solver } } - public function addChoiceRules() + protected function addChoiceRules() { // void From 5e3bf86851a1b269636ac637297a26eb3272aade Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 18 Feb 2012 12:50:45 +0100 Subject: [PATCH 55/62] Remove .swp file --- src/Composer/DependencyResolver/.Solver.php.swp | Bin 94208 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/Composer/DependencyResolver/.Solver.php.swp diff --git a/src/Composer/DependencyResolver/.Solver.php.swp b/src/Composer/DependencyResolver/.Solver.php.swp deleted file mode 100644 index 91ad3e7d858fd419561b0834bbd41e8b882fc820..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94208 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 Apa1{> From 8aa0127de877f7075b3af929bdcae09b92efb987 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sat, 18 Feb 2012 13:09:19 +0100 Subject: [PATCH 56/62] Remove test of undefined behaviour for unknown types in rulesets --- .../Composer/Test/DependencyResolver/RuleSetTest.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index be37b8795..7a5b018b4 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -54,17 +54,6 @@ class RuleSetTest extends TestCase $ruleSet->add(new Rule(array(), 'job1', null), 7); } - public function testAddWhenTypeIsUnknow() - { - $ruleSet = new RuleSet; - - $rule = new Rule(array(), 'job1', null); - $ruleSet->add($rule, -1); - - $rules = $ruleSet->getRules(); - $this->assertSame($rule, $rules[-1][0]); - } - public function testCount() { $ruleSet = new RuleSet; From cc80e5a3b10a84dc8c79da3803ed6cf1a4cbaaf1 Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Sat, 18 Feb 2012 14:29:58 +0100 Subject: [PATCH 57/62] Cleanup unreachable code --- src/Composer/DependencyResolver/Solver.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index eea6621e6..00212bb5c 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -944,20 +944,6 @@ class Solver } foreach ($this->jobs as $job) { - switch ($job['cmd']) { - case 'update-all': - foreach ($installedPackages as $package) { - $this->updateMap[$package->getId()] = true; - } - break; - - case 'fix-all': - foreach ($installedPackages as $package) { - $this->fixMap[$package->getId()] = true; - } - break; - } - foreach ($job['packages'] as $package) { switch ($job['cmd']) { case 'fix': From a28fa790beb582d3bef23bd5a43fce900270337e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 18 Feb 2012 16:03:35 +0100 Subject: [PATCH 58/62] Fix regex --- src/Composer/Repository/Vcs/GitDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index daab73dfe..c37b83cae 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -170,7 +170,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface } // local filesystem - if (preg_match('{^(file://|/|[a-z]:[\\\\/])}', $url)) { + if (preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url)) { $process = new ProcessExecutor(); // check whether there is a git repo in that path if ($process->execute(sprintf('cd %s && git show', escapeshellarg($url)), $output) === 0) { From a8b2db64d58db5fb88360d812a0dafb75c3c35f2 Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Sat, 18 Feb 2012 16:55:45 +0100 Subject: [PATCH 59/62] Expaned the update tests to make sure a) Only requested updates execute and b) Installed repos with no actions get pruned from the jobqueue --- .../Test/DependencyResolver/SolverTest.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index 85ef6fc70..a71feafce 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -158,6 +158,22 @@ class SolverTest extends TestCase $this->checkSolverResult(array()); } + public function testSolverUpdateOnlyUpdatesSelectedPackage() + { + $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0')); + $this->repo->addPackage($packageAnewer = $this->getPackage('A', '1.1')); + $this->repo->addPackage($packageBnewer = $this->getPackage('B', '1.1')); + + $this->reposComplete(); + + $this->request->update('A'); + + $this->checkSolverResult(array( + array('job' => 'update', 'from' => $packageA, 'to' => $packageAnewer), + )); + } + public function testSolverUpdateConstrained() { $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); @@ -192,6 +208,24 @@ class SolverTest extends TestCase ))); } + public function testSolverUpdateFullyConstrainedPrunesInstalledPackages() + { + $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0')); + $this->repoInstalled->addPackage($this->getPackage('B', '1.0')); + $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.2')); + $this->repo->addPackage($this->getPackage('A', '2.0')); + $this->reposComplete(); + + $this->request->install('A', new VersionConstraint('<', '2.0.0.0')); + $this->request->update('A', new VersionConstraint('=', '1.0.0.0')); + + $this->checkSolverResult(array(array( + 'job' => 'update', + 'from' => $packageA, + 'to' => $newPackageA, + ))); + } + public function testSolverAllJobs() { $this->repoInstalled->addPackage($packageD = $this->getPackage('D', '1.0')); From 230e1450536759c47aff54f95487a0573a3e6d10 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 18 Feb 2012 16:59:39 +0100 Subject: [PATCH 60/62] Fix downloader tests on windows and mock Filesystem properly --- src/Composer/Downloader/VcsDownloader.php | 7 +++--- .../Test/Downloader/GitDownloaderTest.php | 25 ++++++++++++++----- .../Test/Downloader/HgDownloaderTest.php | 25 ++++++++++++++----- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 5fa8c9e4b..5a30dacc5 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -24,11 +24,13 @@ abstract class VcsDownloader implements DownloaderInterface { protected $io; protected $process; + protected $filesystem; - public function __construct(IOInterface $io, ProcessExecutor $process = null) + public function __construct(IOInterface $io, ProcessExecutor $process = null, Filesystem $fs = null) { $this->io = $io; $this->process = $process ?: new ProcessExecutor; + $this->filesystem = $fs ?: new Filesystem; } /** @@ -74,8 +76,7 @@ abstract class VcsDownloader implements DownloaderInterface public function remove(PackageInterface $package, $path) { $this->enforceCleanDirectory($path); - $fs = new Filesystem(); - $fs->removeDirectory($path); + $this->filesystem->removeDirectory($path); } /** diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index 7d563d2e4..ff1c3ac07 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -32,7 +32,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testDownload() { - $expectedGitCommand = 'git clone \'https://github.com/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''; + $expectedGitCommand = $this->getCmd('git clone \'https://github.com/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\''); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -66,8 +66,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testUpdate() { - $expectedGitUpdateCommand = 'cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\''; - $expectedGitResetCommand = 'cd \'composerPath\' && git status --porcelain'; + $expectedGitUpdateCommand = $this->getCmd('cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\''); + $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain'); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) @@ -90,22 +90,35 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testRemove() { - $expectedGitResetCommand = 'cd \'composerPath\' && git status --porcelain'; + $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain'); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->any()) ->method('execute') ->with($this->equalTo($expectedGitResetCommand)); + $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem->expects($this->any()) + ->method('removeDirectory') + ->with($this->equalTo('composerPath')); - $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor, $filesystem); $downloader->remove($packageMock, 'composerPath'); } public function testGetInstallationSource() { $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface')); - + $this->assertEquals('source', $downloader->getInstallationSource()); } + + private function getCmd($cmd) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return strtr($cmd, "'", '"'); + } + + return $cmd; + } } diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index 81429194d..a7c246394 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -32,7 +32,7 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase public function testDownload() { - $expectedGitCommand = 'hg clone \'https://mercurial.dev/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && hg up \'ref\''; + $expectedGitCommand = $this->getCmd('hg clone \'https://mercurial.dev/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && hg up \'ref\''); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -66,8 +66,8 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase public function testUpdate() { - $expectedUpdateCommand = 'cd \'composerPath\' && hg pull && hg up \'ref\''; - $expectedResetCommand = 'cd \'composerPath\' && hg st'; + $expectedUpdateCommand = $this->getCmd('cd \'composerPath\' && hg pull && hg up \'ref\''); + $expectedResetCommand = $this->getCmd('cd \'composerPath\' && hg st'); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) @@ -90,22 +90,35 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase public function testRemove() { - $expectedResetCommand = 'cd \'composerPath\' && hg st'; + $expectedResetCommand = $this->getCmd('cd \'composerPath\' && hg st'); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); $processExecutor->expects($this->any()) ->method('execute') ->with($this->equalTo($expectedResetCommand)); + $filesystem = $this->getMock('Composer\Util\Filesystem'); + $filesystem->expects($this->any()) + ->method('removeDirectory') + ->with($this->equalTo('composerPath')); - $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor); + $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor, $filesystem); $downloader->remove($packageMock, 'composerPath'); } public function testGetInstallationSource() { $downloader = new HgDownloader($this->getMock('Composer\IO\IOInterface')); - + $this->assertEquals('source', $downloader->getInstallationSource()); } + + private function getCmd($cmd) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return strtr($cmd, "'", '"'); + } + + return $cmd; + } } From e2199b2b03889121719582d0f23a8e192eb2671c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 18 Feb 2012 17:05:13 +0100 Subject: [PATCH 61/62] Fixed automatic date parsing of versions --- src/Composer/Repository/Vcs/GitDriver.php | 2 +- src/Composer/Repository/Vcs/HgDriver.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index c37b83cae..8a8d1f8c6 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -101,7 +101,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface if (!isset($composer['time'])) { $this->process->execute(sprintf('cd %s && git log -1 --format=%%at %s', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $output); - $date = new \DateTime('@'.$output[0]); + $date = new \DateTime('@'.trim($output)); $composer['time'] = $date->format('Y-m-d H:i:s'); } $this->infoCache[$identifier] = $composer; diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index f65f683c5..1e73104f9 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -107,7 +107,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface if (!isset($composer['time'])) { $this->process->execute(sprintf('cd %s && hg log --template "{date|rfc822date}" -r %s', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $output); - $date = new \DateTime($output[0]); + $date = new \DateTime(trim($output)); $composer['time'] = $date->format('Y-m-d H:i:s'); } $this->infoCache[$identifier] = $composer; From 5fdca198809951f03e0376d3d40e7e42b2b1df0f Mon Sep 17 00:00:00 2001 From: Volker Dusch Date: Sat, 18 Feb 2012 17:50:15 +0100 Subject: [PATCH 62/62] Removed the option to disallowed downgrades and simplifed some places where it was used --- .../DependencyResolver/DefaultPolicy.php | 12 +------- .../DependencyResolver/PolicyInterface.php | 3 +- src/Composer/DependencyResolver/Solver.php | 30 +++++-------------- 3 files changed, 9 insertions(+), 36 deletions(-) diff --git a/src/Composer/DependencyResolver/DefaultPolicy.php b/src/Composer/DependencyResolver/DefaultPolicy.php index 41918b260..f06b4104b 100644 --- a/src/Composer/DependencyResolver/DefaultPolicy.php +++ b/src/Composer/DependencyResolver/DefaultPolicy.php @@ -26,11 +26,6 @@ class DefaultPolicy implements PolicyInterface return true; } - public function allowDowngrade() - { - return true; - } - public function versionCompare(PackageInterface $a, PackageInterface $b, $operator) { $constraint = new VersionConstraint($operator, $b->getVersion()); @@ -39,16 +34,11 @@ class DefaultPolicy implements PolicyInterface return $constraint->matchSpecific($version); } - public function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package, $allowAll = false) + public function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package) { $packages = array(); foreach ($pool->whatProvides($package->getName()) as $candidate) { - // skip old packages unless downgrades are an option - if (!$allowAll && !$this->allowDowngrade() && $this->versionCompare($package, $candidate, '>')) { - continue; - } - if ($candidate !== $package) { $packages[] = $candidate; } diff --git a/src/Composer/DependencyResolver/PolicyInterface.php b/src/Composer/DependencyResolver/PolicyInterface.php index 0271fdab2..45309a081 100644 --- a/src/Composer/DependencyResolver/PolicyInterface.php +++ b/src/Composer/DependencyResolver/PolicyInterface.php @@ -21,9 +21,8 @@ use Composer\Package\PackageInterface; interface PolicyInterface { function allowUninstall(); - function allowDowngrade(); function versionCompare(PackageInterface $a, PackageInterface $b, $operator); - function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package, $allowAll); + function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package); function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package); function selectPreferedPackages(Pool $pool, array $installedMap, array $literals); } diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 00212bb5c..8f12e6e0a 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -375,9 +375,9 @@ class Solver * be added * @param bool $allowAll Whether downgrades are allowed */ - private function addRulesForUpdatePackages(PackageInterface $package, $allowAll) + private function addRulesForUpdatePackages(PackageInterface $package) { - $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package, $allowAll); + $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $this->addRulesForPackage($package); @@ -965,7 +965,7 @@ class Solver } foreach ($installedPackages as $package) { - $this->addRulesForUpdatePackages($package, true); + $this->addRulesForUpdatePackages($package); } @@ -983,31 +983,15 @@ class Solver // solver_addrpmrulesforweak(solv, &addedmap); foreach ($installedPackages as $package) { - // create a feature rule which allows downgrades - $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package, true); - $featureRule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); - - // create an update rule which does not allow downgrades - $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package, false); + $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package); $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package); - if ($rule->equals($featureRule)) { - if ($this->policy->allowUninstall()) { - $featureRule->setWeak(true); - $this->addRule(RuleSet::TYPE_FEATURE, $featureRule); - $this->packageToFeatureRule[$package->getId()] = $rule; - } else { - $this->addRule(RuleSet::TYPE_UPDATE, $rule); - $this->packageToUpdateRule[$package->getId()] = $rule; - } - } else if ($this->policy->allowUninstall()) { - $featureRule->setWeak(true); + if ($this->policy->allowUninstall()) { $rule->setWeak(true); - $this->addRule(RuleSet::TYPE_FEATURE, $featureRule); - $this->addRule(RuleSet::TYPE_UPDATE, $rule); - $this->packageToFeatureRule[$package->getId()] = $rule; + } else { + $this->addRule(RuleSet::TYPE_UPDATE, $rule); $this->packageToUpdateRule[$package->getId()] = $rule; } }