From ae83c73169eace0a0a55722b98eae5cdbae94c3d Mon Sep 17 00:00:00 2001 From: Hassan Amouhzi Date: Sat, 18 Oct 2014 04:23:37 +0200 Subject: [PATCH] first commit --- .gitignore | 38 +++ README.md | 26 ++ composer.json | 25 ++ phpunit.xml.dist | 24 ++ src/Version/Constraint/AbstractConstraint.php | 8 + src/Version/Constraint/EmptyConstraint.php | 16 + src/Version/Constraint/MultiConstraint.php | 34 +++ src/Version/Constraint/VersionConstraint.php | 33 ++ src/Version/VersionParser.php | 270 +++++++++++++++++ .../Constraint/EmptyConstraintTest.php | 12 + .../Constraint/MultiConstraintTest.php | 27 ++ .../Constraint/VersionConstraintTest.php | 28 ++ tests/Version/VersionParserTest.php | 282 ++++++++++++++++++ 13 files changed, 823 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/Version/Constraint/AbstractConstraint.php create mode 100644 src/Version/Constraint/EmptyConstraint.php create mode 100644 src/Version/Constraint/MultiConstraint.php create mode 100644 src/Version/Constraint/VersionConstraint.php create mode 100644 src/Version/VersionParser.php create mode 100644 tests/Version/Constraint/EmptyConstraintTest.php create mode 100644 tests/Version/Constraint/MultiConstraintTest.php create mode 100644 tests/Version/Constraint/VersionConstraintTest.php create mode 100644 tests/Version/VersionParserTest.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ca1d6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +/vendor/ + +/composer.lock +/*.iml + +# IDE files +/.idea/ + +# Eclipse files +*.pydevproject +.metadata +.gradle +/tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d9d13f --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +Version Helper for PHP projects +=============================== + +This module normalize versions as Composer do. Parse constraints as Composer do too. +And tests if a version match a constraint. + +Example: + +```php +parseStability('1.2-RC2'); // RC +echo $parser->parseStability('2.0b'); // beta +echo $parser->parseConstraints('1.0'); // stable + +echo $parser->normalize('2.0b1'); // 2.0.0.0-beta1 + +$c = $parser->parseConstraints('>=1.2.5,<2.0'); +echo $c->match('1.2.0'); // false +echo $c->match('1.5'); // true +echo $c->match('2.0'); // false + +?> +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..92b965f --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "version/version", + "description": "A library for creating, editing, and comparing semantic versioning numbers.", + "keywords": ["semantic", "version"], + "homepage": "http://github.com/php-mod/version", + "license": "MIT", + "authors": [ + { + "name": "Hassan Amouhzi", + "email": "hassan@amouhzi.com", + "homepage": "http://anezi.net" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*" + }, + "autoload": { + "psr-0": { + "Version": "src/" + } + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..6b798d8 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + src/ + + + + + tests/ + + + diff --git a/src/Version/Constraint/AbstractConstraint.php b/src/Version/Constraint/AbstractConstraint.php new file mode 100644 index 0000000..ca0873f --- /dev/null +++ b/src/Version/Constraint/AbstractConstraint.php @@ -0,0 +1,8 @@ +minMax = $minMax; + $this->and = $and; + } + + public function __toString() + { + return implode($this->and ? ',' : '|', $this->minMax); + } + + public function match($version) + { + if($this->and) { + foreach($this->minMax as $c) { + if(!$c->match($version)) + return false; + } + return true; + } else { + foreach($this->minMax as $c) { + if($c->match($version)) + return true; + } + return false; + } + } +} diff --git a/src/Version/Constraint/VersionConstraint.php b/src/Version/Constraint/VersionConstraint.php new file mode 100644 index 0000000..ba43e86 --- /dev/null +++ b/src/Version/Constraint/VersionConstraint.php @@ -0,0 +1,33 @@ +operator = $operator; + if(!is_string($version)) { + throw new \InvalidArgumentException('Arg 2 must be a string'); + } + $this->version = $version; + } + + public function __toString() + { + return $this->operator . + $this->version; + } + + public function match($version) + { + $parser = new VersionParser(); + $version = $parser->normalize($version); + return version_compare($version, $parser->normalize($this->version), $this->operator); + } +} diff --git a/src/Version/VersionParser.php b/src/Version/VersionParser.php new file mode 100644 index 0000000..1463958 --- /dev/null +++ b/src/Version/VersionParser.php @@ -0,0 +1,270 @@ + 0) $parts[] = $matches[1]; + if(isset($matches[2]) && strlen($matches[2]) > 0) $parts[] = $matches[2]; + if(isset($matches[3]) && strlen($matches[3]) > 0) $parts[] = $matches[3]; + if(isset($matches[4]) && strlen($matches[4]) > 0) $parts[] = $matches[4]; + + if(empty($matches)) { + throw new \UnexpectedValueException('Invalid: ' . $input); + } + + if( + isset($parts[0]) && + ( + strlen($parts[0]) != 14 && + strlen($parts[0]) != 8 && + strlen($parts[0]) != 6 && + strlen($parts[0]) != 4 || + ( + strlen($parts[0]) == 4 && + isset($parts[1]) && + strlen($parts[1]) != 2 + ) + ) + ) { + while (count($parts) < 4) { + $parts[] = 0; + } + $glue = '.'; + } else { + $glue = '-'; + } + + $normalized = implode($glue, $parts); + + if(isset($matches[5]) && strlen($matches[5]) > 0) { + $stability = ''; + if(strtolower($matches[5]) == 'rc') { + $stability = 'RC'; + } elseif(in_array(strtolower($matches[5]), array('pl', 'patch', 'p'))) { + $stability = 'patch'; + } elseif(in_array(strtolower($matches[5]), array('beta', 'b'))) { + $stability = 'beta'; + } + $normalized .= '-' . $stability . $matches[6]; + } + + return $normalized; + } + + public function parseConstraints($input) + { + + $input = trim($input); + + if(strlen($input) < 1) { + throw new \UnexpectedValueException('Empty.'); + } + + $array = explode(',', $input); + if(count($array) == 2) { + $min = $this->parseConstraints($array[0]); + $max = $this->parseConstraints($array[1]); + return new MultiConstraint(array($min, $max)); + } + + $array = explode('|', $input); + if(count($array) == 2) { + $min = $this->parseConstraints($array[0]); + $max = $this->parseConstraints($array[1]); + return new MultiConstraint(array($min, $max), false); + } + + $regex = '/^' . + '(?:([\*|x])\.)?' . + '(?:([\*|x])\.)?' . + '(?:([\*|x])\.)?' . + '(?:([\*|x]))?' . + '$/'; + + if(preg_match($regex, $input, $matches)) { + return new EmptyConstraint(); + } + + $regex = '/^' . + '(?:([<|>|!|=|~]*))? *' . + '(?:(\d+|\*|x)\.)?' . + '(?:(\d+|\*|x)\.)?' . + '(?:(\d+|\*|x)\.)?' . + '(?:(\d+|\*|x))?' . + '(?:[-\.]{0,1}([Rr][Cc]|pl|[Bb][Ee][Tt][Aa]|b|patch|p|stable)\.{0,1}(\d*))?' . + '$/'; + + preg_match($regex, $input, $matches); + + if(empty($matches)) { + throw new \UnexpectedValueException('Invalid type: ' . $input); + } + + $operator = '='; + if(isset($matches[1]) && strlen($matches[1]) > 0){ + $operator = $matches[1]; + if($operator == '==') $operator = '='; + } + + if(!in_array($operator, array('=', '<', '>', '<=', '>=', '<>', '!=', '~'))){ + $propositions = array( + '~>' => '~' + ); + throw new \UnexpectedValueException( + 'Invalid type: \'' . $input . + ' contains \'Invalid operator "' . $operator . + '", you probably meant to use the "' . $propositions[$operator] . + '" operator'); + } + + $parts = array(); + + if(isset($matches[2]) && strlen($matches[2]) > 0) $parts[] = $matches[2]; + if(isset($matches[3]) && strlen($matches[3]) > 0) $parts[] = $matches[3]; + if(isset($matches[4]) && strlen($matches[4]) > 0) $parts[] = $matches[4]; + if(isset($matches[5]) && strlen($matches[5]) > 0) $parts[] = $matches[5]; + + if($operator == '~') { + $end = count($parts); + } else { + $end = null; + } + + while (count($parts) < 4) { + $parts[] = 0; + } + + $max = $parts; + + if($end) { + if($end == 1) { + $max[0]++; + } elseif($end == 2) { + $max[0]++; + $max[1] = 0; + } elseif($end == 3) { + $max[1]++; + $max[2] = 0; + } elseif($end == 4) { + $max[2]++; + $max[3] = 0; + } else { + echo $end; + die($end); + } + } + + if($parts[3] === 'x' || $parts[3] === '*') { + $parts[3] = 0; + $max[3] = 0; + $max[2]++; + } + + if($parts[2] === 'x' || $parts[2] === '*') { + $parts[2] = 0; + $max[2] = 0; + $max[1]++; + } + + if($parts[1] === 'x' || $parts[1] === '*') { + $parts[1] = 0; + $max[1] = 0; + $max[0]++; + } + + $version = implode('.', $parts); + + if(isset($matches[6]) && strlen($matches[6]) > 0) { + if(strtolower($matches[5]) == 'rc') { + $stability = '-RC'; + } elseif(in_array(strtolower($matches[6]), array('pl', 'patch', 'p'))) { + $stability = '-patch'; + } elseif(in_array(strtolower($matches[6]), array('beta', 'b'))) { + $stability = '-beta'; + } elseif(strtolower($matches[6]) == 'stable') { + $stability = ''; + } else { + throw new \UnexpectedValueException('Invalid type: ' . $input); + } + $version .= $stability . $matches[7]; + } + + foreach($parts as $k=>$v) { + if ($v != $max[$k]) { + if ($input == '<=1.2.3') { + print_r($parts); + print_r($max); + print_r(array_diff($parts, $max)); + die; + } + $max = implode('.', $max); + if($version == '0.0.0.0') { + return new VersionConstraint('<', $max); + } + if(isset($matches[6]) && strtolower($matches[6]) == 'stable') { + $version .= '-stable'; + } + return new MultiConstraint(array( + new VersionConstraint('>=', $version), + new VersionConstraint('<', $max) + )); + } + } + + return new VersionConstraint($operator, $version); + + } + + public static function parseStability($input) + { + $regex = '/' . self::STABILITY_REGEX . '/'; + + if(preg_match($regex, $input, $matches)) { + $stability = $matches[1]; + if(in_array(strtolower($stability), array('pl', 'patch'))) { + return 'stable'; + } + if(in_array(strtolower($stability), array('b'))) { + return 'beta'; + } + if(in_array(strtolower($stability), array('a'))) { + return 'alpha'; + } + if(in_array(strtolower($stability), array('rc'))) { + return 'RC'; + } + return $stability; + } else { + return 'stable'; + } + } +} diff --git a/tests/Version/Constraint/EmptyConstraintTest.php b/tests/Version/Constraint/EmptyConstraintTest.php new file mode 100644 index 0000000..f79c0ca --- /dev/null +++ b/tests/Version/Constraint/EmptyConstraintTest.php @@ -0,0 +1,12 @@ +assertTrue($c->match('1.2.5')); + } +} diff --git a/tests/Version/Constraint/MultiConstraintTest.php b/tests/Version/Constraint/MultiConstraintTest.php new file mode 100644 index 0000000..5fc5bb1 --- /dev/null +++ b/tests/Version/Constraint/MultiConstraintTest.php @@ -0,0 +1,27 @@ +', '1.7.2'); + $c3 = new VersionConstraint('<=', '2.0.1'); + $c4 = new MultiConstraint(array($c2, $c3)); + $c = new MultiConstraint(array($c1, $c4), false); + + $this->assertFalse($c->match('0.2')); + $this->assertFalse($c->match('0.4.5')); + $this->assertFalse($c->match('1.0')); + $this->assertFalse($c->match('1.0.0')); + $this->assertFalse($c->match('1.0.1')); + $this->assertTrue($c->match('1.5.4')); + $this->assertFalse($c->match('1.6')); + $this->assertTrue($c->match('1.7.3')); + $this->assertTrue($c->match('2.0.0')); + $this->assertFalse($c->match('2.2')); + + } +} diff --git a/tests/Version/Constraint/VersionConstraintTest.php b/tests/Version/Constraint/VersionConstraintTest.php new file mode 100644 index 0000000..8610b99 --- /dev/null +++ b/tests/Version/Constraint/VersionConstraintTest.php @@ -0,0 +1,28 @@ +assertTrue($c->match('1.0')); + $this->assertTrue($c->match('1.0.0')); + $this->assertFalse($c->match('2.0')); + + $c = new VersionConstraint('>', '1.0'); + $this->assertFalse($c->match('1.0')); + $this->assertFalse($c->match('1.0.0')); + $this->assertTrue($c->match('1.0.1')); + $this->assertTrue($c->match('2.0')); + + $c = new VersionConstraint('<', '1.0'); + $this->assertFalse($c->match('1.0')); + $this->assertFalse($c->match('1.0.0')); + $this->assertFalse($c->match('2.0.0')); + $this->assertTrue($c->match('0.2')); + $this->assertTrue($c->match('0.4.5')); + + } +} diff --git a/tests/Version/VersionParserTest.php b/tests/Version/VersionParserTest.php new file mode 100644 index 0000000..9434fdc --- /dev/null +++ b/tests/Version/VersionParserTest.php @@ -0,0 +1,282 @@ +assertSame( + $expected, + $parser->normalize($input), + 'INPUT: ' . $input + ); + } + + public function successfulNormalizedVersions() + { + return array( + 'none' => array('1.0.0', '1.0.0.0'), + 'none/2' => array('1.2.3.4', '1.2.3.4'), + 'parses state' => array('1.0.0RC1', '1.0.0.0-RC1'), + 'CI parsing' => array('1.0.0-rC15', '1.0.0.0-RC15'), + 'delimiters' => array('1.0.0.RC.15', '1.0.0.0-RC15'), + 'RC uppercase' => array('1.0.0-rc1', '1.0.0.0-RC1'), + 'patch replace' => array('1.0.0.pl3', '1.0.0.0-patch3'), + 'forces w.x.y.z' => array('1.0', '1.0.0.0'), + 'forces w.x.y.z/2' => array('0', '0.0.0.0'), + 'parses long' => array('10.4.13-beta', '10.4.13.0-beta'), + 'parses long/2' => array('10.4.13beta2', '10.4.13.0-beta2'), + 'expand shorthand' => array('10.4.13-b', '10.4.13.0-beta'), + 'expand shorthand2' => array('10.4.13-b5', '10.4.13.0-beta5'), + 'strips leading v' => array('v1.0.0', '1.0.0.0'), + 'strips v/datetime' => array('v20100102', '20100102'), + 'parses dates y-m' => array('2010.01', '2010-01'), + 'parses dates w/ .' => array('2010.01.02', '2010-01-02'), + 'parses dates w/ -' => array('2010-01-02', '2010-01-02'), + 'parses numbers' => array('2010-01-02.5', '2010-01-02-5'), + 'parses dates y.m.Y' => array('2010.1.555', '2010.1.555.0'), + 'parses datetime' => array('20100102-203040', '20100102-203040'), + 'parses dt+number' => array('20100102203040-10', '20100102203040-10'), + 'parses dt+patch' => array('20100102-203040-p1', '20100102-203040-patch1'), + ); + } + + /** + * @dataProvider failingNormalizedVersions + * @expectedException UnexpectedValueException + * @param $input + */ + public function testNormalizeFails($input) + { + $parser = new VersionParser; + $parser->normalize($input); + } + + public function failingNormalizedVersions() + { + return array( + 'empty ' => array(''), + 'invalid chars' => array('a'), + 'invalid type' => array('1.0.0-meh'), + 'too many bits' => array('1.0.0.0.0'), + 'non-dev arbitrary' => array('feature-foo'), + ); + } + + /** + * @dataProvider simpleConstraints + * @param $input + * @param $expected + */ + public function testParseConstraintsSimple($input, $expected) + { + $parser = new VersionParser; + $this->assertSame( + (string) $expected, + (string) $parser->parseConstraints($input), + 'INPUT: ' . $input + ); + } + + public function simpleConstraints() + { + return array( + 'match any' => array('*', new EmptyConstraint()), + 'match any/2' => array('*.*', new EmptyConstraint()), + 'match any/3' => array('*.x.*', new EmptyConstraint()), + 'match any/4' => array('x.x.x.*', new EmptyConstraint()), + 'not equal' => array('<>1.0.0', new VersionConstraint('<>', '1.0.0.0')), + 'not equal/2' => array('!=1.0.0', new VersionConstraint('!=', '1.0.0.0')), + 'greater than' => array('>1.0.0', new VersionConstraint('>', '1.0.0.0')), + 'lesser than' => array('<1.2.3.4', new VersionConstraint('<', '1.2.3.4')), + 'less/eq than' => array('<=1.2.3', new VersionConstraint('<=', '1.2.3.0')), + 'great/eq than' => array('>=1.2.3', new VersionConstraint('>=', '1.2.3.0')), + 'equals' => array('=1.2.3', new VersionConstraint('=', '1.2.3.0')), + 'double equals' => array('==1.2.3', new VersionConstraint('=', '1.2.3.0')), + 'no op means eq' => array('1.2.3', new VersionConstraint('=', '1.2.3.0')), + 'completes version' => array('=1.0', new VersionConstraint('=', '1.0.0.0')), + 'shorthand beta' => array('1.2.3b5', new VersionConstraint('=', '1.2.3.0-beta5')), + 'accepts spaces' => array('>= 1.2.3', new VersionConstraint('>=', '1.2.3.0')), + 'lesser than override' => array('<1.2.3.4-stable', new VersionConstraint('<', '1.2.3.4')), + ); + } + + /** + * @expectedException UnexpectedValueException + * @expectedExceptionMessage Invalid operator "~>", you probably meant to use the "~" operator + */ + public function testParseConstraintsNudgesRubyDevsTowardsThePathOfRighteousness() + { + $parser = new VersionParser; + $parser->parseConstraints('~>1.2'); + } + + /** + * @dataProvider wildcardConstraints + * @param $input + * @param $min + * @param $max + */ + public function testParseConstraintsWildcard($input, $min, $max) + { + $parser = new VersionParser; + if ($min) { + $expected = new MultiConstraint(array($min, $max)); + } else { + $expected = $max; + } + + $this->assertSame((string) $expected, (string) $parser->parseConstraints($input)); + } + + public function wildcardConstraints() + { + return array( + array('2.*', new VersionConstraint('>=', '2.0.0.0'), new VersionConstraint('<', '3.0.0.0')), + array('2.0.*', new VersionConstraint('>=', '2.0.0.0'), new VersionConstraint('<', '2.1.0.0')), + array('2.2.x', new VersionConstraint('>=', '2.2.0.0'), new VersionConstraint('<', '2.3.0.0')), + array('2.1.3.*', new VersionConstraint('>=', '2.1.3.0'), new VersionConstraint('<', '2.1.4.0')), + array('20.*', new VersionConstraint('>=', '20.0.0.0'), new VersionConstraint('<', '21.0.0.0')), + array('2.10.x', new VersionConstraint('>=', '2.10.0.0'), new VersionConstraint('<', '2.11.0.0')), + array('0.*', null, new VersionConstraint('<', '1.0.0.0')), + ); + } + + /** + * @dataProvider tildeConstraints + * @param $input + * @param $min + * @param $max + */ + public function testParseTildeWildcard($input, $min, $max) + { + $parser = new VersionParser; + if ($min) { + $expected = new MultiConstraint(array($min, $max)); + } else { + $expected = $max; + } + + $this->assertSame( + (string) $expected, + (string) $parser->parseConstraints($input), + 'INPUT: ' . $input + ); + } + + public function tildeConstraints() + { + return array( + array('~1', new VersionConstraint('>=', '1.0.0.0'), new VersionConstraint('<', '2.0.0.0')), + array('~1.0', new VersionConstraint('>=', '1.0.0.0'), new VersionConstraint('<', '2.0.0.0')), + array('~1.0.0', new VersionConstraint('>=', '1.0.0.0'), new VersionConstraint('<', '1.1.0.0')), + array('~1.2', new VersionConstraint('>=', '1.2.0.0'), new VersionConstraint('<', '2.0.0.0')), + array('~1.2.3', new VersionConstraint('>=', '1.2.3.0'), new VersionConstraint('<', '1.3.0.0')), + array('~1.2.3.4', new VersionConstraint('>=', '1.2.3.4'), new VersionConstraint('<', '1.2.4.0')), + array('~1.2-beta',new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<', '2.0.0.0')), + array('~1.2-b2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0')), + array('~1.2-BETA2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0')), + array('~1.2.2', new VersionConstraint('>=', '1.2.2.0'), new VersionConstraint('<', '1.3.0.0')), + array('~1.2.2-stable', new VersionConstraint('>=', '1.2.2.0-stable'), new VersionConstraint('<', '1.3.0.0')), + ); + } + + public function testParseConstraintsMulti() + { + $parser = new VersionParser; + $first = new VersionConstraint('>', '2.0.0.0'); + $second = new VersionConstraint('<=', '3.0.0.0'); + $multi = new MultiConstraint(array($first, $second)); + $this->assertSame((string) $multi, (string) $parser->parseConstraints('>2.0,<=3.0')); + } + + public function testParseConstraintsMultiDisjunctiveHasPrioOverConjuctive() + { + $parser = new VersionParser; + $first = new VersionConstraint('>', '2.0.0.0'); + $second = new VersionConstraint('<', '2.0.5.0'); + $third = new VersionConstraint('>', '2.0.6.0'); + $multi1 = new MultiConstraint(array($first, $second)); + $multi2 = new MultiConstraint(array($multi1, $third), false); + $this->assertSame((string) $multi2, (string) $parser->parseConstraints('>2.0,<2.0.5 | >2.0.6')); + } + + public function testParseConstraintsMultiWithStabilities() + { + $parser = new VersionParser; + $first = new VersionConstraint('>', '2.0.0.0'); + $second = new VersionConstraint('<=', '3.0.0.0'); + $multi = new MultiConstraint(array($first, $second)); + $this->assertSame((string) $multi, (string) $parser->parseConstraints('>2.0,<=3.0')); + } + + /** + * @dataProvider failingConstraints + * @expectedException UnexpectedValueException + * @param $input + */ + public function testParseConstraintsFails($input) + { + $parser = new VersionParser; + $parser->parseConstraints($input); + } + + public function failingConstraints() + { + return array( + 'empty ' => array(''), + 'invalid version' => array('1.0.0-meh'), + ); + } + + /** + * @dataProvider stabilityProvider + * @param $expected + * @param $version + */ + public function testParseStability($expected, $version) + { + $this->assertSame( + $expected, + VersionParser::parseStability($version), + 'INPUT: ' . $version + ); + } + + public function stabilityProvider() + { + return array( + array('stable', '1'), + array('stable', '1.0'), + array('stable', '3.2.1'), + array('stable', 'v3.2.1'), + array('RC', '3.0-RC2'), + array('stable', '3.1.2-pl2'), + array('stable', '3.1.2-patch'), + array('alpha', '3.1.2-alpha5'), + array('beta', '3.1.2-beta'), + array('beta', '2.0B1'), + array('alpha', '1.2.0a1'), + array('alpha', '1.2_a1'), + array('RC', '2.0.0rc1') + ); + } +}