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,
];
}
}