diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac98e5..e287901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ For a full diff see [`64ced12...main`][64ced12...main]. - Added `Major` as a value object ([#3]), by [@localheinz] - Added `Minor` as a value object ([#4]), by [@localheinz] - Added `Patch` as a value object ([#5]), by [@localheinz] +- Added `PreRelease` as a value object ([#8]), by [@localheinz] [64ced12...main]: https://github.com/ergebnis/version/compare/64ced12...main @@ -21,5 +22,6 @@ For a full diff see [`64ced12...main`][64ced12...main]. [#3]: https://github.com/ergebnis/version/pull/3 [#4]: https://github.com/ergebnis/version/pull/4 [#5]: https://github.com/ergebnis/version/pull/5 +[#8]: https://github.com/ergebnis/version/pull/8 [@localheinz]: https://github.com/localheinz diff --git a/README.md b/README.md index 24933e4..a31b276 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,14 @@ declare(strict_types=1); use Ergebnis\Version; -$version = Version\Version::fromString('1.2.3'); +$version = Version\Version::fromString('1.2.3-alpha'); echo $version->toString(); // 1.2.3 echo $version->major()->toString(); // 1 echo $version->minor()->toString(); // 2 echo $version->patch()->toString(); // 3 +echo $version->preRelease()->toString(); // alpha ``` ### Compare a `Version` with another `Version` @@ -158,6 +159,53 @@ $one->equals($three); // false $one->equals($two); // true ``` +### Create a `PreRelease` from a `string` + +```php +toString(); // alpha +``` + +### Create an empty `PreRelease` + +```php +toString(); // empty +``` + +### Compare a `PreRelease` with another `PreRelease` + +```php +equals($two); // true +$one->equals($three); // false + +$one->equals($two); // true +``` + ## Changelog The maintainers of this project record notable changes to this project in a [changelog](CHANGELOG.md). diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 2bf90e2..44da050 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -18,6 +18,12 @@ provideValidValue + + + provideInvalidValue + provideValidValue + + provideInvalidValue diff --git a/src/Exception/InvalidPreRelease.php b/src/Exception/InvalidPreRelease.php new file mode 100644 index 0000000..429eae5 --- /dev/null +++ b/src/Exception/InvalidPreRelease.php @@ -0,0 +1,25 @@ +(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)$/'; + + private function __construct(private readonly string $value) + { + } + + /** + * @throws Exception\InvalidPreRelease + */ + public static function fromString(string $value): self + { + if (1 !== \preg_match(self::REGEX, $value)) { + throw Exception\InvalidPreRelease::fromString($value); + } + + return new self($value); + } + + public static function empty(): self + { + return new self(''); + } + + public function toString(): string + { + return $this->value; + } + + public function equals(self $other): bool + { + return $this->value === $other->value; + } +} diff --git a/src/Version.php b/src/Version.php index ffb0545..96e4f60 100644 --- a/src/Version.php +++ b/src/Version.php @@ -26,6 +26,7 @@ private function __construct( private readonly Major $major, private readonly Minor $minor, private readonly Patch $patch, + private readonly PreRelease $preRelease, ) { } @@ -38,11 +39,21 @@ public static function fromString(string $value): self throw Exception\InvalidVersion::fromString($value); } + $preRelease = PreRelease::empty(); + + if ( + \array_key_exists('prerelease', $matches) + && '' !== $matches['prerelease'] + ) { + $preRelease = PreRelease::fromString($matches['prerelease']); + } + return new self( $value, Major::fromString($matches['major']), Minor::fromString($matches['minor']), Patch::fromString($matches['patch']), + $preRelease, ); } @@ -70,4 +81,9 @@ public function patch(): Patch { return $this->patch; } + + public function preRelease(): PreRelease + { + return $this->preRelease; + } } diff --git a/test/Unit/Exception/InvalidPreReleaseTest.php b/test/Unit/Exception/InvalidPreReleaseTest.php new file mode 100644 index 0000000..8cb9d28 --- /dev/null +++ b/test/Unit/Exception/InvalidPreReleaseTest.php @@ -0,0 +1,38 @@ +word(); + + $exception = Exception\InvalidPreRelease::fromString($value); + + $message = \sprintf( + 'Value "%s" does not appear to be valid.', + $value, + ); + + self::assertSame($message, $exception->getMessage()); + } +} diff --git a/test/Unit/PreReleaseTest.php b/test/Unit/PreReleaseTest.php new file mode 100644 index 0000000..3a29c3b --- /dev/null +++ b/test/Unit/PreReleaseTest.php @@ -0,0 +1,138 @@ +expectException(Exception\InvalidPreRelease::class); + + PreRelease::fromString($value); + } + + /** + * @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + * @see https://regex101.com/r/Ly7O1x/3/ + * + * @return \Generator + */ + public static function provideInvalidValue(): \Generator + { + $values = [ + '', // use named constructor to create an empty pre-release + '0123', + '0123.0123', + 'alpha_beta', + 'alpha.', + 'alpha..', + 'alpha..1', + 'alpha...1', + 'alpha....1', + 'alpha.....1', + 'alpha......1', + 'alpha.......1', + '-1.0.3-gamma+b7718', + '+justmeta', + '9.8.7+meta+meta', + '9.8.7-whatever+meta+meta', + '99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12', + ]; + + foreach ($values as $value) { + yield $value => [ + $value, + ]; + } + } + + #[Framework\Attributes\DataProvider('provideValidValue')] + public function testFromStringReturnsPreRelease(string $value): void + { + $preRelease = PreRelease::fromString($value); + + self::assertSame($value, $preRelease->toString()); + } + + /** + * @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + * @see https://regex101.com/r/Ly7O1x/3/ + * + * @return \Generator + */ + public static function provideValidValue(): \Generator + { + $values = [ + 'prerelease', + 'alpha', + 'beta', + 'alpha.beta', + 'alpha.beta.1', + 'alpha.1', + 'alpha0.valid', + 'alpha.0valid', + 'alpha-a.b-c-somethinglong', + 'rc.1', + 'DEV-SNAPSHOT', + 'SNAPSHOT-123', + 'alpha.1227', + '---RC-SNAPSHOT.12.9.1--.12', + '---R-S.12.9.1--.12', + '0A.is.legal', + ]; + + foreach ($values as $value) { + yield $value => [ + $value, + ]; + } + } + + public function testEmptyReturnsPreRelease(): void + { + $preRelease = PreRelease::empty(); + + self::assertSame('', $preRelease->toString()); + } + + public function testEqualsReturnsFalseWhenValuesAreDifferent(): void + { + $faker = self::faker()->unique(); + + $one = PreRelease::fromString($faker->word()); + $two = PreRelease::fromString($faker->word()); + + self::assertFalse($one->equals($two)); + } + + public function testEqualsReturnsTrueWhenValueIsSame(): void + { + $value = self::faker()->word(); + + $one = PreRelease::fromString($value); + $two = PreRelease::fromString($value); + + self::assertTrue($one->equals($two)); + } +} diff --git a/test/Unit/VersionTest.php b/test/Unit/VersionTest.php index 0fbd236..d95efe5 100644 --- a/test/Unit/VersionTest.php +++ b/test/Unit/VersionTest.php @@ -17,6 +17,7 @@ use Ergebnis\Version\Major; use Ergebnis\Version\Minor; use Ergebnis\Version\Patch; +use Ergebnis\Version\PreRelease; use Ergebnis\Version\Test; use Ergebnis\Version\Version; use PHPUnit\Framework; @@ -26,6 +27,7 @@ #[Framework\Attributes\UsesClass(Major::class)] #[Framework\Attributes\UsesClass(Minor::class)] #[Framework\Attributes\UsesClass(Patch::class)] +#[Framework\Attributes\UsesClass(PreRelease::class)] final class VersionTest extends Framework\TestCase { use Test\Util\Helper; @@ -101,6 +103,7 @@ public function testFromStringReturnsVersion( Major $major, Minor $minor, Patch $patch, + PreRelease $preRelease, ): void { $version = Version::fromString($value); @@ -109,13 +112,14 @@ public function testFromStringReturnsVersion( self::assertEquals($major, $version->major()); self::assertEquals($minor, $version->minor()); self::assertEquals($patch, $version->patch()); + self::assertEquals($preRelease, $version->preRelease()); } /** * @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string * @see https://regex101.com/r/Ly7O1x/3/ * - * @return \Generator + * @return \Generator */ public static function provideValidValue(): \Generator { @@ -124,165 +128,197 @@ public static function provideValidValue(): \Generator Major::fromString('0'), Minor::fromString('0'), Patch::fromString('4'), + PreRelease::empty(), ], '1.2.3' => [ Major::fromString('1'), Minor::fromString('2'), Patch::fromString('3'), + PreRelease::empty(), ], '10.20.30' => [ Major::fromString('10'), Minor::fromString('20'), Patch::fromString('30'), + PreRelease::empty(), ], '1.1.2-prerelease+meta' => [ Major::fromString('1'), Minor::fromString('1'), Patch::fromString('2'), + PreRelease::fromString('prerelease'), ], '1.1.2+meta' => [ Major::fromString('1'), Minor::fromString('1'), Patch::fromString('2'), + PreRelease::empty(), ], '1.1.2+meta-valid' => [ Major::fromString('1'), Minor::fromString('1'), Patch::fromString('2'), + PreRelease::empty(), ], '1.0.0-alpha' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha'), ], '1.0.0-beta' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('beta'), ], '1.0.0-alpha.beta' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha.beta'), ], '1.0.0-alpha.beta.1' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha.beta.1'), ], '1.0.0-alpha.1' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha.1'), ], '1.0.0-alpha0.valid' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha0.valid'), ], '1.0.0-alpha.0valid' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha.0valid'), ], '1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha-a.b-c-somethinglong'), ], '1.0.0-rc.1+build.1' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('rc.1'), ], '2.0.0-rc.1+build.123' => [ Major::fromString('2'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('rc.1'), ], '1.2.3-beta' => [ Major::fromString('1'), Minor::fromString('2'), Patch::fromString('3'), + PreRelease::fromString('beta'), ], '10.2.3-DEV-SNAPSHOT' => [ Major::fromString('10'), Minor::fromString('2'), Patch::fromString('3'), + PreRelease::fromString('DEV-SNAPSHOT'), ], '1.2.3-SNAPSHOT-123' => [ Major::fromString('1'), Minor::fromString('2'), Patch::fromString('3'), + PreRelease::fromString('SNAPSHOT-123'), ], '1.0.0' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::empty(), ], '2.0.0' => [ Major::fromString('2'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::empty(), ], '1.1.7' => [ Major::fromString('1'), Minor::fromString('1'), Patch::fromString('7'), + PreRelease::empty(), ], '2.0.0+build.1848' => [ Major::fromString('2'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::empty(), ], '2.0.1-alpha.1227' => [ Major::fromString('2'), Minor::fromString('0'), Patch::fromString('1'), + PreRelease::fromString('alpha.1227'), ], '1.0.0-alpha+beta' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('alpha'), ], '1.2.3----RC-SNAPSHOT.12.9.1--.12+788' => [ Major::fromString('1'), Minor::fromString('2'), Patch::fromString('3'), + PreRelease::fromString('---RC-SNAPSHOT.12.9.1--.12'), ], '1.2.3----R-S.12.9.1--.12+meta' => [ Major::fromString('1'), Minor::fromString('2'), Patch::fromString('3'), + PreRelease::fromString('---R-S.12.9.1--.12'), ], '1.2.3----RC-SNAPSHOT.12.9.1--.12' => [ Major::fromString('1'), Minor::fromString('2'), Patch::fromString('3'), + PreRelease::fromString('---RC-SNAPSHOT.12.9.1--.12'), ], '1.0.0+0.build.1-rc.10000aaa-kk-0.1' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::empty(), ], '99999999999999999999999.999999999999999999.99999999999999999' => [ Major::fromString('99999999999999999999999'), Minor::fromString('999999999999999999'), Patch::fromString('99999999999999999'), + PreRelease::empty(), ], '1.0.0-0A.is.legal' => [ Major::fromString('1'), Minor::fromString('0'), Patch::fromString('0'), + PreRelease::fromString('0A.is.legal'), ], ]; - foreach ($values as $value => [$major, $minor, $patch]) { + foreach ($values as $value => [$major, $minor, $patch, $preRelease]) { yield $value => [ $value, $major, $minor, $patch, + $preRelease, ]; } }