Skip to content

Commit

Permalink
Merge pull request #8 from ergebnis/feature/pre-release
Browse files Browse the repository at this point in the history
Enhancement: Implement `PreRelease`
  • Loading branch information
localheinz authored Dec 25, 2023
2 parents 95c0d01 + 68ac924 commit 4b641f3
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ 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

[#1]: https://github.com/ergebnis/version/pull/1
[#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
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -158,6 +159,53 @@ $one->equals($three); // false
$one->equals($two); // true
```

### Create a `PreRelease` from a `string`

```php
<?php

declare(strict_types=1);

use Ergebnis\Version;

$preRelease = Version\PreRelease::fromString('alpha');

echo $preRelease->toString(); // alpha
```

### Create an empty `PreRelease`

```php
<?php

declare(strict_types=1);

use Ergebnis\Version;

$preRelease = Version\PreRelease::empty();

echo $preRelease->toString(); // empty
```

### Compare a `PreRelease` with another `PreRelease`

```php
<?php

declare(strict_types=1);

use Ergebnis\Version;

$one = Version\PreRelease::::fromString('alpha');
$two = Version\PreRelease::fromString('alpha');
$three = Version\PreRelease::fromString('beta');

$one->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).
Expand Down
6 changes: 6 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
<code>provideValidValue</code>
</PossiblyUnusedMethod>
</file>
<file src="test/Unit/PreReleaseTest.php">
<PossiblyUnusedMethod>
<code>provideInvalidValue</code>
<code>provideValidValue</code>
</PossiblyUnusedMethod>
</file>
<file src="test/Unit/VersionTest.php">
<PossiblyUnusedMethod>
<code>provideInvalidValue</code>
Expand Down
25 changes: 25 additions & 0 deletions src/Exception/InvalidPreRelease.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2023 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/version
*/

namespace Ergebnis\Version\Exception;

final class InvalidPreRelease extends \InvalidArgumentException
{
public static function fromString(string $value): self
{
return new self(\sprintf(
'Value "%s" does not appear to be valid.',
$value,
));
}
}
54 changes: 54 additions & 0 deletions src/PreRelease.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2023 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/version
*/

namespace Ergebnis\Version;

final class PreRelease
{
/**
* @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
* @see https://regex101.com/r/Ly7O1x/3/
*/
private const REGEX = '/^(?P<prerelease>(?: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;
}
}
16 changes: 16 additions & 0 deletions src/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ private function __construct(
private readonly Major $major,
private readonly Minor $minor,
private readonly Patch $patch,
private readonly PreRelease $preRelease,
) {
}

Expand All @@ -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,
);
}

Expand Down Expand Up @@ -70,4 +81,9 @@ public function patch(): Patch
{
return $this->patch;
}

public function preRelease(): PreRelease
{
return $this->preRelease;
}
}
38 changes: 38 additions & 0 deletions test/Unit/Exception/InvalidPreReleaseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2023 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/version
*/

namespace Ergebnis\Version\Test\Unit\Exception;

use Ergebnis\Version\Exception;
use Ergebnis\Version\Test;
use PHPUnit\Framework;

#[Framework\Attributes\CoversClass(Exception\InvalidPreRelease::class)]
final class InvalidPreReleaseTest extends Framework\TestCase
{
use Test\Util\Helper;

public function testFromStringReturnsException(): void
{
$value = self::faker()->word();

$exception = Exception\InvalidPreRelease::fromString($value);

$message = \sprintf(
'Value "%s" does not appear to be valid.',
$value,
);

self::assertSame($message, $exception->getMessage());
}
}
138 changes: 138 additions & 0 deletions test/Unit/PreReleaseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

declare(strict_types=1);

/**
* Copyright (c) 2023 Andreas Möller
*
* For the full copyright and license information, please view
* the LICENSE.md file that was distributed with this source code.
*
* @see https://github.com/ergebnis/version
*/

namespace Ergebnis\Version\Test\Unit;

use Ergebnis\Version\Exception;
use Ergebnis\Version\PreRelease;
use Ergebnis\Version\Test;
use PHPUnit\Framework;

#[Framework\Attributes\CoversClass(PreRelease::class)]
#[Framework\Attributes\UsesClass(Exception\InvalidPreRelease::class)]
final class PreReleaseTest extends Framework\TestCase
{
use Test\Util\Helper;

#[Framework\Attributes\DataProvider('provideInvalidValue')]
public function testFromStringRejectsInvalidValue(string $value): void
{
$this->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<string, array{0: string}>
*/
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<string, array{0: string}>
*/
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));
}
}
Loading

0 comments on commit 4b641f3

Please sign in to comment.