Full Code of PHLAK/SemVer for AI

master bf9c3ca45511 cached
21 files
48.9 KB
14.7k tokens
93 symbols
1 requests
Download .txt
Repository: PHLAK/SemVer
Branch: master
Commit: bf9c3ca45511
Files: 21
Total size: 48.9 KB

Directory structure:
gitextract_fljnwk0f/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       └── test-suite.yaml
├── .gitignore
├── .php-cs-fixer.dist.php
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── phpstan-ignores.neon
├── phpstan.neon.dist
├── phpunit.xml
├── src/
│   ├── Enums/
│   │   └── Compare.php
│   ├── Exceptions/
│   │   └── InvalidVersionException.php
│   ├── Support/
│   │   └── helpers.php
│   ├── Traits/
│   │   ├── Comparable.php
│   │   └── Incrementable.php
│   └── Version.php
└── tests/
    └── VersionTest.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.json]
indent_style = space
indent_size = 4

[*.md]
trim_trailing_whitespace = false
indent_size = 4

[*.{yml,yaml}]
indent_size = 2
indent_style = space



================================================
FILE: .gitattributes
================================================
tests           export-ignore
.gitattributes  export-ignore
.gitignore      export-ignore
.php_cs.dist    export-ignore
.travis.yml     export-ignore
phpunit.xml     export-ignore


================================================
FILE: .github/FUNDING.yml
================================================
github: PHLAK
patreon: PHLAK
custom: https://paypal.me/ChrisKankiewicz


================================================
FILE: .github/dependabot.yml
================================================
version: 2

updates:
  - package-ecosystem: composer
    directory: "/"
    schedule:
      interval: monthly
      timezone: US/Arizona
    open-pull-requests-limit: 10
    assignees:
    - PHLAK

  - package-ecosystem: github-actions
    directory: "/"
    schedule:
      interval: monthly
      timezone: US/Arizona
    assignees:
      - PHLAK


================================================
FILE: .github/workflows/test-suite.yaml
================================================
name: SemVer Test Suite
on: [push, pull_request, workflow_dispatch]

jobs:
  coding-standards:
    name: Coding Standards
    runs-on: 'ubuntu-latest'

    env:
      PHP_CS_FIXER_IGNORE_ENV: 1

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v5

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'

      - name: Install PHP Dependencies
        run: composer install --no-interaction --no-progress --no-scripts --prefer-dist

      - name: Verify Coding Standards
        run: vendor/bin/php-cs-fixer fix --diff --dry-run

  static-analysis:
    name: Static Analysis
    runs-on: 'ubuntu-latest'

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v5

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'

      - name: Install PHP Dependencies
        run: composer install --no-interaction --no-progress --no-scripts --prefer-dist

      - name: Run Static Analysis
        run: vendor/bin/phpstan analyze

  tests:
    name: Tests
    runs-on: 'ubuntu-latest'

    strategy:
      matrix:
        php-versions: ['8.1', '8.2', '8.3', '8.4']

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v5

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          coverage: xdebug

      - name: Install PHP Dependencies
        run: composer install --no-interaction --no-progress --no-scripts --prefer-dist

      - name: Run Tests
        run: vendor/bin/phpunit --coverage-text


================================================
FILE: .gitignore
================================================
/.php-cs-fixer.cache
/.phpunit.cache
/composer.lock
/vendor/


================================================
FILE: .php-cs-fixer.dist.php
================================================
<?php

require __DIR__ . '/vendor/autoload.php';

$finder = PhpCsFixer\Finder::create()->in([
    __DIR__ . DIRECTORY_SEPARATOR . 'src',
    __DIR__ . DIRECTORY_SEPARATOR . 'tests',
]);

return PHLAK\CodingStandards\ConfigFactory::make($finder);


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Chris Kankiewicz <Chris@ChrisKankiewicz.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
dev development: # Build application for development
	@composer install --no-interaction

prod production: # Build application for production
	@composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader

test: # Run coding standards/static analysis checks and tests
	@vendor/bin/php-cs-fixer fix --diff --dry-run \
		&& vendor/bin/phpstan analyze \
		&& vendor/bin/phpunit --coverage-text

coverage: # Generate an HTML coverage report
	@vendor/bin/phpunit --coverage-html .coverage


================================================
FILE: README.md
================================================
SemVer
======

<p align="center">
  <img src="semver.png" alt="SemVer" width="50%">
</p>

<p align="center">
    <a href="http://semver.org">Semantic versioning</a> helper library
  • Created by <a href="https://www.ChrisKankiewicz.com">Chris Kankiewicz</a> (<a href="https://bsky.app/profile/phlak.dev">@phlak.dev</a>)
</p>

<p align="center">
    <a href="https://github.com/PHLAK/SemVer/discussions"><img src="https://img.shields.io/badge/Join_the-Community-7b16ff.svg?style=for-the-badge" alt="Join our Community"></a>
    <a href="https://github.com/users/PHLAK/sponsorship"><img src="https://img.shields.io/badge/Become_a-Sponsor-cc4195.svg?style=for-the-badge" alt="Become a Sponsor"></a>
    <a href="https://paypal.me/ChrisKankiewicz"><img src="https://img.shields.io/badge/Make_a-Donation-006bb6.svg?style=for-the-badge" alt="One-time Donation"></a>
    <br>
    <a href="https://packagist.org/packages/PHLAK/SemVer"><img src="https://img.shields.io/packagist/v/PHLAK/SemVer.svg?style=flat-square" alt="Latest Stable Version"></a>
    <a href="https://packagist.org/packages/PHLAK/SemVer"><img src="https://img.shields.io/packagist/dt/PHLAK/SemVer.svg?style=flat-square" alt="Total Downloads"></a>
    <a href="https://packagist.org/packages/PHLAK/SemVer"><img src="https://img.shields.io/packagist/l/PHLAK/SemVer.svg?style=flat-square" alt="License"></a>
    <a href="https://github.com/PHLAK/SemVer/actions"><img src="https://img.shields.io/github/actions/workflow/status/PHLAK/SemVer/test-suite.yaml?style=flat-square&label=tests" alt="Tests Status"></a>
</p>

---

