From dca0cbc93ab3e430a495000e8e32c62ed3da4995 Mon Sep 17 00:00:00 2001 From: digitalkaoz Date: Mon, 23 Jan 2012 12:10:49 +0100 Subject: [PATCH 1/4] added installer script --- README.md | 7 ++- bin/installer | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 2 deletions(-) create mode 100755 bin/installer diff --git a/README.md b/README.md index e3b20a3af..79cfb19c9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,10 @@ See the [about page](http://packagist.org/about) on [packagist.org](http://packa Installation / Usage -------------------- -1. Download the [`composer.phar`](http://getcomposer.org/composer.phar) executable +1. Download the [`composer.phar`](http://getcomposer.org/composer.phar) executable or use the installer. + + $ curl http://getcomposer.org/installer | php + 2. Create a composer.json defining your dependencies. Note that this example is a short version for applications that are not meant to be published as packages themselves. To create libraries/packages please read the [guidelines](http://packagist.org/about). @@ -43,7 +46,7 @@ Since composer works with the current working directory it is possible to instal in a system wide way. 1. Change into a directory in your path like `cd /usr/local/bin` -2. Get composer `wget http://getcomposer.org/composer.phar` +2. Get composer `curl http://getcomposer.org/installer | php` 3. Make the phar executeable `chmod a+x composer.phar` 3. Change into a project directory `cd /path/to/my/project` 4. Use composer as you normally would `composer.phar install` diff --git a/bin/installer b/bin/installer new file mode 100755 index 000000000..798757f64 --- /dev/null +++ b/bin/installer @@ -0,0 +1,151 @@ +#!/usr/bin/env php + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +process($argv); + +/** + * processes the installer + */ +function process($argv) +{ + $check = in_array('--check', $argv); + $help = in_array('--help', $argv); + $force = in_array('--force', $argv); + + if ($help) { + displayHelp(); + exit(0); + } + + $ok = checkPlatform(); + + if ($check && !$ok) { + exit(1); + } + + if ($ok || $force) { + installComposer(); + } + + exit(0); +} + +/** + * displays the help + */ +function displayHelp() +{ + echo << $actual) { + switch ($error) { + case 'unicode': + $text = " detect_unicode = Off (actual: {$actual})".PHP_EOL; + break; + + case 'readonly': + $text = " phar.readonly = Off (actual: {$actual})".PHP_EOL; + break; + + case 'require_hash': + $text = " phar.require_hash = Off (actual: {$actual})".PHP_EOL; + break; + + case 'suhosin': + $text = " suhosin.executor.include.whitelist = phar (actual: {$actual})".PHP_EOL; + break; + case 'php': + $text = " PHP_VERSION (actual: {$actual})".PHP_EOL; + break; + } + out($text, 'info'); + } + echo PHP_EOL; + return false; + } + + out("All settings correct for using Composer".PHP_EOL,'success'); + return true; +} + +/** + * installs composer to the current working directory + */ +function installComposer() +{ + $installDir = getcwd(); + $file = $installDir . DIRECTORY_SEPARATOR . 'composer.phar'; + + if (is_readable($file)) { + @unlink($file); + } + + $download = copy('http://getcomposer.org/composer.phar', $installDir.DIRECTORY_SEPARATOR.'composer.phar'); + + out(PHP_EOL."Composer successfully installed to: " . $file, 'success'); + out(PHP_EOL."Use it: php composer.phar".PHP_EOL, 'info'); +} + +/** + * colorize output + */ +function out($text, $color = null) +{ + $styles = array( + 'success' => "\033[0;32m%s\033[0m", + 'error' => "\033[31;31m%s\033[0m", + 'info' => "\033[33;33m%s\033[0m" + ); + + echo sprintf(isset($styles[$color]) ? $styles[$color] : "%s", $text); +} \ No newline at end of file From 50e7f4bea93a565b5e511ff84ac39f3a074c4219 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 23 Jan 2012 10:28:15 +0100 Subject: [PATCH 2/4] Fix feedback from 7222c1 --- src/Composer/Autoload/AutoloadGenerator.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 99263c0bf..47e75a944 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -31,7 +31,7 @@ class AutoloadGenerator Date: Mon, 23 Jan 2012 19:41:44 +0100 Subject: [PATCH 3/4] fix issue #251 - Using $this not in object context --- src/Composer/Repository/Vcs/HgDriver.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 2b8c4351c..61a80c1c4 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -185,7 +185,8 @@ class HgDriver extends VcsDriver implements VcsDriverInterface return false; } - $exit = $this->process->execute(sprintf('cd %s && hg identify %s', escapeshellarg(sys_get_temp_dir()), escapeshellarg($url)), $ignored); + $processExecutor = new ProcessExecutor(); + $exit = $processExecutor->process->execute(sprintf('cd %s && hg identify %s', escapeshellarg(sys_get_temp_dir()), escapeshellarg($url)), $ignored); return $exit === 0; } } From 6929c42848ea843a0652e93bcad0ddbf193f4f9f Mon Sep 17 00:00:00 2001 From: digitalkaoz Date: Fri, 9 Dec 2011 12:16:17 +0100 Subject: [PATCH 4/4] added schema/syntax validation for composer.json --- bin/installer | 151 ---------------------- composer.json | 4 +- res/composer-schema.json | 3 +- src/Composer/Command/ValidateCommand.php | 2 +- src/Composer/Console/Application.php | 2 +- src/Composer/Factory.php | 2 +- src/Composer/Json/JsonFile.php | 105 ++++++++------- tests/Composer/Test/Json/JsonFileTest.php | 34 +++-- 8 files changed, 86 insertions(+), 217 deletions(-) delete mode 100755 bin/installer diff --git a/bin/installer b/bin/installer deleted file mode 100755 index 798757f64..000000000 --- a/bin/installer +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env php - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - - -process($argv); - -/** - * processes the installer - */ -function process($argv) -{ - $check = in_array('--check', $argv); - $help = in_array('--help', $argv); - $force = in_array('--force', $argv); - - if ($help) { - displayHelp(); - exit(0); - } - - $ok = checkPlatform(); - - if ($check && !$ok) { - exit(1); - } - - if ($ok || $force) { - installComposer(); - } - - exit(0); -} - -/** - * displays the help - */ -function displayHelp() -{ - echo << $actual) { - switch ($error) { - case 'unicode': - $text = " detect_unicode = Off (actual: {$actual})".PHP_EOL; - break; - - case 'readonly': - $text = " phar.readonly = Off (actual: {$actual})".PHP_EOL; - break; - - case 'require_hash': - $text = " phar.require_hash = Off (actual: {$actual})".PHP_EOL; - break; - - case 'suhosin': - $text = " suhosin.executor.include.whitelist = phar (actual: {$actual})".PHP_EOL; - break; - case 'php': - $text = " PHP_VERSION (actual: {$actual})".PHP_EOL; - break; - } - out($text, 'info'); - } - echo PHP_EOL; - return false; - } - - out("All settings correct for using Composer".PHP_EOL,'success'); - return true; -} - -/** - * installs composer to the current working directory - */ -function installComposer() -{ - $installDir = getcwd(); - $file = $installDir . DIRECTORY_SEPARATOR . 'composer.phar'; - - if (is_readable($file)) { - @unlink($file); - } - - $download = copy('http://getcomposer.org/composer.phar', $installDir.DIRECTORY_SEPARATOR.'composer.phar'); - - out(PHP_EOL."Composer successfully installed to: " . $file, 'success'); - out(PHP_EOL."Use it: php composer.phar".PHP_EOL, 'info'); -} - -/** - * colorize output - */ -function out($text, $color = null) -{ - $styles = array( - 'success' => "\033[0;32m%s\033[0m", - 'error' => "\033[31;31m%s\033[0m", - 'info' => "\033[33;33m%s\033[0m" - ); - - echo sprintf(isset($styles[$color]) ? $styles[$color] : "%s", $text); -} \ No newline at end of file diff --git a/composer.json b/composer.json index 7b89586b2..236584f85 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,9 @@ } ], "require": { - "php": ">=5.3.0", + "php": ">=5.3.0", + "justinrainbow/json-schema": ">=1.1.0", + "seld/jsonlint": "*", "symfony/console": "dev-master", "symfony/finder": "dev-master", "symfony/process": "dev-master" diff --git a/res/composer-schema.json b/res/composer-schema.json index 62d6be0ed..ed25a5b07 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -35,8 +35,7 @@ }, "version": { "type": "string", - "description": "Package version, see http://packagist.org/about for more info on valid schemes.", - "required": true + "description": "Package version, see http://packagist.org/about for more info on valid schemes." }, "time": { "type": "string", diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index d862a2b61..41f516ad5 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -53,7 +53,7 @@ EOT } try { - JsonFile::parseJson(file_get_contents($file)); + JsonFile::parseJson(file_get_contents($file), true); } catch (\Exception $e) { $output->writeln(''.$e->getMessage().''); return 1; diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index fe6b6cb13..1881fb575 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -128,4 +128,4 @@ class Application extends BaseApplication return $helperSet; } -} +} \ No newline at end of file diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index e27976a4b..f01d39b1a 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -54,7 +54,7 @@ class Factory 'vendor-dir' => 'vendor', ); - $packageConfig = $file->read(); + $packageConfig = $file->read(true); if (isset($packageConfig['config']) && is_array($packageConfig['config'])) { $packageConfig['config'] = array_merge($composerConfig, $packageConfig['config']); diff --git a/src/Composer/Json/JsonFile.php b/src/Composer/Json/JsonFile.php index 0fc0105b3..110401688 100644 --- a/src/Composer/Json/JsonFile.php +++ b/src/Composer/Json/JsonFile.php @@ -14,6 +14,8 @@ namespace Composer\Json; use Composer\Repository\RepositoryManager; use Composer\Composer; +use JsonSchema\Validator; +use Seld\JsonLint\JsonParser; use Composer\Util\StreamContextFactory; if (!defined('JSON_UNESCAPED_SLASHES')) { @@ -68,7 +70,7 @@ class JsonFile * * @return array */ - public function read() + public function read($validate = false) { $ctx = StreamContextFactory::getContext(array( 'http' => array( @@ -80,7 +82,7 @@ class JsonFile throw new \RuntimeException('Could not read '.$this->path.', you are probably offline'); } - return static::parseJson($json); + return static::parseJson($json, $validate); } /** @@ -215,59 +217,64 @@ class JsonFile /** * Parses json string and returns hash. * - * @param string $json json string + * @param string $json json string + * @param boolean $validateSchema wether to validate the json schema * * @return mixed */ - static public function parseJson($json) - { - $data = json_decode($json, true); + public static function parseJson($json, $validateSchema=false) + { + $data = static::validateSyntax($json); - if (null === $data && 'null' !== strtolower($json)) { - switch (json_last_error()) { - case JSON_ERROR_NONE: - $msg = 'No error has occurred, is your composer.json file empty?'; - break; - case JSON_ERROR_DEPTH: - $msg = 'The maximum stack depth has been exceeded'; - break; - case JSON_ERROR_STATE_MISMATCH: - $msg = 'Invalid or malformed JSON'; - break; - case JSON_ERROR_CTRL_CHAR: - $msg = 'Control character error, possibly incorrectly encoded'; - break; - case JSON_ERROR_SYNTAX: - $msg = 'Syntax error'; - $charOffset = 0; - if (preg_match('#["}\]]\s*(,)\s*[}\]]#', $json, $match, PREG_OFFSET_CAPTURE)) { - $msg .= ', extra comma'; - } elseif (preg_match('#((?<=[^\\\\])\\\\(?!["\\\\/bfnrt]|u[a-f0-9]{4}))#i', $json, $match, PREG_OFFSET_CAPTURE)) { - $msg .= ', unescaped backslash (\\)'; - } elseif (preg_match('#(["}\]])(?: *\r?\n *)+"#', $json, $match, PREG_OFFSET_CAPTURE)) { - $msg .= ', missing comma'; - $charOffset = 1; - } elseif (preg_match('#^ *([a-z0-9_-]+) *:#mi', $json, $match, PREG_OFFSET_CAPTURE)) { - $msg .= ', you must use double quotes (") around keys'; - } elseif (preg_match('#(\'.+?\' *:|: *\'.+?\')#', $json, $match, PREG_OFFSET_CAPTURE)) { - $msg .= ', use double quotes (") instead of single quotes (\')'; - } elseif (preg_match('#(\[".*?":.*?\])#', $json, $match, PREG_OFFSET_CAPTURE)) { - $msg .= ', you must use the hash syntax (e.g. {"foo": "bar"}) instead of array syntax (e.g. ["foo", "bar"])'; - } elseif (preg_match('#".*?"( *["{\[])#', $json, $match, PREG_OFFSET_CAPTURE)) { - $msg .= ', missing colon'; - } - if (isset($match[1][1])) { - $preError = substr($json, 0, $match[1][1]); - $msg .= ' on line '.(substr_count($preError, "\n")+1).', char '.abs(strrpos($preError, "\n") - strlen($preError) - $charOffset); - } - break; - case JSON_ERROR_UTF8: - $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; - break; - } - throw new \UnexpectedValueException('JSON Parse Error: '.$msg); + if ($validateSchema) { + static::validateSchema($json); } return $data; } + + /** + * validates a composer.json against the schema + * + * @param string $json + * @return boolean + * @throws \UnexpectedValueException + */ + public static function validateSchema($json) + { + $data = json_decode($json); + $schema = json_decode(file_get_contents(__DIR__ . '/../../../res/composer-schema.json')); + + $validator = new Validator(); + + $validator->check($data, $schema); + + if (!$validator->isValid()) { + $msg = "\n"; + foreach ((array) $validator->getErrors() as $error) { + $msg .= ($error['property'] ? $error['property'].' : ' : '').$error['message']."\n"; + } + + throw new \UnexpectedValueException('Your composer.json did not validate against the schema. The following mistakes were found:'.$msg); + } + } + + /** + * validates the json syntax + * + * @param string $json + * @return array + * @throws \UnexpectedValueException + */ + public static function validateSyntax($json) + { + $parser = new JsonParser(); + $result = $parser->lint($json); + + if (null === $result) { + return json_decode($json, true); + } + + throw $result; + } } diff --git a/tests/Composer/Test/Json/JsonFileTest.php b/tests/Composer/Test/Json/JsonFileTest.php index fc205eceb..7bb7aecc5 100644 --- a/tests/Composer/Test/Json/JsonFileTest.php +++ b/tests/Composer/Test/Json/JsonFileTest.php @@ -12,6 +12,7 @@ namespace Composer\Test\Json; +use Seld\JsonLint\ParsingException; use Composer\Json\JsonFile; class JsonFileTest extends \PHPUnit_Framework_TestCase @@ -21,7 +22,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $json = '{ "foo": "bar", }'; - $this->expectParseException('extra comma on line 2, char 21', $json); + $this->expectParseException('Parse error on line 2', $json); } public function testParseErrorDetectExtraCommaInArray() @@ -31,7 +32,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase "bar", ] }'; - $this->expectParseException('extra comma on line 3, char 18', $json); + $this->expectParseException('Parse error on line 3', $json); } public function testParseErrorDetectUnescapedBackslash() @@ -39,7 +40,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $json = '{ "fo\o": "bar" }'; - $this->expectParseException('unescaped backslash (\\) on line 2, char 12', $json); + $this->expectParseException('Parse error on line 1', $json); } public function testParseErrorSkipsEscapedBackslash() @@ -48,7 +49,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase "fo\\\\o": "bar" "a": "b" }'; - $this->expectParseException('missing comma on line 2, char 23', $json); + $this->expectParseException('Parse error on line 2', $json); } public function testParseErrorDetectSingleQuotes() @@ -56,7 +57,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $json = '{ \'foo\': "bar" }'; - $this->expectParseException('use double quotes (") instead of single quotes (\') on line 2, char 9', $json); + $this->expectParseException('Parse error on line 1', $json); } public function testParseErrorDetectMissingQuotes() @@ -64,7 +65,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $json = '{ foo: "bar" }'; - $this->expectParseException('must use double quotes (") around keys on line 2, char 9', $json); + $this->expectParseException('Parse error on line 1', $json); } public function testParseErrorDetectArrayAsHash() @@ -72,7 +73,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase $json = '{ "foo": ["bar": "baz"] }'; - $this->expectParseException('you must use the hash syntax (e.g. {"foo": "bar"}) instead of array syntax (e.g. ["foo", "bar"]) on line 2, char 16', $json); + $this->expectParseException('Parse error on line 2', $json); } public function testParseErrorDetectMissingComma() @@ -81,7 +82,18 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase "foo": "bar" "bar": "foo" }'; - $this->expectParseException('missing comma on line 2, char 21', $json); + $this->expectParseException('Parse error on line 2', $json); + } + + public function testSchemaValidation() + { + $json = file_get_contents(__DIR__.'/../../../../composer.json'); + + try { + $this->assertNull(JsonFile::validateSchema($json)); + } catch (\UnexpectedValueException $e) { + $this->fail('invalid schema'); + } } public function testParseErrorDetectMissingCommaMultiline() @@ -91,7 +103,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase "bar": "foo" }'; - $this->expectParseException('missing comma on line 2, char 24', $json); + $this->expectParseException('Parse error on line 2', $json); } public function testParseErrorDetectMissingColon() @@ -100,7 +112,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase "foo": "bar", "bar" "foo" }'; - $this->expectParseException('missing colon on line 3, char 14', $json); + $this->expectParseException('Parse error on line 3', $json); } public function testSimpleJsonString() @@ -166,7 +178,7 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase try { JsonFile::parseJson($json); $this->fail(); - } catch (\UnexpectedValueException $e) { + } catch (ParsingException $e) { $this->assertContains($text, $e->getMessage()); } }