Completed
Push — master ( 383c64...d37ce4 )
by brian
03:46
created

HyphenatedRangeParser::getUpperConstraint()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 13
cts 13
cp 1
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 1
crap 4
1
<?php
2
3
/**
4
 * @copyright   (c) 2014-2017 brian ridley
5
 * @author      brian ridley <[email protected]>
6
 * @license     http://opensource.org/licenses/MIT MIT
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 ptlis\SemanticVersion\Parse\RangeParser;
13
14
use ptlis\SemanticVersion\Comparator\ComparatorInterface;
15
use ptlis\SemanticVersion\Parse\ChunkBySeparator;
16
use ptlis\SemanticVersion\Parse\Token;
17
use ptlis\SemanticVersion\Parse\VersionParser;
18
use ptlis\SemanticVersion\Version\Version;
19
use ptlis\SemanticVersion\VersionRange\ComparatorVersion;
20
use ptlis\SemanticVersion\VersionRange\LogicalAnd;
21
use ptlis\SemanticVersion\VersionRange\VersionRangeInterface;
22
23
/**
24
 * Parser for hyphenated ranges.
25
 *
26
 * Hyphenated ranges are implemented as described @ https://getcomposer.org/doc/articles/versions.md#range-hyphen-
27
 */
28
final class HyphenatedRangeParser implements RangeParserInterface
29
{
30
    use ChunkBySeparator;
31
32
    /** @var VersionParser */
33
    private $versionParser;
34
35
    /** @var ComparatorInterface */
36
    private $greaterOrEqualTo;
37
38
    /** @var ComparatorInterface */
39
    private $lessThan;
40
41
    /** @var ComparatorInterface */
42
    private $lessOrEqualTo;
43
44
    /** @var string[][] */
45
    private $validConfigurations = [
46
        [Token::DIGITS, Token::DIGITS],
47
        [Token::DIGITS, Token::LABEL_STRING, Token::DIGITS],
48
        [Token::DIGITS, Token::DIGITS, Token::LABEL_STRING],
49
        [Token::DIGITS, Token::LABEL_STRING, Token::DIGITS, Token::LABEL_STRING]
50
    ];
51
52
53
    /**
54
     * Constructor.
55
     *
56
     * @param VersionParser $versionParser
57
     * @param ComparatorInterface $greaterOrEqualTo
58
     * @param ComparatorInterface $lessThan
59
     * @param ComparatorInterface $lessOrEqualTo
60
     */
61 11
    public function __construct(
62
        VersionParser $versionParser,
63
        ComparatorInterface $greaterOrEqualTo,
64
        ComparatorInterface $lessThan,
65
        ComparatorInterface $lessOrEqualTo
66
    ) {
67 11
        $this->versionParser = $versionParser;
68 11
        $this->greaterOrEqualTo = $greaterOrEqualTo;
69 11
        $this->lessThan = $lessThan;
70 11
        $this->lessOrEqualTo = $lessOrEqualTo;
71 11
    }
72
73
    /**
74
     * Returns true if the token list can be parsed as a hyphenated range.
75
     *
76
     * @param Token[] $tokenList
77
     *
78
     * @return boolean
79
     */
80 11
    public function canParse(array $tokenList)
81
    {
82 11
        $isRange = false;
83 11
        $chunkedList = $this->chunkByDash($tokenList);
84 11
        foreach ($this->validConfigurations as $configuration) {
85 11
            list($lowerVersionTokenList, $upperVersionTokenList) = $this->getSingleVersionTokens($chunkedList);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $lowerVersionTokenList exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
Comprehensibility Naming introduced by
The variable name $upperVersionTokenList exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
86 11
            $isRange = $isRange || (
87 11
                $this->chunksMatchConfiguration($chunkedList, $configuration)
88 11
                && $this->versionParser->parse($lowerVersionTokenList)
89 11
                && $this->versionParser->parse($upperVersionTokenList)
90
            );
91
        }
92
93 11
        return $isRange;
94
    }
95
96
    /**
97
     * Build a ComparatorVersion representing the hyphenated range.
98
     *
99
     * @param Token[] $tokenList
100
     *
101
     * @return VersionRangeInterface
102
     */
103 10
    public function parse(array $tokenList)
104
    {
105 10
        if (!$this->canParse($tokenList)) {
106 4
            throw new \RuntimeException('Invalid version');
107
        }
108
109 6
        $chunkedList = $this->chunkByDash($tokenList);
110
111 6
        list($lowerVersionTokenList, $upperVersionTokenList) = $this->getSingleVersionTokens($chunkedList);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $lowerVersionTokenList exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
Comprehensibility Naming introduced by
The variable name $upperVersionTokenList exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
112
113 6
        return new LogicalAnd(
114 6
            new ComparatorVersion(
115 6
                $this->greaterOrEqualTo,
116 6
                $this->versionParser->parse($lowerVersionTokenList)
117
            ),
118 6
            $this->getUpperConstraint($upperVersionTokenList)
119
        );
120
    }
121
122
    /**
123
     * Chunk a token list by the dash seperator, returning array of token lists omitting the seperator tokens.
124
     *
125
     * @param Token[] $tokenList
126
     * @return Token[][]
127
     */
128 11
    private function chunkByDash(array $tokenList)
129
    {
130 11
        return array_values(array_filter(
131 11
            $this->chunk($tokenList, [Token::DASH_SEPARATOR]),
132 11
            function ($chunk) {
133 11
                return 1 !== count($chunk) || (1 === count($chunk) && Token::DASH_SEPARATOR !== $chunk[0]->getType());
134 11
            }
135
        ));
136
    }
137
138
    /**
139
     * Returns an array of token arrays, the first is tokens for the lower bound and the second is the upper bound.
140
     *
141
     * @param Token[][] $chunkedList
142
     *
143
     * @return Token[][]
0 ignored issues
show
Documentation introduced by
Should the return type not be array[]?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
144
     */
145 11
    private function getSingleVersionTokens(
146
        array $chunkedList
147
    ) {
148 11
        $dashTokenList = [new Token(Token::DASH_SEPARATOR, '-')];
149 11
        $lowerTokenList = [];
150 11
        $upperTokenList = [];
151
152
        switch (true) {
153
            // No labels
154 11
            case 2 === count($chunkedList):
155 4
                $lowerTokenList = $chunkedList[0];
156 4
                $upperTokenList = $chunkedList[1];
157 4
                break;
158
159
            // Label on first version
160 7
            case 3 === count($chunkedList) && Token::LABEL_STRING === $chunkedList[1][0]->getType():
161 1
                $lowerTokenList = array_merge($chunkedList[0], $dashTokenList, $chunkedList[1]);
162 1
                $upperTokenList = $chunkedList[2];
163 1
                break;
164
165
            // Label on second version
166 6
            case 3 === count($chunkedList) && Token::LABEL_STRING === $chunkedList[2][0]->getType():
167 1
                $lowerTokenList = $chunkedList[0];
168 1
                $upperTokenList = array_merge($chunkedList[1], $dashTokenList, $chunkedList[2]);
169 1
                break;
170
171
            // Label on both versions
172 5
            case 4 === count($chunkedList):
173 2
                $lowerTokenList = array_merge($chunkedList[0], $dashTokenList, $chunkedList[1]);
174 2
                $upperTokenList = array_merge($chunkedList[2], $dashTokenList, $chunkedList[3]);
175 2
                break;
176
        }
177
178 11
        return [$lowerTokenList, $upperTokenList];
179
    }
180
181
    /**
182
     * Returns true if the chunks match the configuration.
183
     *
184
     * @param Token[][] $chunkedList
185
     * @param string[] $configuration
186
     *
187
     * @return boolean
188
     */
189 11
    private function chunksMatchConfiguration(
190
        array $chunkedList,
191
        array $configuration
192
    ) {
193 11
        $matches = count($chunkedList) === count($configuration);
194
195 11
        foreach ($configuration as $index => $token) {
196 11
            if ($matches) {
197 11
                $matches = $chunkedList[$index][0]->getType() === $token;
198
            }
199
        }
200
201 11
        return $matches;
202
    }
203
204
    /**
205
     * Get the upper version constraint from a token list.
206
     *
207
     * @param Token[] $tokenList
208
     *
209
     * @return ComparatorVersion
210
     */
211 6
    private function getUpperConstraint(array $tokenList)
212
    {
213 6
        $comparator = $this->lessThan;
214 6
        $version = $this->versionParser->parse($tokenList);
215
216
        switch (true) {
217 6
            case 1 === count($tokenList):
218 1
                $version = new Version($version->getMajor() + 1, 0, 0);
219 1
                break;
220
221 5
            case 3 === count($tokenList):
222 1
                $version = new Version($version->getMajor(), $version->getMinor() + 1, 0);
223 1
                break;
224
225 4
            case count($tokenList) >= 5:
226 4
                $comparator = $this->lessOrEqualTo;
227 4
                break;
228
        }
229
230 6
        return new ComparatorVersion($comparator, $version);
231
    }
232
}
233