diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index f1d8e6f91..5a2568886 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -349,7 +349,7 @@ EOT } $author = $io->askAndValidate( - 'Author ['.$author.', n to skip]: ', + 'Author ['.(is_string($author) ? ''.$author.', ' : '') . 'n to skip]: ', function ($value) use ($author) { if ($value === 'n' || $value === 'no') { return; @@ -357,6 +357,10 @@ EOT $value = $value ?: $author; $author = $this->parseAuthorString($value); + if ($author['email'] === null) { + return $author['name']; + } + return sprintf('%s <%s>', $author['name'], $author['email']); }, null, @@ -470,33 +474,41 @@ EOT /** * @private * @param string $author - * @return array{name: string, email: string} + * @return array{name: string, email: string|null} */ public function parseAuthorString($author) { - if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+) <(?P.+?)>$/u', $author, $match)) { - if ($this->isValidEmail($match['email'])) { - return array( - 'name' => trim($match['name']), - 'email' => $match['email'], - ); + if (Preg::isMatch('/^(?P[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P.+?)>)?$/u', $author, $match)) { + $hasEmail = isset($match['email']) && '' !== $match['email']; + if ($hasEmail && !$this->isValidEmail($match['email'])) { + throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"'); } + + return array( + 'name' => trim($match['name']), + 'email' => $hasEmail ? $match['email'] : null, + ); } throw new \InvalidArgumentException( - 'Invalid author string. Must be in the format: '. - 'John Smith ' + 'Invalid author string. Must be in the formats: '. + 'Jane Doe or John Smith ' ); } /** * @param string $author * - * @return array + * @return array */ protected function formatAuthors($author) { - return array($this->parseAuthorString($author)); + $author = $this->parseAuthorString($author); + if (null === $author['email']) { + unset($author['email']); + } + + return array($author); } /** diff --git a/src/Composer/Command/LicensesCommand.php b/src/Composer/Command/LicensesCommand.php index 5c02e9d72..5a9bfa16b 100644 --- a/src/Composer/Command/LicensesCommand.php +++ b/src/Composer/Command/LicensesCommand.php @@ -86,7 +86,7 @@ EOT $tableStyle = $table->getStyle(); $tableStyle->setVerticalBorderChars(''); $tableStyle->setCellRowContentFormat('%s '); - $table->setHeaders(array('Name', 'Version', 'License')); + $table->setHeaders(array('Name', 'Version', 'Licenses')); foreach ($packages as $package) { $link = PackageInfo::getViewSourceOrHomepageUrl($package); if ($link !== null) { @@ -124,12 +124,16 @@ EOT case 'summary': $usedLicenses = array(); foreach ($packages as $package) { - $license = $package instanceof CompletePackageInterface ? $package->getLicense() : array(); - $licenseName = $license[0]; - if (!isset($usedLicenses[$licenseName])) { - $usedLicenses[$licenseName] = 0; + $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : array(); + if (count($licenses) === 0) { + $licenses[] = 'none'; + } + foreach ($licenses as $licenseName) { + if (!isset($usedLicenses[$licenseName])) { + $usedLicenses[$licenseName] = 0; + } + $usedLicenses[$licenseName]++; } - $usedLicenses[$licenseName]++; } // Sort licenses so that the most used license will appear first diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 694204899..dd4033991 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/src/Composer/Repository/Vcs/GitBitbucketDriver.php @@ -61,7 +61,10 @@ class GitBitbucketDriver extends VcsDriver */ public function initialize() { - Preg::match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $this->url, $match); + if (!Preg::isMatch('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', $this->url)); + } + $this->owner = $match[1]; $this->repository = $match[2]; $this->originUrl = 'bitbucket.org'; diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index e95492a66..33d7e8cd3 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -59,7 +59,10 @@ class GitHubDriver extends VcsDriver */ public function initialize() { - Preg::match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match); + if (!Preg::isMatch('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match)) { + throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url)); + } + $this->owner = $match[3]; $this->repository = $match[4]; $this->originUrl = strtolower(!empty($match[1]) ? $match[1] : $match[2]); diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 3c45b4e73..2e7d36cc2 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -94,7 +94,7 @@ class GitLabDriver extends VcsDriver public function initialize() { if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) { - throw new \InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.'); + throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url)); } $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; diff --git a/tests/Composer/Test/Command/InitCommandTest.php b/tests/Composer/Test/Command/InitCommandTest.php index 7cb6fe94c..22950158e 100644 --- a/tests/Composer/Test/Command/InitCommandTest.php +++ b/tests/Composer/Test/Command/InitCommandTest.php @@ -25,6 +25,14 @@ class InitCommandTest extends TestCase $this->assertEquals('john@example.com', $author['email']); } + public function testParseValidAuthorStringWithoutEmail() + { + $command = new InitCommand; + $author = $command->parseAuthorString('John Smith'); + $this->assertEquals('John Smith', $author['name']); + $this->assertNull($author['email']); + } + public function testParseValidUtf8AuthorString() { $command = new InitCommand; diff --git a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php index a8ee520f0..66de8b7eb 100644 --- a/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitBitbucketDriverTest.php @@ -217,6 +217,14 @@ class GitBitbucketDriverTest extends TestCase ); } + public function testInitializeInvalidRepositoryUrl() + { + $this->expectException('\InvalidArgumentException'); + + $driver = $this->getDriver(array('url' => 'https://bitbucket.org/acme')); + $driver->initialize(); + } + public function testSupports() { $this->assertTrue( diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php index 6ec9c25dc..ad7d15896 100644 --- a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -14,11 +14,11 @@ namespace Composer\Test\Repository\Vcs; use Composer\Downloader\TransportException; use Composer\Repository\Vcs\GitHubDriver; +use Composer\Test\Mock\ProcessExecutorMock; use Composer\Test\TestCase; use Composer\Util\Filesystem; -use Composer\Util\Http\Response; -use Composer\Test\Mock\ProcessExecutorMock; use Composer\Config; +use Composer\Util\Http\Response; use Composer\Util\ProcessExecutor; class GitHubDriverTest extends TestCase @@ -306,6 +306,50 @@ class GitHubDriverTest extends TestCase $this->assertEquals($sha, $source['reference']); } + /** + * @return void + */ + public function initializeInvalidReoUrl() + { + $this->expectException('\InvalidArgumentException'); + + $repoConfig = array( + 'url' => 'https://github.com/acme', + ); + + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $httpDownloader = $this->getMockBuilder('Composer\Util\HttpDownloader') + ->setConstructorArgs(array($io, $this->config)) + ->getMock(); + + $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, new ProcessExecutorMock); + $gitHubDriver->initialize(); + } + + /** + * @dataProvider supportsProvider + * @param bool $expected + * @param string $repoUrl + */ + public function testSupports($expected, $repoUrl) + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + + $this->assertSame($expected, GitHubDriver::supports($io, $this->config, $repoUrl)); + } + + /** + * @return list + */ + public function supportsProvider() + { + return array( + array(false, 'https://github.com/acme'), + array(true, 'https://github.com/acme/repository'), + array(true, 'git@github.com:acme/repository.git'), + ); + } + /** * @param string|object $object * @param string $attribute