Requirements
------------

  - [PHP](https://php.net) >= 8.1

Installation
------------

```bash
composer require phlak/semver
```

Initializing
------------

```php
use PHLAK\SemVer;

$version = new SemVer\Version(); // Initilializes to '0.1.0'
```

Or initialize with a custom version by passing a version string on creation.
Accepts any valid semantic version string with or without a preceding `v`.

```php
$version = new SemVer\Version('v1.2.3-alpha.5+sha.8d31ff4');
```

Or parse an incomple version string with the static `Version::parse()` constructor.

```php
$version = SemVer\Version::parse('v1')   // Initializes to '1.0.0'
$version = SemVer\Version::parse('v1.2') // Initializes to '1.2.0'
```

Usage
-----

#### Retrieve the version or individual values

```php
$version = new SemVer\Version('v1.2.3-beta.4+007');

echo $version;             // '1.2.3-beta.4+007'
echo $version->major;      // 1
echo $version->minor;      // 2
echo $version->patch;      // 3
echo $version->preRelease; // 'beta.4'
echo $version->build;      // '007'
```

#### Increment the version

```php
$version = new SemVer\Version('v1.2.3');

$version->incrementMajor();      // v1.2.3 -> v2.0.0
$version->incrementMinor();      // v1.2.3 -> v1.3.0
$version->incrementPatch();      // v1.2.3 -> v1.2.4
$version->incrementPreRelease(); // v1.2.3-alpha.5 -> v1.2.3-alpha.6
```

#### Set (override) the version or individual values

```php
$version = new SemVer\Version();

$version->setVersion('v1.2.3');  // v1.2.3
$version->setMajor(3);           // v1.2.3 -> v3.0.0
$version->setMinor(5);           // v1.2.3 -> v1.5.0
$version->setPatch(7);           // v1.2.3 -> 1.2.7
$version->setPreRelease('rc.2'); // v1.2.3 -> v1.2.3-rc.2
$version->setBuild('007');       // v1.2.3 -> v1.2.3+007
```

#### Clear pre-release / build values

```php
$version->setPreRelease(null); // v1.2.3-rc.2 -> v1.2.3
$version->setBuild(null);      // v1.2.3+007 -> v1.2.3
```


#### Check for pre-release / build values

```php
$version->isPreRelease();
$version->hasBuild();
```

#### Compare two SemVer objects

```php
$version1 = new SemVer\Version('v1.2.3');
$version2 = new SemVer\Version('v3.2.1');

$version1->gt($version2);  // false
$version1->lt($version2);  // true
$version1->eq($version2);  // false
$version1->neq($version2); // true
$version1->gte($version2); // false
$version1->lte($version2); // true
```

##### Limit comparison to the major version only

Ignores the minor, patch and pre-release versions completely

```php
$version1 = new SemVer\Version('v1.2.3-alpha.4');
$version2 = new SemVer\Version('v1.3.4-alpha.5');

$version1->gt($version2, Compare::MAJOR);  // false
$version1->lt($version2, Compare::MAJOR);  // false
$version1->eq($version2, Compare::MAJOR);  // true
$version1->neq($version2, Compare::MAJOR); // false
$version1->gte($version2, Compare::MAJOR); // true
$version1->lte($version2, Compare::MAJOR); // true
```

##### Limit comparison to the major and minor versions only

Ignores the patch and pre-release versions completely

```php
$version1 = new SemVer\Version('v1.2.3-alpha.4');
$version2 = new SemVer\Version('v1.2.4-alpha.5');

$version1->gt($version2, Compare::MINOR);  // false
$version1->lt($version2, Compare::MINOR);  // false
$version1->eq($version2, Compare::MINOR);  // true
$version1->neq($version2, Compare::MINOR); // false
$version1->gte($version2, Compare::MINOR); // true
$version1->lte($version2, Compare::MINOR); // true
```

##### Limit comparison to the major, minor and patch versions only

Ignores the pre-release version completely

```php
$version1 = new SemVer\Version('v1.2.3-alpha.4');
$version2 = new SemVer\Version('v1.2.3-alpha.5');

$version1->gt($version2, Compare::PATCH);  // false
$version1->lt($version2, Compare::PATCH);  // false
$version1->eq($version2, Compare::PATCH);  // true
$version1->neq($version2, Compare::PATCH); // false
$version1->gte($version2, Compare::PATCH); // true
$version1->lte($version2, Compare::PATCH); // true
```

Troubleshooting
---------------

For general help and support join our [GitHub Discussions](https://github.com/PHLAK/SemVer/discussions) or reach out on [Bluesky](https://bsky.app/profile/phlak.dev).

Please report bugs to the [GitHub Issue Tracker](https://github.com/PHLAK/SemVer/issues).

Copyright
---------

This project is liscensed under the [MIT License](https://github.com/PHLAK/SemVer/blob/master/LICENSE).


================================================
FILE: composer.json
================================================
{
    "name": "phlak/semver",
    "description": "Semantic versioning helper library",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Chris Kankiewicz",
            "email": "Chris@ChrisKankiewicz.com"
        }
    ],
    "funding": [
        {
            "type": "github",
            "url": "https://github.com/sponsors/PHLAK"
        },
        {
            "type": "paypal",
            "url": "https://paypal.me/ChrisKankiewicz"
        }
    ],
    "support": {
        "issues": "https://github.com/PHLAK/SemVer/issues"
    },
    "require": {
        "php": "^8.1 || ^8.2 || ^8.3 || ^8.4"
    },
    "require-dev": {
        "phlak/coding-standards": "^4.0",
        "phpstan/phpstan": "^2.0",
        "yoast/phpunit-polyfills": "^4.0"
    },
    "autoload": {
        "psr-4": {
            "PHLAK\\SemVer\\": "src/"
        },
        "files": ["src/Support/helpers.php"]
    },
    "autoload-dev": {
        "psr-4": {
            "PHLAK\\Semver\\Tests\\": "tests/"
        }
    },
    "config": {
        "sort-packages": true,
        "optimize-autoloader": true,
        "allow-plugins": {
            "composer/package-versions-deprecated": true
        }
    }
}


================================================
FILE: phpstan-ignores.neon
================================================
parameters:
  ignoreErrors:
    - message: "/^Method .+ throws checked exception .+ but it's missing from the PHPDoc @throws tag.$/"
      path: tests/*


================================================
FILE: phpstan.neon.dist
================================================
parameters:

  paths:
    - src
    - tests

  level: max

  checkFunctionNameCase: true

  exceptions:
    implicitThrows: false

    check:
      missingCheckedExceptionInThrows: true
      tooWideThrowType: true

    uncheckedExceptionClasses:
      - 'RuntimeException'
      - 'PHPUnit\Framework\Exception'

includes:
  - phpstan-ignores.neon


================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
>
  <testsuites>
    <testsuite name="PHLAK/SemVer Test Suite">
      <directory>tests/</directory>
    </testsuite>
  </testsuites>
  <source>
    <include>
      <directory suffix=".php">src/</directory>
    </include>
  </source>
</phpunit>


================================================
FILE: src/Enums/Compare.php
================================================
<?php

namespace PHLAK\SemVer\Enums;

enum Compare
{
    case FULL;
    case MAJOR;
    case MINOR;
    case PATCH;
}


================================================
FILE: src/Exceptions/InvalidVersionException.php
================================================
<?php

namespace PHLAK\SemVer\Exceptions;

use Exception;

class InvalidVersionException extends Exception {}


================================================
FILE: src/Support/helpers.php
================================================
<?php

use PHLAK\SemVer\Version;

if (! function_exists('semver')) {
    /**
     * Create a SemVer version object.
     *
     * @throws \PHLAK\SemVer\Exceptions\InvalidVersionException
     */
    function semver(string $string): Version
    {
        return new PHLAK\SemVer\Version($string);
    }
}


================================================
FILE: src/Traits/Comparable.php
================================================
<?php

namespace PHLAK\SemVer\Traits;

use PHLAK\SemVer\Enums\Compare;
use PHLAK\SemVer\Version;

trait Comparable
{
    /**
     * Compare two versions. Returns -1, 0 or 1 if the first version is less
     * than, equal to or greater than the second version respectively.
     *
     * @param Version $version1 An instance of SemVer/Version
     * @param Version $version2 An instance of SemVer/Version
     */
    public static function compare(Version $version1, Version $version2, Compare $comparison = Compare::FULL): int
    {
        [$v1, $v2] = match ($comparison) {
            Compare::MAJOR => [[$version1->major], [$version2->major]],
            Compare::MINOR => [[$version1->major, $version1->minor], [$version2->major, $version2->minor]],
            default => [[$version1->major, $version1->minor, $version1->patch], [$version2->major, $version2->minor, $version2->patch]]
        };

        $baseComparison = $v1 <=> $v2;

        if ($baseComparison !== 0 || $comparison !== Compare::FULL) {
            return $baseComparison;
        }

        if ($version1->preRelease !== null && $version2->preRelease === null) {
            return -1;
        }

        if ($version1->preRelease === null && $version2->preRelease !== null) {
            return 1;
        }

        $v1preReleaseParts = explode('.', $version1->preRelease ?? '');
        $v2preReleaseParts = explode('.', $version2->preRelease ?? '');

        $preReleases1 = array_pad($v1preReleaseParts, count($v2preReleaseParts), null);
        $preReleases2 = array_pad($v2preReleaseParts, count($v1preReleaseParts), null);

        return $preReleases1 <=> $preReleases2;
    }

    /**
     * Check if this Version object is greater than another.
     *
     * @param Version $version An instance of SemVer/Version
     *
     * @return bool True if this Version object is greater than the comparing
     *              object, otherwise false
     */
    public function gt(Version $version, Compare $comparison = Compare::FULL): bool
    {
        return self::compare($this, $version, $comparison) > 0;
    }

    /**
     * Check if this Version object is less than another.
     *
     * @param Version $version An instance of SemVer/Version
     *
     * @return bool True if this Version object is less than the comparing
     *              object, otherwise false
     */
    public function lt(Version $version, Compare $comparison = Compare::FULL): bool
    {
        return self::compare($this, $version, $comparison) < 0;
    }

    /**
     * Check if this Version object is equal to than another.
     *
     * @param Version $version An instance of SemVer/Version
     *
     * @return bool True if this Version object is equal to the comparing
     *              object, otherwise false
     */
    public function eq(Version $version, Compare $comparison = Compare::FULL): bool
    {
        return self::compare($this, $version, $comparison) === 0;
    }

    /**
     * Check if this Version object is not equal to another.
     *
     * @param Version $version An instance of SemVer/Version
     *
     * @return bool True if this Version object is not equal to the comparing
     *              object, otherwise false
     */
    public function neq(Version $version, Compare $comparison = Compare::FULL): bool
    {
        return self::compare($this, $version, $comparison) !== 0;
    }

    /**
     * Check if this Version object is greater than or equal to another.
     *
     * @param Version $version An instance of SemVer/Version
     *
     * @return bool True if this Version object is greater than or equal to the
     *              comparing object, otherwise false
     */
    public function gte(Version $version, Compare $comparison = Compare::FULL): bool
    {
        return self::compare($this, $version, $comparison) >= 0;
    }

    /**
     * Check if this Version object is less than or equal to another.
     *
     * @param Version $version An instance of SemVer/Version
     *
     * @return bool True if this Version object is less than or equal to the
     *              comparing object, otherwise false
     */
    public function lte(Version $version, Compare $comparison = Compare::FULL): bool
    {
        return self::compare($this, $version, $comparison) <= 0;
    }
}


================================================
FILE: src/Traits/Incrementable.php
================================================
<?php

namespace PHLAK\SemVer\Traits;

trait Incrementable
{
    /**
     * Increment the major version value by one.
     *
     * @return static This Version object
     */
    public function incrementMajor(): static
    {
        $this->setMajor($this->major + 1);

        return $this;
    }

    /**
     * Increment the minor version value by one.
     *
     * @return static This Version object
     */
    public function incrementMinor(): static
    {
        $this->setMinor($this->minor + 1);

        return $this;
    }

    /**
     * Increment the patch version value by one.
     *
     * @return static This Version object
     */
    public function incrementPatch(): static
    {
        $this->setPatch($this->patch + 1);

        return $this;
    }

    /**
     * Increment the pre-release version value by one.
     *
     * @return static This Version object
     */
    public function incrementPreRelease(): static
    {
        if (empty($this->preRelease)) {
            $this->incrementPatch();
            $this->setPreRelease('1');

            return $this;
        }

        $identifiers = explode('.', $this->preRelease);

        if (! is_numeric(end($identifiers))) {
            $this->setPreRelease(implode('.', [$this->preRelease, '1']));

            return $this;
        }

        array_push($identifiers, (string) ((int) array_pop($identifiers) + 1));

        $this->setPreRelease(implode('.', $identifiers));

        return $this;
    }
}


================================================
FILE: src/Version.php
================================================
<?php

namespace PHLAK\SemVer;

use JsonSerializable;
use PHLAK\SemVer\Exceptions\InvalidVersionException;
use PHLAK\SemVer\Traits\Comparable;
use PHLAK\SemVer\Traits\Incrementable;

/**
 * @property int $major Major release number
 * @property int $minor Minor release number
 * @property int $patch Patch release number
 * @property string|null $preRelease Pre-release value
 * @property string|null $build Build release value
 */
class Version implements JsonSerializable
{
    use Comparable;
    use Incrementable;

    /** @var int Major release number */
    protected $major;

    /** @var int Minor release number */
    protected $minor;

    /** @var int Patch release number */
    protected $patch;

    /** @var string|null Pre-release value */
    protected $preRelease;

    /** @var string|null Build release value */
    protected $build;

    /**
     * Class constructor, runs on object creation.
     *
     * @param string $version Version string
     *
     * @throws \PHLAK\SemVer\Exceptions\InvalidVersionException
     */
    final public function __construct(string $version = '0.1.0')
    {
        $this->setVersion($version);
    }

    /**
     * Magic get method; provides access to version properties.
     *
     * @param string $property Version property
     *
     * @return mixed Version property value
     */
    public function __get(string $property)
    {
        return $this->$property;
    }

    /**
     * Magic toString method; allows object interaction as if it were a string.
     *
     * @return string Current version string
     */
    public function __toString(): string
    {
        $version = implode('.', [$this->major, $this->minor, $this->patch]);

        if (! empty($this->preRelease)) {
            $version .= '-' . $this->preRelease;
        }

        if (! empty($this->build)) {
            $version .= '+' . $this->build;
        }

        return $version;
    }

    /**
     * Attempt to parse an incomplete version string.
     *
     * Examples: 'v1', 'v1.2', 'v1-beta.4', 'v1.3+007'
     *
     * @param string $version Version string
     *
     * @throws \PHLAK\SemVer\Exceptions\InvalidVersionException
     *
     * @return static This Version object
     */
    public static function parse(string $version): static
    {
        $semverRegex = '/^v?(?<major>\d+)(?:\.(?<minor>\d+)(?:\.(?<patch>\d+))?)?(?:-(?<pre_release>[0-9A-Za-z-.]+))?(?:\+(?<build>[0-9A-Za-z-.]+)?)?$/';

        if (! preg_match($semverRegex, $version, $matches, PREG_UNMATCHED_AS_NULL)) {
            throw new InvalidVersionException('Invalid semantic version string provided');
        }

        $version = sprintf('%s.%s.%s', $matches['major'], $matches['minor'] ?? 0, $matches['patch'] ?? 0);

        if (! empty($matches['pre_release'])) {
            $version .= '-' . $matches['pre_release'];
        }

        if (! empty($matches['build'])) {
            $version .= '+' . $matches['build'];
        }

        return new static($version);
    }

    /** Serialize version to JSON. */
    public function jsonSerialize(): mixed
    {
        return (string) $this;
    }

    /**
     * Set (override) the entire version value.
     *
     * @param string $version Version string
     *
     * @throws \PHLAK\SemVer\Exceptions\InvalidVersionException
     *
     * @return static This Version object
     */
    public function setVersion(string $version): static
    {
        $semverRegex = '/^v?(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<pre_release>[0-9A-Za-z-.]+))?(?:\+(?<build>[0-9A-Za-z-.]+)?)?$/';

        if (! preg_match($semverRegex, $version, $matches, PREG_UNMATCHED_AS_NULL)) {
            throw new InvalidVersionException('Invalid semantic version string provided');
        }

        $this->major = (int) $matches['major'];
        $this->minor = (int) $matches['minor'];
        $this->patch = (int) $matches['patch'];
        $this->preRelease = $matches['pre_release'] ?? null;
        $this->build = $matches['build'] ?? null;

        return $this;
    }

    /**
     * Set the major version to a custom value.
     *
     * @param int $value Positive integer value
     *
     * @return static This Version object
     */
    public function setMajor(int $value): static
    {
        $this->major = $value;
        $this->setMinor(0);

        return $this;
    }

    /**
     * Set the minor version to a custom value.
     *
     * @param int $value Positive integer value
     *
     * @return static This Version object
     */
    public function setMinor(int $value): static
    {
        $this->minor = $value;
        $this->setPatch(0);

        return $this;
    }

    /**
     * Set the patch version to a custom value.
     *
     * @param int $value Positive integer value
     *
     * @return static This Version object
     */
    public function setPatch(int $value): static
    {
        $this->patch = $value;
        $this->setPreRelease(null);
        $this->setBuild(null);

        return $this;
    }

    /**
     * Set the pre-release string to a custom value.
     *
     * @param string|null $value A new pre-release value
     *
     * @return static This Version object
     */
    public function setPreRelease($value): static
    {
        $this->preRelease = $value;

        return $this;
    }

    /**
     * Set the build string to a custom value.
     *
     * @param string|null $value A new build value
     *
     * @return static This Version object
     */
    public function setBuild($value): static
    {
        $this->build = $value;

        return $this;
    }

    /**
     * Get the version string prefixed with a custom string.
     *
     * @param string $prefix String to prepend to the version string
     *                       (default: 'v')
     *
     * @return string Prefixed version string
     */
    public function prefix(string $prefix = 'v'): string
    {
        return $prefix . (string) $this;
    }

    /**
     * Determine if the version is a pre-release.
     *
     * @return bool Returns true if the version is a pre-release, false otherwise
     */
    public function isPreRelease(): bool
    {
        return ! empty($this->preRelease);
    }

    /**
     * Determine if the version has a build string.
     *
     * @return bool Returns true if the version has a build string, false otherwise
     */
    public function hasBuild(): bool
    {
        return ! empty($this->build);
    }
}


================================================
FILE: tests/VersionTest.php
================================================
<?php

namespace PHLAK\SemVer\Tests;

use JsonSerializable;
use PHLAK\SemVer;
use PHLAK\SemVer\Enums\Compare;
use PHLAK\SemVer\Exceptions\InvalidVersionException;
use PHLAK\SemVer\Traits\Comparable;
use PHLAK\SemVer\Traits\Incrementable;
use PHLAK\SemVer\Version;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversTrait;
use PHPUnit\Framework\Attributes\Test;
use Yoast\PHPUnitPolyfills\TestCases\TestCase;

#[CoversClass(Version::class)]
#[CoversTrait(Comparable::class)]
#[CoversTrait(Incrementable::class)]
class VersionTest extends TestCase
{
    /** @return array<array<int, mixed>> */
    public static function pre_release_comparison_provider(): array
    {
        return [
            ['v1.3.37', 'v1.3.37-alpha'],
            ['v1.3.37', 'v1.3.37-alpha.5+007'],
            ['v1.3.0', 'v1.3.0-beta'],
            ['v1.0.0', 'v1.0.0-rc1'],
            // Test cases from http://semver.org
            ['1.0.0', '1.0.0-rc.1'],
            ['1.0.0-rc.1', '1.0.0-beta.11'],
            ['1.0.0-beta.11', '1.0.0-beta.2'],
            ['1.0.0-beta.2', '1.0.0-beta'],
            ['1.0.0-beta', '1.0.0-alpha.beta'],
            ['1.0.0-alpha.beta', '1.0.0-alpha.1'],
            ['1.0.0-alpha.1', '1.0.0-alpha'],
        ];
    }

    #[Test]
    public function it_can_be_initialized(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertInstanceOf(SemVer\Version::class, $version);
    }

    #[Test]
    public function it_can_be_initialized_with_the_helper_function(): void
    {
        $version = semver('v1.3.37');

        $this->assertInstanceOf(SemVer\Version::class, $version);
    }

    #[Test]
    public function it_throws_a_runtime_exception_for_an_invalid_version(): void
    {
        $this->expectException(InvalidVersionException::class);

        new SemVer\Version('not.a.version');
    }

    #[Test]
    public function it_can_parse_an_incomplete_version_string(): void
    {
        $this->assertEquals('1.0.0', (string) SemVer\Version::parse('v1'));
        $this->assertEquals('1.3.0', (string) SemVer\Version::parse('v1.3'));
        $this->assertEquals('1.3.37', (string) SemVer\Version::parse('v1.3.37'));
        $this->assertEquals('1.0.0-alpha.5', (string) SemVer\Version::parse('v1-alpha.5'));
        $this->assertEquals('1.3.0-alpha.5', (string) SemVer\Version::parse('v1.3-alpha.5'));
        $this->assertEquals('1.3.37-alpha.5', (string) SemVer\Version::parse('v1.3.37-alpha.5'));
        $this->assertEquals('1.0.0+007', (string) SemVer\Version::parse('v1+007'));
        $this->assertEquals('1.3.0+007', (string) SemVer\Version::parse('v1.3+007'));
        $this->assertEquals('1.3.37+007', (string) SemVer\Version::parse('v1.3.37+007'));
    }

    #[Test]
    public function it_throws_a_runtime_exception_when_parsing_an_invalid_version(): void
    {
        $this->expectException(InvalidVersionException::class);

        SemVer\Version::parse('not.a.version');
    }

    #[Test]
    public function it_can_set_and_retrieve_a_version(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setVersion('v2.4.48');

        $this->assertEquals('2.4.48', (string) $version);
    }

    #[Test]
    public function it_can_return_a_prefixed_version_string(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertEquals('v1.3.37', $version->prefix());
        $this->assertEquals('x1.3.37', $version->prefix('x'));
    }

    #[Test]
    public function it_can_be_cast_to_a_string(): void
    {
        $this->assertEquals('1.3.37', (string) new SemVer\Version('v1.3.37'));
        $this->assertEquals('1.3.37-alpha.5', (string) new SemVer\Version('v1.3.37-alpha.5'));
        $this->assertEquals('1.3.37+007', (string) new SemVer\Version('v1.3.37+007'));
        $this->assertEquals('1.3.37-alpha.5+007', (string) new SemVer\Version('v1.3.37-alpha.5+007'));
    }

    #[Test]
    public function it_can_get_individual_properties(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5+007');

        $this->assertEquals(1, $version->major);
        $this->assertEquals(3, $version->minor);
        $this->assertEquals(37, $version->patch);
        $this->assertEquals('alpha.5', $version->preRelease);
        $this->assertEquals('007', $version->build);
    }

    #[Test]
    public function it_can_increment_major(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->incrementMajor();

        $this->assertEquals('2.0.0', (string) $version);
    }

    #[Test]
    public function it_can_set_major(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setMajor(7);

        $this->assertEquals('7.0.0', (string) $version);
    }

    #[Test]
    public function it_can_increment_minor(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->incrementMinor();

        $this->assertEquals('1.4.0', (string) $version);
    }

    #[Test]
    public function it_can_set_minor(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setMinor(5);

        $this->assertEquals('1.5.0', (string) $version);
    }

    #[Test]
    public function it_can_increment_patch(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->incrementPatch();

        $this->assertEquals('1.3.38', (string) $version);
    }

    #[Test]
    public function it_can_increment_prerelease(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setPreRelease('alpha.5');
        $version->incrementPreRelease();

        $this->assertEquals('1.3.37-alpha.6', (string) $version);
    }

    #[Test]
    public function it_can_increment_prerelease_without_number(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setPreRelease('alpha');
        $version->incrementPreRelease();

        $this->assertEquals('1.3.37-alpha.1', (string) $version);
    }

    #[Test]
    public function it_can_increment_prerelease_without_prefix_for_prerelease(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setPreRelease('5');
        $version->incrementPreRelease();

        $this->assertEquals('1.3.37-6', (string) $version);
    }

    #[Test]
    public function it_can_increment_prerelease_containing_multiple_dots(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setPreRelease('alpha.a.b.5');
        $version->incrementPreRelease();

        $this->assertEquals('1.3.37-alpha.a.b.6', (string) $version);
    }

    #[Test]
    public function it_can_increment_prerelease_containing_multiple_dots_and_without_number(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setPreRelease('alpha.a.b');
        $version->incrementPreRelease();

        $this->assertEquals('1.3.37-alpha.a.b.1', (string) $version);
    }

    #[Test]
    public function it_can_increment_prerelease_and_patch_when_prerelease_is_null(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->incrementPreRelease();

        $this->assertEquals('1.3.38-1', (string) $version);
    }

    #[Test]
    public function it_can_set_patch(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setPatch(12);

        $this->assertEquals('1.3.12', (string) $version);
    }

    #[Test]
    public function it_can_set_pre_release(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setPreRelease('alpha.5');

        $this->assertEquals('1.3.37-alpha.5', (string) $version);
    }

    #[Test]
    public function it_can_unset_pre_release(): void
    {
        $version = (new SemVer\Version('v1.3.37-alpha.5'))->setPreRelease(null);

        $this->assertNull($version->preRelease);
    }

    #[Test]
    public function it_can_set_build(): void
    {
        $version = new SemVer\Version('v1.3.37');
        $version->setBuild('007');

        $this->assertEquals('1.3.37+007', (string) $version);
    }

    #[Test]
    public function it_can_unset_build(): void
    {
        $version = (new SemVer\Version('v1.3.37+007'))->setBuild(null);

        $this->assertNull($version->build);
    }

    #[Test]
    public function it_can_be_greater_than_another_semver_object(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertTrue($version->gt(new SemVer\Version('v0.5.0')));
        $this->assertTrue($version->gt(new SemVer\Version('v1.3.36')));
        $this->assertFalse($version->gt(new SemVer\Version('v1.3.38')));
        $this->assertFalse($version->gt(new SemVer\Version('v1.3.37')));
    }

    #[Test]
    public function it_can_be_less_than_another_semver_object(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertTrue($version->lt(new SemVer\Version('v1.3.38')));
        $this->assertTrue($version->lt(new SemVer\Version('v1.4.0')));
        $this->assertFalse($version->lt(new SemVer\Version('v1.3.36')));
        $this->assertFalse($version->lt(new SemVer\Version('v1.3.37')));
    }

    #[Test]
    public function it_can_be_equal_to_another_semver_object(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertTrue($version->eq(new SemVer\Version('v1.3.37')));
        $this->assertFalse($version->eq(new SemVer\Version('v1.2.3')));
    }

    #[Test]
    public function it_can_be_not_equal_to_another_semver_object(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertTrue($version->neq(new SemVer\Version('v1.2.3')));
        $this->assertFalse($version->neq(new SemVer\Version('v1.3.37')));
    }

    #[Test]
    public function it_can_be_greater_than_or_equal_to_another_semver_object(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertTrue($version->gte(new SemVer\Version('v1.2.3')));
        $this->assertTrue($version->gte(new SemVer\Version('v1.3.37')));
        $this->assertFalse($version->gte(new SemVer\Version('v2.3.4')));
    }

    #[Test]
    public function it_can_be_less_than_or_equal_to_another_semver_object(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertTrue($version->lte(new SemVer\Version('v2.3.4')));
        $this->assertTrue($version->lte(new SemVer\Version('v1.3.37')));
        $this->assertFalse($version->lte(new SemVer\Version('v1.2.3')));
    }

    #[Test]
    public function it_can_be_greater_than_another_semver_object_using_major(): void
    {
        $version = new SemVer\Version('v2.3.37');

        $this->assertTrue($version->gt(new SemVer\Version('v0.5.0'), Compare::MAJOR));
        $this->assertTrue($version->gt(new SemVer\Version('v1.3.38'), Compare::MAJOR));
        $this->assertFalse($version->gt(new SemVer\Version('v2.3.36'), Compare::MAJOR));
        $this->assertFalse($version->gt(new SemVer\Version('v3.3.36'), Compare::MAJOR));
    }

    #[Test]
    public function it_can_be_less_than_another_semver_object_using_major(): void
    {
        $version = new SemVer\Version('v2.3.37');

        $this->assertTrue($version->lt(new SemVer\Version('v3.3.38'), Compare::MAJOR));
        $this->assertFalse($version->lt(new SemVer\Version('v2.3.36'), Compare::MAJOR));
        $this->assertFalse($version->lt(new SemVer\Version('v1.3.37'), Compare::MAJOR));
    }

    #[Test]
    public function it_can_be_equal_to_another_semver_object_using_major(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->eq(new SemVer\Version('v1.3.37-alpha.4'), Compare::MAJOR));
        $this->assertTrue($version->eq(new SemVer\Version('v1.2.3'), Compare::MAJOR));
    }

    #[Test]
    public function it_can_be_not_equal_to_another_semver_object_using_major(): void
    {
        $version = new SemVer\Version('v2.3.37');

        $this->assertTrue($version->neq(new SemVer\Version('v3.2.3'), Compare::MAJOR));
        $this->assertFalse($version->neq(new SemVer\Version('v2.2.3'), Compare::MAJOR));
        $this->assertTrue($version->neq(new SemVer\Version('v1.3.37'), Compare::MAJOR));
    }

    #[Test]
    public function it_can_be_greater_than_or_equal_to_another_semver_object_using_major(): void
    {
        $version = new SemVer\Version('v3.3.37-alpha.5');

        $this->assertTrue($version->gte(new SemVer\Version('v1.3.4'), Compare::MAJOR));
        $this->assertTrue($version->gte(new SemVer\Version('v2.3.37-alpha.6'), Compare::MAJOR));
        $this->assertTrue($version->gte(new SemVer\Version('v3.2.3'), Compare::MAJOR));
        $this->assertFalse($version->gte(new SemVer\Version('v4.2.3'), Compare::MAJOR));
    }

    #[Test]
    public function it_can_be_less_than_or_equal_to_another_semver_object_using_major(): void
    {
        $version = new SemVer\Version('v2.3.37-alpha.5');

        $this->assertTrue($version->lte(new SemVer\Version('v3.2.3'), Compare::MAJOR));
        $this->assertTrue($version->lte(new SemVer\Version('v2.3.37-alpha.4'), Compare::MAJOR));
        $this->assertFalse($version->lte(new SemVer\Version('v1.3.4'), Compare::MAJOR));
    }

    #[Test]
    public function it_can_be_greater_than_another_semver_object_using_minor(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->gt(new SemVer\Version('v0.5.0'), Compare::MINOR));
        $this->assertTrue($version->gt(new SemVer\Version('v1.2.40'), Compare::MINOR));
        $this->assertFalse($version->gt(new SemVer\Version('v1.3.38-alpha.5'), Compare::MINOR));
        $this->assertFalse($version->gt(new SemVer\Version('v1.4.0'), Compare::MINOR));
    }

    #[Test]
    public function it_can_be_less_than_another_semver_object_using_minor(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->lt(new SemVer\Version('v1.4.38'), Compare::MINOR));
        $this->assertTrue($version->lt(new SemVer\Version('v1.4.0'), Compare::MINOR));
        $this->assertFalse($version->lt(new SemVer\Version('v1.2.36'), Compare::MINOR));
        $this->assertFalse($version->lt(new SemVer\Version('v1.3.37-alpha.6'), Compare::MINOR));
    }

    #[Test]
    public function it_can_be_equal_to_another_semver_object_using_minor(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertFalse($version->eq(new SemVer\Version('v1.4.3'), Compare::MINOR));
        $this->assertTrue($version->eq(new SemVer\Version('v1.3.37-alpha.4'), Compare::MINOR));
        $this->assertFalse($version->eq(new SemVer\Version('v1.2.3'), Compare::MINOR));
    }

    #[Test]
    public function it_can_be_not_equal_to_another_semver_object_using_minor(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->neq(new SemVer\Version('v1.2.37-alpha.5'), Compare::MINOR));
        $this->assertFalse($version->neq(new SemVer\Version('v1.3.37-alpha.6'), Compare::MINOR));
        $this->assertTrue($version->neq(new SemVer\Version('v1.4.37'), Compare::MINOR));
    }

    #[Test]
    public function it_can_be_greater_than_or_equal_to_another_semver_object_using_minor(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->gte(new SemVer\Version('v1.3.38-alpha.4'), Compare::MINOR));
        $this->assertTrue($version->gte(new SemVer\Version('v1.2.3'), Compare::MINOR));
        $this->assertFalse($version->gte(new SemVer\Version('v1.4.4'), Compare::MINOR));
        $this->assertFalse($version->gte(new SemVer\Version('v2.3.4'), Compare::MINOR));
    }

    #[Test]
    public function it_can_be_less_than_or_equal_to_another_semver_object_using_minor(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->lte(new SemVer\Version('v2.3.4'), Compare::MINOR));
        $this->assertTrue($version->lte(new SemVer\Version('v1.3.37'), Compare::MINOR));
        $this->assertTrue($version->lte(new SemVer\Version('v1.3.36'), Compare::MINOR));
        $this->assertFalse($version->lte(new SemVer\Version('v1.2.37-alpha.5'), Compare::MINOR));
    }

    #[Test]
    public function it_can_be_greater_than_another_semver_object_using_patch(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->gt(new SemVer\Version('v0.5.0'), Compare::PATCH));
        $this->assertTrue($version->gt(new SemVer\Version('v1.3.36'), Compare::PATCH));
        $this->assertFalse($version->gt(new SemVer\Version('v1.3.38'), Compare::PATCH));
        $this->assertFalse($version->gt(new SemVer\Version('v1.3.37-alpha.4'), Compare::PATCH));
    }

    #[Test]
    public function it_can_be_less_than_another_semver_object_using_patch(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->lt(new SemVer\Version('v1.3.38'), Compare::PATCH));
        $this->assertTrue($version->lt(new SemVer\Version('v1.4.0'), Compare::PATCH));
        $this->assertFalse($version->lt(new SemVer\Version('v1.3.36'), Compare::PATCH));
        $this->assertFalse($version->lt(new SemVer\Version('v1.3.37-alpha.6'), Compare::PATCH));
    }

    #[Test]
    public function it_can_be_equal_to_another_semver_object_using_patch(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->eq(new SemVer\Version('v1.3.37-alpha.6'), Compare::PATCH));
        $this->assertFalse($version->eq(new SemVer\Version('v1.2.3'), Compare::PATCH));
    }

    #[Test]
    public function it_can_be_not_equal_to_another_semver_object_using_patch(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->neq(new SemVer\Version('v1.2.3'), Compare::PATCH));
        $this->assertFalse($version->neq(new SemVer\Version('v1.3.37-alpha.6'), Compare::PATCH));
    }

    #[Test]
    public function it_can_be_greater_than_or_equal_to_another_semver_object_using_patch(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->gte(new SemVer\Version('v1.2.3'), Compare::PATCH));
        $this->assertTrue($version->gte(new SemVer\Version('v1.3.37-alpha.6'), Compare::PATCH));
        $this->assertFalse($version->gte(new SemVer\Version('v2.3.4'), Compare::PATCH));
    }

    #[Test]
    public function it_can_be_less_than_or_equal_to_another_semver_object_using_patch(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5');

        $this->assertTrue($version->lte(new SemVer\Version('v2.3.4'), Compare::PATCH));
        $this->assertTrue($version->lte(new SemVer\Version('v1.3.37-alpha.4'), Compare::PATCH));
        $this->assertFalse($version->lte(new SemVer\Version('v1.2.3'), Compare::PATCH));
    }

    #[Test]
    public function setting_the_major_version_resets_appropriate_properties(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5+007');
        $version->setMajor(2);

        $this->assertEquals(2, $version->major);
        $this->assertEquals(0, $version->minor);
        $this->assertEquals(0, $version->patch);
        $this->assertNull($version->preRelease);
        $this->assertNull($version->build);
    }

    #[Test]
    public function setting_the_minor_version_resets_appropriate_properties(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5+007');
        $version->setMinor(4);

        $this->assertEquals(1, $version->major);
        $this->assertEquals(4, $version->minor);
        $this->assertEquals(0, $version->patch);
        $this->assertNull($version->preRelease);
        $this->assertNull($version->build);
    }

    #[Test]
    public function setting_the_patch_version_resets_appropriate_properties(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5+007');
        $version->setPatch(38);

        $this->assertEquals(1, $version->major);
        $this->assertEquals(3, $version->minor);
        $this->assertEquals(38, $version->patch);
        $this->assertNull($version->preRelease);
        $this->assertNull($version->build);
    }

    #[Test]
    public function it_compares_pre_release_tags(): void
    {
        $alpha = new SemVer\Version('v1.3.37-alpha');
        $beta = new SemVer\Version('v1.3.37-beta');

        $this->assertTrue($alpha->lt($beta));
        $this->assertFalse($alpha->gt($beta));
        $this->assertTrue($alpha->lte($beta));
        $this->assertFalse($alpha->gte($beta));
        $this->assertFalse($alpha->eq($beta));
    }

    #[Test]
    public function it_ignores_the_build_version_when_comparing_versions(): void
    {
        $oldBuild = new SemVer\Version('v1.3.37-alpha.5+006');
        $newBuild = new SemVer\Version('v1.3.37-alpha.5+007');

        $this->assertTrue($oldBuild->eq($newBuild));
        $this->assertFalse($oldBuild->neq($newBuild));
        $this->assertFalse($oldBuild->gt($newBuild));
        $this->assertFalse($oldBuild->lt($newBuild));
        $this->assertTrue($oldBuild->gte($newBuild));
        $this->assertTrue($oldBuild->lte($newBuild));

        $this->assertTrue((new SemVer\Version('v1.3.37'))->eq(new SemVer\Version('v1.3.37+007')));
    }

    #[Test]
    #[\PHPUnit\Framework\Attributes\DataProvider('pre_release_comparison_provider')]
    public function it_compares_pre_release_tags_vs_release(string $release, string $prerelease): void
    {
        $release = new SemVer\Version($release);
        $prerelease = new SemVer\Version($prerelease);

        $this->assertFalse($release->eq($prerelease));
        $this->assertFalse($release->lt($prerelease));
        $this->assertFalse($release->lte($prerelease));
        $this->assertTrue($release->gt($prerelease));
        $this->assertTrue($release->gte($prerelease));

        $this->assertFalse($prerelease->gt($release));
        $this->assertFalse($prerelease->eq($release));
        $this->assertFalse($prerelease->gte($release));
        $this->assertTrue($prerelease->lt($release));
        $this->assertTrue($prerelease->lte($release));
    }

    #[Test]
    public function it_can_compare_two_versions(): void
    {
        $version1 = new SemVer\Version('v1.3.37');
        $version2 = new SemVer\Version('v3.2.1');

        // Major Comparisons
        $this->assertEquals(-1, SemVer\Version::compare(new SemVer\Version('v1.2.3'), new SemVer\Version('v3.2.1')));
        $this->assertEquals(0, SemVer\Version::compare(new SemVer\Version('v1.2.3'), new SemVer\Version('v1.2.3')));
        $this->assertEquals(1, SemVer\Version::compare(new SemVer\Version('v3.2.1'), new SemVer\Version('v1.2.3')));

        // Minor Comparisons
        $this->assertEquals(-1, SemVer\Version::compare(new SemVer\Version('v0.1.2'), new SemVer\Version('v0.2.1')));
        $this->assertEquals(0, SemVer\Version::compare(new SemVer\Version('v0.1.2'), new SemVer\Version('v0.1.2')));
        $this->assertEquals(1, SemVer\Version::compare(new SemVer\Version('v0.2.1'), new SemVer\Version('v0.1.2')));

        // Patch Comparisons
        $this->assertEquals(-1, SemVer\Version::compare(new SemVer\Version('v1.0.1'), new SemVer\Version('v1.0.2')));
        $this->assertEquals(0, SemVer\Version::compare(new SemVer\Version('v1.0.0'), new SemVer\Version('v1.0.0')));
        $this->assertEquals(1, SemVer\Version::compare(new SemVer\Version('v1.0.2'), new SemVer\Version('v1.0.1')));
    }

    #[Test]
    public function it_can_be_serialized_to_json(): void
    {
        $version = new SemVer\Version('v1.3.37');

        $this->assertInstanceOf(JsonSerializable::class, $version);
        $this->assertEquals('1.3.37', $version->jsonSerialize());
    }

    #[Test]
    public function it_can_determine_if_it_is_a_pre_release(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5+007');

        $this->assertTrue($version->isPreRelease());
    }

    #[Test]
    public function can_determine_if_it_is_not_a_pre_release(): void
    {
        $version = new SemVer\Version('v1.3.37+007');

        $this->assertFalse($version->isPreRelease());
    }

    #[Test]
    public function can_determine_if_it_has_a_build_string(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha.5+007');

        $this->assertTrue($version->hasBuild());
    }

    #[Test]
    public function it_can_determine_if_it_has_a_build_string(): void
    {
        $version = new SemVer\Version('v1.3.37-alpha');

        $this->assertFalse($version->hasBuild());
    }
}
Download .txt
gitextract_fljnwk0f/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       └── test-suite.yaml
├── .gitignore
├── .php-cs-fixer.dist.php
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── phpstan-ignores.neon
├── phpstan.neon.dist
├── phpunit.xml
├── src/
│   ├── Enums/
│   │   └── Compare.php
│   ├── Exceptions/
│   │   └── InvalidVersionException.php
│   ├── Support/
│   │   └── helpers.php
│   ├── Traits/
│   │   ├── Comparable.php
│   │   └── Incrementable.php
│   └── Version.php
└── tests/
    └── VersionTest.php
