Completed
Push — master ( 444b50...ece018 )
by Gábor
03:00
created

SemVer::match()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 3
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the egabor/composer-release-plugin package.
5
 *
6
 * (c) Gábor Egyed <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace egabor\Composer\ReleasePlugin;
13
14
use egabor\Composer\ReleasePlugin\Exception\InvalidVersionException;
15
16
/**
17
 * Tokenize, validate, and parse SemVer version strings.
18
 *
19
 * @author Gábor Egyed <[email protected]>
20
 */
21
abstract class SemVer
22
{
23
    private $major;
24
    private $minor;
25
    private $patch;
26
    private $pre;
27
    private $build;
28
29
    public function __construct($major, $minor, $patch, $pre = '', $build = '')
30
    {
31
        $this->assertValidNumericIdentifier($major);
32
        $this->assertValidNumericIdentifier($minor);
33
        $this->assertValidNumericIdentifier($patch);
34
        $pre ? $this->assertValidPreReleaseVersion($pre) : null;
35
        $build ? $this->assertValidBuildMetadata($build) : null;
36
37
        $this->major = (string) $major;
38
        $this->minor = (string) $minor;
39
        $this->patch = (string) $patch;
40
        $this->pre = (string) $pre;
41
        $this->build = (string) $build;
42
    }
43
44
    public function __toString()
45
    {
46
        return implode('.', [$this->major, $this->minor, $this->patch])
47
            .$this->prefix('-', $this->pre)
48
            .$this->prefix('+', $this->build);
49
    }
50
51
    public static function fromString($version)
52
    {
53
        $parts = self::parse($version);
54
55
        return new static($parts['major'], $parts['minor'], $parts['patch'], $parts['pre'], $parts['build']);
56
    }
57
58
    public function getMajor()
59
    {
60
        return $this->major;
61
    }
62
63
    public function getMinor()
64
    {
65
        return $this->minor;
66
    }
67
68
    public function getPatch()
69
    {
70
        return $this->patch;
71
    }
72
73
    public function getPre()
74
    {
75
        return $this->pre;
76
    }
77
78
    public function getBuild()
79
    {
80
        return $this->build;
81
    }
82
83
    protected static function match($pattern, $value, $invalidValueMessage)
84
    {
85
        if (!preg_match('{\\A'.$pattern.'\\Z}i', $value, $matches)) {
86
            if (PREG_NO_ERROR !== $error = preg_last_error()) {
87
                throw new \RuntimeException(sprintf('PCRE regex error with code: "%s"', $error)); // @codeCoverageIgnore
88
            }
89
90
            throw new InvalidVersionException(sprintf($invalidValueMessage, $value));
91
        }
92
93
        return $matches;
94
    }
95
96
    private static function parse($version)
97
    {
98
        $p = self::patterns();
99
        // Main Version - Three dot-separated numeric identifiers.
100
        $mainVersion = '(?P<major>'.$p['numeric_identifier'].')\\.(?P<minor>'.$p['numeric_identifier'].')\\.(?P<patch>'.$p['numeric_identifier'].')';
101
        // Pre-release Version - Hyphen, followed by one or more dot-separated pre-release version identifiers.
102
        $pre = '(?:-(?P<pre>'.$p['pre_version'].'))';
103
        // Build Metadata - Plus sign, followed by one or more dot-separated build metadata identifiers.
104
        $build = '(?:\\+(?P<build>'.$p['build_meta'].'))';
105
        // Full Version String - A main version, followed optionally by a pre-release version and build metadata.
106
        $full = 'v?'.$mainVersion.$pre.'?'.$build.'?';
107
108
        $parts = self::match($full, trim((string) $version), 'Version "%s" is not valid and cannot be parsed.');
109
110
        return array_replace(['major' => '', 'minor' => '', 'patch' => '', 'pre' => '', 'build' => ''], $parts);
111
    }
112
113
    private function prefix($prefix, $string)
114
    {
115
        return '' !== $string ? $prefix.$string : $string;
116
    }
117
118
    private function assertValidNumericIdentifier($value)
119
    {
120
        self::match(self::patterns()['numeric_identifier'], $value, 'Invalid numeric value "%s".');
121
    }
122
123
    private function assertValidPreReleaseVersion($value)
124
    {
125
        self::match(self::patterns()['pre_version'], $value, 'Invalid pre-release version "%s".');
126
    }
127
128
    private function assertValidBuildMetadata($value)
129
    {
130
        self::match(self::patterns()['build_meta'], $value, 'Invalid build metadata "%s".');
131
    }
132
133
    /**
134
     * @todo move to private static properties, requires php 5.6
135
     * @todo move to private constants, requires php 7.1
136
     */
137
    private static function patterns()
138
    {
139
        $patterns = [
140
            // Numeric Identifier - A single `0`, or a non-zero digit followed by zero or more digits.
141
            'numeric_identifier' => '0|[1-9]\\d*',
142
            // Non-numeric Identifier - Zero or more digits, followed by a letter or hyphen, and then zero or more letters, digits, or hyphens.
143
            'non_numeric_identifier' => '\\d*[a-zA-Z-][a-zA-Z0-9-]*',
144
            // Build Metadata Identifier - Any combination of digits, letters, or hyphens.
145
            'build_identifier' => '[0-9A-Za-z-]+',
146
        ];
147
148
        $patterns += [
149
            // Pre-release Version Identifier - A numeric identifier, or a non-numeric identifier.
150
            'pre_identifier' => '(?:'.$patterns['numeric_identifier'].'|'.$patterns['non_numeric_identifier'].')',
151
        ];
152
153
        return $patterns + [
154
            // Pre-release Version - One or more dot-separated pre-release version identifiers.
155
            'pre_version' => $patterns['pre_identifier'].'(?:\\.'.$patterns['pre_identifier'].')*',
156
            // Build Metadata - One or more dot-separated build metadata identifiers.
157
            'build_meta' => $patterns['build_identifier'].'(?:\\.'.$patterns['build_identifier'].')*',
158
        ];
159
    }
160
}
161