From 7d3d8242b47abc5fab0016054c4fb79a941577aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Sat, 14 Aug 2021 18:20:03 +0200 Subject: [PATCH] Convert asset manager version into composer version for comparison --- Asset/AbstractAssetManager.php | 24 ++- Converter/SemverConverter.php | 35 +++++ Converter/SemverUtil.php | 187 ++++++++++++++++++++++++ Converter/VersionConverterInterface.php | 29 ++++ Tests/Converter/SemverConverterTest.php | 107 ++++++++++++++ 5 files changed, 375 insertions(+), 7 deletions(-) create mode 100644 Converter/SemverConverter.php create mode 100644 Converter/SemverUtil.php create mode 100644 Converter/VersionConverterInterface.php create mode 100644 Tests/Converter/SemverConverterTest.php diff --git a/Asset/AbstractAssetManager.php b/Asset/AbstractAssetManager.php index dcbdcb2..8a26833 100644 --- a/Asset/AbstractAssetManager.php +++ b/Asset/AbstractAssetManager.php @@ -18,6 +18,8 @@ use Composer\Util\Platform; use Composer\Util\ProcessExecutor; use Foxy\Config\Config; +use Foxy\Converter\SemverConverter; +use Foxy\Converter\VersionConverterInterface; use Foxy\Exception\RuntimeException; use Foxy\Fallback\FallbackInterface; use Foxy\Json\JsonFile; @@ -51,6 +53,11 @@ abstract class AbstractAssetManager implements AssetManagerInterface */ protected $fs; + /** + * @var VersionConverterInterface + */ + protected $versionConverter; + /** * @var null|FallbackInterface */ @@ -69,24 +76,27 @@ abstract class AbstractAssetManager implements AssetManagerInterface /** * Constructor. * - * @param IOInterface $io The IO - * @param Config $config The config - * @param ProcessExecutor $executor The process - * @param Filesystem $fs The filesystem - * @param FallbackInterface $fallback The asset fallback + * @param IOInterface $io The IO + * @param Config $config The config + * @param ProcessExecutor $executor The process + * @param Filesystem $fs The filesystem + * @param null|FallbackInterface $fallback The asset fallback + * @param null|VersionConverterInterface $versionConverter The version converter */ public function __construct( IOInterface $io, Config $config, ProcessExecutor $executor, Filesystem $fs, - FallbackInterface $fallback = null + FallbackInterface $fallback = null, + ?VersionConverterInterface $versionConverter = null ) { $this->io = $io; $this->config = $config; $this->executor = $executor; $this->fs = $fs; $this->fallback = $fallback; + $this->versionConverter = null !== $versionConverter ? $versionConverter : new SemverConverter(); } /** @@ -258,7 +268,7 @@ protected function getVersion() { if ('' === $this->version) { $this->executor->execute($this->getVersionCommand(), $version); - $this->version = '' !== trim($version) ? trim($version) : null; + $this->version = '' !== trim($version) ? $this->versionConverter->convertVersion(trim($version)) : null; } return $this->version; diff --git a/Converter/SemverConverter.php b/Converter/SemverConverter.php new file mode 100644 index 0000000..adeda1e --- /dev/null +++ b/Converter/SemverConverter.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Foxy\Converter; + +/** + * Converter for Semver syntax version to composer syntax version. + * + * @author François Pluchino + */ +class SemverConverter implements VersionConverterInterface +{ + public function convertVersion($version) + { + if (\in_array($version, array(null, '', 'latest'), true)) { + return ('latest' === $version ? 'default || ' : '').'*'; + } + + $version = str_replace('–', '-', $version); + $prefix = preg_match('/^[a-z]/', $version) && 0 !== strpos($version, 'dev-') ? substr($version, 0, 1) : ''; + $version = substr($version, \strlen($prefix)); + $version = SemverUtil::convertVersionMetadata($version); + $version = SemverUtil::convertDateVersion($version); + + return $prefix.$version; + } +} diff --git a/Converter/SemverUtil.php b/Converter/SemverUtil.php new file mode 100644 index 0000000..b8540a6 --- /dev/null +++ b/Converter/SemverUtil.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Foxy\Converter; + +use Composer\Package\Version\VersionParser; + +/** + * Utils for semver converter. + * + * @author François Pluchino + */ +abstract class SemverUtil +{ + /** + * Converts the date or datetime version. + * + * @param string $version The version + * + * @return string + */ + public static function convertDateVersion($version) + { + if (preg_match('/^\d{7,}\./', $version)) { + $pos = strpos($version, '.'); + $version = substr($version, 0, $pos).self::convertDateMinorVersion(substr($version, $pos + 1)); + } + + return $version; + } + + /** + * Converts the version metadata. + * + * @param string $version + * + * @return string + */ + public static function convertVersionMetadata($version) + { + if (preg_match_all( + self::createPattern('([a-zA-Z]+|(\-|\+)[a-zA-Z]+|(\-|\+)[0-9]+)'), + $version, + $matches, + PREG_OFFSET_CAPTURE + )) { + list($type, $version, $end) = self::cleanVersion(strtolower($version), $matches); + list($version, $patchVersion) = self::matchVersion($version, $type); + + $matches = array(); + $hasPatchNumber = preg_match('/[0-9]+\.[0-9]+|[0-9]+|\.[0-9]+$/', $end, $matches); + $end = $hasPatchNumber ? $matches[0] : '1'; + + if ($patchVersion) { + $version .= $end; + } + } + + return static::cleanWildcard($version); + } + + /** + * Creates a pattern with the version prefix pattern. + * + * @param string $pattern The pattern without '/' + * + * @return string The full pattern with '/' + */ + public static function createPattern($pattern) + { + $numVer = '([0-9]+|x|\*)'; + $numVer2 = '('.$numVer.'\.'.$numVer.')'; + $numVer3 = '('.$numVer.'\.'.$numVer.'\.'.$numVer.')'; + + return '/^'.'('.$numVer.'|'.$numVer2.'|'.$numVer3.')'.$pattern.'/'; + } + + /** + * Clean the wildcard in version. + * + * @param string $version The version + * + * @return string The cleaned version + */ + private static function cleanWildcard($version) + { + while (false !== strpos($version, '.x.x')) { + $version = str_replace('.x.x', '.x', $version); + } + + return $version; + } + + /** + * Clean the raw version. + * + * @param string $version The version + * @param array $matches The match of pattern asset version + * + * @return array The list of $type, $version and $end + */ + private static function cleanVersion($version, array $matches) + { + $end = substr($version, \strlen($matches[1][0][0])); + $version = $matches[1][0][0].'-'; + + $matches = array(); + if (preg_match('/^([-+])/', $end, $matches)) { + $end = substr($end, 1); + } + + $matches = array(); + preg_match('/^[a-z]+/', $end, $matches); + $type = isset($matches[0]) ? VersionParser::normalizeStability($matches[0]) : null; + $end = substr($end, \strlen($type)); + + return array($type, $version, $end); + } + + /** + * Match the version. + * + * @param string $version + * @param string $type + * + * @return array The list of $version and $patchVersion + */ + private static function matchVersion($version, $type) + { + $patchVersion = true; + + switch ($type) { + case 'dev': + case 'snapshot': + $type = 'dev'; + $patchVersion = false; + + break; + + case 'a': + $type = 'alpha'; + + break; + + case 'b': + case 'pre': + $type = 'beta'; + + break; + + default: + if (!\in_array($type, array('alpha', 'beta', 'RC'), true)) { + $type = 'patch'; + } + + break; + } + + $version .= $type; + + return array($version, $patchVersion); + } + + /** + * Convert the minor version of date. + * + * @param string $minor The minor version + * + * @return string + */ + private static function convertDateMinorVersion($minor) + { + $split = explode('.', $minor); + $minor = (int) $split[0]; + $revision = isset($split[1]) ? (int) $split[1] : 0; + + return '.'.sprintf('%03d', $minor).sprintf('%03d', $revision); + } +} diff --git a/Converter/VersionConverterInterface.php b/Converter/VersionConverterInterface.php new file mode 100644 index 0000000..afa48d4 --- /dev/null +++ b/Converter/VersionConverterInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Foxy\Converter; + +/** + * Interface for the converter for asset syntax version to composer syntax version. + * + * @author François Pluchino + */ +interface VersionConverterInterface +{ + /** + * Converts the asset version to composer version. + * + * @param string $version The asset version + * + * @return string The composer version + */ + public function convertVersion($version); +} diff --git a/Tests/Converter/SemverConverterTest.php b/Tests/Converter/SemverConverterTest.php new file mode 100644 index 0000000..4f2e999 --- /dev/null +++ b/Tests/Converter/SemverConverterTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Foxy\Tests\Converter; + +use Foxy\Converter\SemverConverter; +use Foxy\Converter\VersionConverterInterface; +use PHPUnit\Framework\TestCase; + +/** + * Tests for the conversion of Semver syntax to composer syntax. + * + * @author François Pluchino + * + * @internal + */ +final class SemverConverterTest extends TestCase +{ + /** + * @var VersionConverterInterface + */ + protected $converter; + + protected function setUp(): void + { + $this->converter = new SemverConverter(); + } + + protected function tearDown(): void + { + $this->converter = null; + } + + /** + * @dataProvider getTestVersions + * + * @param string $semver + * @param string $composer + */ + public function testConverter($semver, $composer) + { + static::assertEquals($composer, $this->converter->convertVersion($semver)); + + if (!ctype_alpha($semver) && !\in_array($semver, array(null, ''), true)) { + static::assertEquals('v'.$composer, $this->converter->convertVersion('v'.$semver)); + } + } + + public function getTestVersions() + { + return array( + array('1.2.3', '1.2.3'), + array('1.2.3alpha', '1.2.3-alpha1'), + array('1.2.3-alpha', '1.2.3-alpha1'), + array('1.2.3a', '1.2.3-alpha1'), + array('1.2.3a1', '1.2.3-alpha1'), + array('1.2.3-a', '1.2.3-alpha1'), + array('1.2.3-a1', '1.2.3-alpha1'), + array('1.2.3b', '1.2.3-beta1'), + array('1.2.3b1', '1.2.3-beta1'), + array('1.2.3-b', '1.2.3-beta1'), + array('1.2.3-b1', '1.2.3-beta1'), + array('1.2.3beta', '1.2.3-beta1'), + array('1.2.3-beta', '1.2.3-beta1'), + array('1.2.3beta1', '1.2.3-beta1'), + array('1.2.3-beta1', '1.2.3-beta1'), + array('1.2.3rc1', '1.2.3-RC1'), + array('1.2.3-rc1', '1.2.3-RC1'), + array('1.2.3rc2', '1.2.3-RC2'), + array('1.2.3-rc2', '1.2.3-RC2'), + array('1.2.3rc.2', '1.2.3-RC.2'), + array('1.2.3-rc.2', '1.2.3-RC.2'), + array('1.2.3+0', '1.2.3-patch0'), + array('1.2.3-0', '1.2.3-patch0'), + array('1.2.3pre', '1.2.3-beta1'), + array('1.2.3-pre', '1.2.3-beta1'), + array('1.2.3dev', '1.2.3-dev'), + array('1.2.3-dev', '1.2.3-dev'), + array('1.2.3+build2012', '1.2.3-patch2012'), + array('1.2.3-build2012', '1.2.3-patch2012'), + array('1.2.3+build.2012', '1.2.3-patch.2012'), + array('1.2.3-build.2012', '1.2.3-patch.2012'), + array('1.3.0–rc30.79', '1.3.0-RC30.79'), + array('1.2.3-SNAPSHOT', '1.2.3-dev'), + array('1.2.3-20123131.3246', '1.2.3-patch20123131.3246'), + array('1.x.x-dev', '1.x-dev'), + array('20170124.0.0', '20170124.000000'), + array('20170124.1.0', '20170124.001000'), + array('20170124.1.1', '20170124.001001'), + array('20170124.100.200', '20170124.100200'), + array('20170124.0', '20170124.000000'), + array('20170124.1', '20170124.001000'), + array('20170124', '20170124'), + array('latest', 'default || *'), + array(null, '*'), + array('', '*'), + ); + } +}