Download .txt
SYMBOL INDEX (93 symbols across 6 files)

FILE: src/Exceptions/InvalidVersionException.php
  class InvalidVersionException (line 7) | class InvalidVersionException extends Exception {}

FILE: src/Support/helpers.php
  function semver (line 11) | function semver(string $string): Version

FILE: src/Traits/Comparable.php
  type Comparable (line 8) | trait Comparable
    method compare (line 17) | public static function compare(Version $version1, Version $version2, C...
    method gt (line 56) | public function gt(Version $version, Compare $comparison = Compare::FU...
    method lt (line 69) | public function lt(Version $version, Compare $comparison = Compare::FU...
    method eq (line 82) | public function eq(Version $version, Compare $comparison = Compare::FU...
    method neq (line 95) | public function neq(Version $version, Compare $comparison = Compare::F...
    method gte (line 108) | public function gte(Version $version, Compare $comparison = Compare::F...
    method lte (line 121) | public function lte(Version $version, Compare $comparison = Compare::F...

FILE: src/Traits/Incrementable.php
  type Incrementable (line 5) | trait Incrementable
    method incrementMajor (line 12) | public function incrementMajor(): static
    method incrementMinor (line 24) | public function incrementMinor(): static
    method incrementPatch (line 36) | public function incrementPatch(): static
    method incrementPreRelease (line 48) | public function incrementPreRelease(): static

FILE: src/Version.php
  class Version (line 17) | class Version implements JsonSerializable
    method __construct (line 44) | final public function __construct(string $version = '0.1.0')
    method __get (line 56) | public function __get(string $property)
    method __toString (line 66) | public function __toString(): string
    method parse (line 92) | public static function parse(string $version): static
    method jsonSerialize (line 114) | public function jsonSerialize(): mixed
    method setVersion (line 128) | public function setVersion(string $version): static
    method setMajor (line 152) | public function setMajor(int $value): static
    method setMinor (line 167) | public function setMinor(int $value): static
    method setPatch (line 182) | public function setPatch(int $value): static
    method setPreRelease (line 198) | public function setPreRelease($value): static
    method setBuild (line 212) | public function setBuild($value): static
    method prefix (line 227) | public function prefix(string $prefix = 'v'): string
    method isPreRelease (line 237) | public function isPreRelease(): bool
    method hasBuild (line 247) | public function hasBuild(): bool

FILE: tests/VersionTest.php
  class VersionTest (line 17) | #[CoversClass(Version::class)]
    method pre_release_comparison_provider (line 23) | public static function pre_release_comparison_provider(): array
    method it_can_be_initialized (line 41) | #[Test]
    method it_can_be_initialized_with_the_helper_function (line 49) | #[Test]
    method it_throws_a_runtime_exception_for_an_invalid_version (line 57) | #[Test]
    method it_can_parse_an_incomplete_version_string (line 65) | #[Test]
    method it_throws_a_runtime_exception_when_parsing_an_invalid_version (line 79) | #[Test]
    method it_can_set_and_retrieve_a_version (line 87) | #[Test]
    method it_can_return_a_prefixed_version_string (line 96) | #[Test]
    method it_can_be_cast_to_a_string (line 105) | #[Test]
    method it_can_get_individual_properties (line 114) | #[Test]
    method it_can_increment_major (line 126) | #[Test]
    method it_can_set_major (line 135) | #[Test]
    method it_can_increment_minor (line 144) | #[Test]
    method it_can_set_minor (line 153) | #[Test]
    method it_can_increment_patch (line 162) | #[Test]
    method it_can_increment_prerelease (line 171) | #[Test]
    method it_can_increment_prerelease_without_number (line 181) | #[Test]
    method it_can_increment_prerelease_without_prefix_for_prerelease (line 191) | #[Test]
    method it_can_increment_prerelease_containing_multiple_dots (line 201) | #[Test]
    method it_can_increment_prerelease_containing_multiple_dots_and_without_number (line 211) | #[Test]
    method it_can_increment_prerelease_and_patch_when_prerelease_is_null (line 221) | #[Test]
    method it_can_set_patch (line 230) | #[Test]
    method it_can_set_pre_release (line 239) | #[Test]
    method it_can_unset_pre_release (line 248) | #[Test]
    method it_can_set_build (line 256) | #[Test]
    method it_can_unset_build (line 265) | #[Test]
    method it_can_be_greater_than_another_semver_object (line 273) | #[Test]
    method it_can_be_less_than_another_semver_object (line 284) | #[Test]
    method it_can_be_equal_to_another_semver_object (line 295) | #[Test]
    method it_can_be_not_equal_to_another_semver_object (line 304) | #[Test]
    method it_can_be_greater_than_or_equal_to_another_semver_object (line 313) | #[Test]
    method it_can_be_less_than_or_equal_to_another_semver_object (line 323) | #[Test]
    method it_can_be_greater_than_another_semver_object_using_major (line 333) | #[Test]
    method it_can_be_less_than_another_semver_object_using_major (line 344) | #[Test]
    method it_can_be_equal_to_another_semver_object_using_major (line 354) | #[Test]
    method it_can_be_not_equal_to_another_semver_object_using_major (line 363) | #[Test]
    method it_can_be_greater_than_or_equal_to_another_semver_object_using_major (line 373) | #[Test]
    method it_can_be_less_than_or_equal_to_another_semver_object_using_major (line 384) | #[Test]
    method it_can_be_greater_than_another_semver_object_using_minor (line 394) | #[Test]
    method it_can_be_less_than_another_semver_object_using_minor (line 405) | #[Test]
    method it_can_be_equal_to_another_semver_object_using_minor (line 416) | #[Test]
    method it_can_be_not_equal_to_another_semver_object_using_minor (line 426) | #[Test]
    method it_can_be_greater_than_or_equal_to_another_semver_object_using_minor (line 436) | #[Test]
    method it_can_be_less_than_or_equal_to_another_semver_object_using_minor (line 447) | #[Test]
    method it_can_be_greater_than_another_semver_object_using_patch (line 458) | #[Test]
    method it_can_be_less_than_another_semver_object_using_patch (line 469) | #[Test]
    method it_can_be_equal_to_another_semver_object_using_patch (line 480) | #[Test]
    method it_can_be_not_equal_to_another_semver_object_using_patch (line 489) | #[Test]
    method it_can_be_greater_than_or_equal_to_another_semver_object_using_patch (line 498) | #[Test]
    method it_can_be_less_than_or_equal_to_another_semver_object_using_patch (line 508) | #[Test]
    method setting_the_major_version_resets_appropriate_properties (line 518) | #[Test]
    method setting_the_minor_version_resets_appropriate_properties (line 531) | #[Test]
    method setting_the_patch_version_resets_appropriate_properties (line 544) | #[Test]
    method it_compares_pre_release_tags (line 557) | #[Test]
    method it_ignores_the_build_version_when_comparing_versions (line 570) | #[Test]
    method it_compares_pre_release_tags_vs_release (line 586) | #[Test]
    method it_can_compare_two_versions (line 606) | #[Test]
    method it_can_be_serialized_to_json (line 628) | #[Test]
    method it_can_determine_if_it_is_a_pre_release (line 637) | #[Test]
    method can_determine_if_it_is_not_a_pre_release (line 645) | #[Test]
    method can_determine_if_it_has_a_build_string (line 653) | #[Test]
    method it_can_determine_if_it_has_a_build_string (line 661) | #[Test]
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (53K chars).
[
  {
    "path": ".editorconfig",
    "chars": 305,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".gitattributes",
    "chars": 180,
    "preview": "tests           export-ignore\n.gitattributes  export-ignore\n.gitignore      export-ignore\n.php_cs.dist    export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 71,
    "preview": "github: PHLAK\npatreon: PHLAK\ncustom: https://paypal.me/ChrisKankiewicz\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 349,
    "preview": "version: 2\n\nupdates:\n  - package-ecosystem: composer\n    directory: \"/\"\n    schedule:\n      interval: monthly\n      time"
  },
  {
    "path": ".github/workflows/test-suite.yaml",
    "chars": 1645,
    "preview": "name: SemVer Test Suite\non: [push, pull_request, workflow_dispatch]\n\njobs:\n  coding-standards:\n    name: Coding Standard"
  },
  {
    "path": ".gitignore",
    "chars": 61,
    "preview": "/.php-cs-fixer.cache\n/.phpunit.cache\n/composer.lock\n/vendor/\n"
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "chars": 246,
    "preview": "<?php\n\nrequire __DIR__ . '/vendor/autoload.php';\n\n$finder = PhpCsFixer\\Finder::create()->in([\n    __DIR__ . DIRECTORY_SE"
  },
  {
    "path": "LICENSE",
    "chars": 1101,
    "preview": "MIT License\n\nCopyright (c) 2024 Chris Kankiewicz <Chris@ChrisKankiewicz.com>\n\nPermission is hereby granted, free of char"
  },
  {
    "path": "Makefile",
    "chars": 502,
    "preview": "dev development: # Build application for development\n\t@composer install --no-interaction\n\nprod production: # Build appli"
  },
  {
    "path": "README.md",
    "chars": 5991,
    "preview": "SemVer\n======\n\n<p align=\"center\">\n  <img src=\"semver.png\" alt=\"SemVer\" width=\"50%\">\n</p>\n\n<p align=\"center\">\n    <a href"
  },
  {
    "path": "composer.json",
    "chars": 1229,
    "preview": "{\n    \"name\": \"phlak/semver\",\n    \"description\": \"Semantic versioning helper library\",\n    \"type\": \"library\",\n    \"licen"
  },
  {
    "path": "phpstan-ignores.neon",
    "chars": 153,
    "preview": "parameters:\n  ignoreErrors:\n    - message: \"/^Method .+ throws checked exception .+ but it's missing from the PHPDoc @th"
  },
  {
    "path": "phpstan.neon.dist",
    "chars": 348,
    "preview": "parameters:\n\n  paths:\n    - src\n    - tests\n\n  level: max\n\n  checkFunctionNameCase: true\n\n  exceptions:\n    implicitThro"
  },
  {
    "path": "phpunit.xml",
    "chars": 475,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\" colors=\"true\"\n    xmlns:xsi=\"http://www."
  },
  {
    "path": "src/Enums/Compare.php",
    "chars": 118,
    "preview": "<?php\n\nnamespace PHLAK\\SemVer\\Enums;\n\nenum Compare\n{\n    case FULL;\n    case MAJOR;\n    case MINOR;\n    case PATCH;\n}\n"
  },
  {
    "path": "src/Exceptions/InvalidVersionException.php",
    "chars": 110,
    "preview": "<?php\n\nnamespace PHLAK\\SemVer\\Exceptions;\n\nuse Exception;\n\nclass InvalidVersionException extends Exception {}\n"
  },
  {
    "path": "src/Support/helpers.php",
    "chars": 304,
    "preview": "<?php\n\nuse PHLAK\\SemVer\\Version;\n\nif (! function_exists('semver')) {\n    /**\n     * Create a SemVer version object.\n    "
  },
  {
    "path": "src/Traits/Comparable.php",
    "chars": 4318,
    "preview": "<?php\n\nnamespace PHLAK\\SemVer\\Traits;\n\nuse PHLAK\\SemVer\\Enums\\Compare;\nuse PHLAK\\SemVer\\Version;\n\ntrait Comparable\n{\n   "
  },
  {
    "path": "src/Traits/Incrementable.php",
    "chars": 1491,
    "preview": "<?php\n\nnamespace PHLAK\\SemVer\\Traits;\n\ntrait Incrementable\n{\n    /**\n     * Increment the major version value by one.\n  "
  },
  {
    "path": "src/Version.php",
    "chars": 6480,
    "preview": "<?php\n\nnamespace PHLAK\\SemVer;\n\nuse JsonSerializable;\nuse PHLAK\\SemVer\\Exceptions\\InvalidVersionException;\nuse PHLAK\\Sem"
  },
  {
    "path": "tests/VersionTest.php",
    "chars": 24599,
    "preview": "<?php\n\nnamespace PHLAK\\SemVer\\Tests;\n\nuse JsonSerializable;\nuse PHLAK\\SemVer;\nuse PHLAK\\SemVer\\Enums\\Compare;\nuse PHLAK\\"
  }
]

About this extraction

This page contains the full source code of the PHLAK/SemVer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (48.9 KB), approximately 14.7k tokens, and a symbol index with 93 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!