Mercurial > hg > rc1
comparison vendor/composer/semver/src/VersionParser.php @ 0:1e000243b222
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:50:29 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1e000243b222 |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of composer/semver. | |
5 * | |
6 * (c) Composer <https://github.com/composer> | |
7 * | |
8 * For the full copyright and license information, please view | |
9 * the LICENSE file that was distributed with this source code. | |
10 */ | |
11 | |
12 namespace Composer\Semver; | |
13 | |
14 use Composer\Semver\Constraint\ConstraintInterface; | |
15 use Composer\Semver\Constraint\EmptyConstraint; | |
16 use Composer\Semver\Constraint\MultiConstraint; | |
17 use Composer\Semver\Constraint\Constraint; | |
18 | |
19 /** | |
20 * Version parser. | |
21 * | |
22 * @author Jordi Boggiano <j.boggiano@seld.be> | |
23 */ | |
24 class VersionParser | |
25 { | |
26 /** | |
27 * Regex to match pre-release data (sort of). | |
28 * | |
29 * Due to backwards compatibility: | |
30 * - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted. | |
31 * - Only stabilities as recognized by Composer are allowed to precede a numerical identifier. | |
32 * - Numerical-only pre-release identifiers are not supported, see tests. | |
33 * | |
34 * |--------------| | |
35 * [major].[minor].[patch] -[pre-release] +[build-metadata] | |
36 * | |
37 * @var string | |
38 */ | |
39 private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?'; | |
40 | |
41 /** @var array */ | |
42 private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev'); | |
43 | |
44 /** | |
45 * Returns the stability of a version. | |
46 * | |
47 * @param string $version | |
48 * | |
49 * @return string | |
50 */ | |
51 public static function parseStability($version) | |
52 { | |
53 $version = preg_replace('{#.+$}i', '', $version); | |
54 | |
55 if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) { | |
56 return 'dev'; | |
57 } | |
58 | |
59 preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match); | |
60 if (!empty($match[3])) { | |
61 return 'dev'; | |
62 } | |
63 | |
64 if (!empty($match[1])) { | |
65 if ('beta' === $match[1] || 'b' === $match[1]) { | |
66 return 'beta'; | |
67 } | |
68 if ('alpha' === $match[1] || 'a' === $match[1]) { | |
69 return 'alpha'; | |
70 } | |
71 if ('rc' === $match[1]) { | |
72 return 'RC'; | |
73 } | |
74 } | |
75 | |
76 return 'stable'; | |
77 } | |
78 | |
79 /** | |
80 * @param string $stability | |
81 * | |
82 * @return string | |
83 */ | |
84 public static function normalizeStability($stability) | |
85 { | |
86 $stability = strtolower($stability); | |
87 | |
88 return $stability === 'rc' ? 'RC' : $stability; | |
89 } | |
90 | |
91 /** | |
92 * Normalizes a version string to be able to perform comparisons on it. | |
93 * | |
94 * @param string $version | |
95 * @param string $fullVersion optional complete version string to give more context | |
96 * | |
97 * @throws \UnexpectedValueException | |
98 * | |
99 * @return string | |
100 */ | |
101 public function normalize($version, $fullVersion = null) | |
102 { | |
103 $version = trim($version); | |
104 if (null === $fullVersion) { | |
105 $fullVersion = $version; | |
106 } | |
107 | |
108 // strip off aliasing | |
109 if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) { | |
110 $version = $match[1]; | |
111 } | |
112 | |
113 // match master-like branches | |
114 if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) { | |
115 return '9999999-dev'; | |
116 } | |
117 | |
118 // if requirement is branch-like, use full name | |
119 if ('dev-' === strtolower(substr($version, 0, 4))) { | |
120 return 'dev-' . substr($version, 4); | |
121 } | |
122 | |
123 // strip off build metadata | |
124 if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) { | |
125 $version = $match[1]; | |
126 } | |
127 | |
128 // match classical versioning | |
129 if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) { | |
130 $version = $matches[1] | |
131 . (!empty($matches[2]) ? $matches[2] : '.0') | |
132 . (!empty($matches[3]) ? $matches[3] : '.0') | |
133 . (!empty($matches[4]) ? $matches[4] : '.0'); | |
134 $index = 5; | |
135 // match date(time) based versioning | |
136 } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { | |
137 $version = preg_replace('{\D}', '.', $matches[1]); | |
138 $index = 2; | |
139 } | |
140 | |
141 // add version modifiers if a version was matched | |
142 if (isset($index)) { | |
143 if (!empty($matches[$index])) { | |
144 if ('stable' === $matches[$index]) { | |
145 return $version; | |
146 } | |
147 $version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? ltrim($matches[$index + 1], '.-') : ''); | |
148 } | |
149 | |
150 if (!empty($matches[$index + 2])) { | |
151 $version .= '-dev'; | |
152 } | |
153 | |
154 return $version; | |
155 } | |
156 | |
157 // match dev branches | |
158 if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) { | |
159 try { | |
160 return $this->normalizeBranch($match[1]); | |
161 } catch (\Exception $e) { | |
162 } | |
163 } | |
164 | |
165 $extraMessage = ''; | |
166 if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) { | |
167 $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version'; | |
168 } elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) { | |
169 $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-'; | |
170 } | |
171 | |
172 throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage); | |
173 } | |
174 | |
175 /** | |
176 * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison. | |
177 * | |
178 * @param string $branch Branch name (e.g. 2.1.x-dev) | |
179 * | |
180 * @return string|false Numeric prefix if present (e.g. 2.1.) or false | |
181 */ | |
182 public function parseNumericAliasPrefix($branch) | |
183 { | |
184 if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', $branch, $matches)) { | |
185 return $matches['version'] . '.'; | |
186 } | |
187 | |
188 return false; | |
189 } | |
190 | |
191 /** | |
192 * Normalizes a branch name to be able to perform comparisons on it. | |
193 * | |
194 * @param string $name | |
195 * | |
196 * @return string | |
197 */ | |
198 public function normalizeBranch($name) | |
199 { | |
200 $name = trim($name); | |
201 | |
202 if (in_array($name, array('master', 'trunk', 'default'))) { | |
203 return $this->normalize($name); | |
204 } | |
205 | |
206 if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) { | |
207 $version = ''; | |
208 for ($i = 1; $i < 5; ++$i) { | |
209 $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x'; | |
210 } | |
211 | |
212 return str_replace('x', '9999999', $version) . '-dev'; | |
213 } | |
214 | |
215 return 'dev-' . $name; | |
216 } | |
217 | |
218 /** | |
219 * Parses a constraint string into MultiConstraint and/or Constraint objects. | |
220 * | |
221 * @param string $constraints | |
222 * | |
223 * @return ConstraintInterface | |
224 */ | |
225 public function parseConstraints($constraints) | |
226 { | |
227 $prettyConstraint = $constraints; | |
228 | |
229 if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) { | |
230 $constraints = empty($match[1]) ? '*' : $match[1]; | |
231 } | |
232 | |
233 if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) { | |
234 $constraints = $match[1]; | |
235 } | |
236 | |
237 $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints)); | |
238 $orGroups = array(); | |
239 foreach ($orConstraints as $constraints) { | |
240 $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints); | |
241 if (count($andConstraints) > 1) { | |
242 $constraintObjects = array(); | |
243 foreach ($andConstraints as $constraint) { | |
244 foreach ($this->parseConstraint($constraint) as $parsedConstraint) { | |
245 $constraintObjects[] = $parsedConstraint; | |
246 } | |
247 } | |
248 } else { | |
249 $constraintObjects = $this->parseConstraint($andConstraints[0]); | |
250 } | |
251 | |
252 if (1 === count($constraintObjects)) { | |
253 $constraint = $constraintObjects[0]; | |
254 } else { | |
255 $constraint = new MultiConstraint($constraintObjects); | |
256 } | |
257 | |
258 $orGroups[] = $constraint; | |
259 } | |
260 | |
261 if (1 === count($orGroups)) { | |
262 $constraint = $orGroups[0]; | |
263 } elseif (2 === count($orGroups) | |
264 // parse the two OR groups and if they are contiguous we collapse | |
265 // them into one constraint | |
266 && $orGroups[0] instanceof MultiConstraint | |
267 && $orGroups[1] instanceof MultiConstraint | |
268 && 2 === count($orGroups[0]->getConstraints()) | |
269 && 2 === count($orGroups[1]->getConstraints()) | |
270 && ($a = (string) $orGroups[0]) | |
271 && substr($a, 0, 3) === '[>=' && (false !== ($posA = strpos($a, '<', 4))) | |
272 && ($b = (string) $orGroups[1]) | |
273 && substr($b, 0, 3) === '[>=' && (false !== ($posB = strpos($b, '<', 4))) | |
274 && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5) | |
275 ) { | |
276 $constraint = new MultiConstraint(array( | |
277 new Constraint('>=', substr($a, 4, $posA - 5)), | |
278 new Constraint('<', substr($b, $posB + 2, -1)), | |
279 )); | |
280 } else { | |
281 $constraint = new MultiConstraint($orGroups, false); | |
282 } | |
283 | |
284 $constraint->setPrettyString($prettyConstraint); | |
285 | |
286 return $constraint; | |
287 } | |
288 | |
289 /** | |
290 * @param string $constraint | |
291 * | |
292 * @throws \UnexpectedValueException | |
293 * | |
294 * @return array | |
295 */ | |
296 private function parseConstraint($constraint) | |
297 { | |
298 if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) { | |
299 $constraint = $match[1]; | |
300 if ($match[2] !== 'stable') { | |
301 $stabilityModifier = $match[2]; | |
302 } | |
303 } | |
304 | |
305 if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) { | |
306 return array(new EmptyConstraint()); | |
307 } | |
308 | |
309 $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\s]+)?'; | |
310 | |
311 // Tilde Range | |
312 // | |
313 // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous | |
314 // version, to ensure that unstable instances of the current version are allowed. However, if a stability | |
315 // suffix is added to the constraint, then a >= match on the current version is used instead. | |
316 if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) { | |
317 if (substr($constraint, 0, 2) === '~>') { | |
318 throw new \UnexpectedValueException( | |
319 'Could not parse version constraint ' . $constraint . ': ' . | |
320 'Invalid operator "~>", you probably meant to use the "~" operator' | |
321 ); | |
322 } | |
323 | |
324 // Work out which position in the version we are operating at | |
325 if (isset($matches[4]) && '' !== $matches[4]) { | |
326 $position = 4; | |
327 } elseif (isset($matches[3]) && '' !== $matches[3]) { | |
328 $position = 3; | |
329 } elseif (isset($matches[2]) && '' !== $matches[2]) { | |
330 $position = 2; | |
331 } else { | |
332 $position = 1; | |
333 } | |
334 | |
335 // Calculate the stability suffix | |
336 $stabilitySuffix = ''; | |
337 if (!empty($matches[5])) { | |
338 $stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : ''); | |
339 } | |
340 | |
341 if (!empty($matches[7])) { | |
342 $stabilitySuffix .= '-dev'; | |
343 } | |
344 | |
345 if (!$stabilitySuffix) { | |
346 $stabilitySuffix = '-dev'; | |
347 } | |
348 | |
349 $lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix; | |
350 $lowerBound = new Constraint('>=', $lowVersion); | |
351 | |
352 // For upper bound, we increment the position of one more significance, | |
353 // but highPosition = 0 would be illegal | |
354 $highPosition = max(1, $position - 1); | |
355 $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; | |
356 $upperBound = new Constraint('<', $highVersion); | |
357 | |
358 return array( | |
359 $lowerBound, | |
360 $upperBound, | |
361 ); | |
362 } | |
363 | |
364 // Caret Range | |
365 // | |
366 // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. | |
367 // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for | |
368 // versions 0.X >=0.1.0, and no updates for versions 0.0.X | |
369 if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) { | |
370 // Work out which position in the version we are operating at | |
371 if ('0' !== $matches[1] || '' === $matches[2]) { | |
372 $position = 1; | |
373 } elseif ('0' !== $matches[2] || '' === $matches[3]) { | |
374 $position = 2; | |
375 } else { | |
376 $position = 3; | |
377 } | |
378 | |
379 // Calculate the stability suffix | |
380 $stabilitySuffix = ''; | |
381 if (empty($matches[5]) && empty($matches[7])) { | |
382 $stabilitySuffix .= '-dev'; | |
383 } | |
384 | |
385 $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); | |
386 $lowerBound = new Constraint('>=', $lowVersion); | |
387 | |
388 // For upper bound, we increment the position of one more significance, | |
389 // but highPosition = 0 would be illegal | |
390 $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; | |
391 $upperBound = new Constraint('<', $highVersion); | |
392 | |
393 return array( | |
394 $lowerBound, | |
395 $upperBound, | |
396 ); | |
397 } | |
398 | |
399 // X Range | |
400 // | |
401 // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple. | |
402 // A partial version range is treated as an X-Range, so the special character is in fact optional. | |
403 if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) { | |
404 if (isset($matches[3]) && '' !== $matches[3]) { | |
405 $position = 3; | |
406 } elseif (isset($matches[2]) && '' !== $matches[2]) { | |
407 $position = 2; | |
408 } else { | |
409 $position = 1; | |
410 } | |
411 | |
412 $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev'; | |
413 $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; | |
414 | |
415 if ($lowVersion === '0.0.0.0-dev') { | |
416 return array(new Constraint('<', $highVersion)); | |
417 } | |
418 | |
419 return array( | |
420 new Constraint('>=', $lowVersion), | |
421 new Constraint('<', $highVersion), | |
422 ); | |
423 } | |
424 | |
425 // Hyphen Range | |
426 // | |
427 // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range, | |
428 // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in | |
429 // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but | |
430 // nothing that would be greater than the provided tuple parts. | |
431 if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) { | |
432 // Calculate the stability suffix | |
433 $lowStabilitySuffix = ''; | |
434 if (empty($matches[6]) && empty($matches[8])) { | |
435 $lowStabilitySuffix = '-dev'; | |
436 } | |
437 | |
438 $lowVersion = $this->normalize($matches['from']); | |
439 $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix); | |
440 | |
441 $empty = function ($x) { | |
442 return ($x === 0 || $x === '0') ? false : empty($x); | |
443 }; | |
444 | |
445 if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) { | |
446 $highVersion = $this->normalize($matches['to']); | |
447 $upperBound = new Constraint('<=', $highVersion); | |
448 } else { | |
449 $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]); | |
450 $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev'; | |
451 $upperBound = new Constraint('<', $highVersion); | |
452 } | |
453 | |
454 return array( | |
455 $lowerBound, | |
456 $upperBound, | |
457 ); | |
458 } | |
459 | |
460 // Basic Comparators | |
461 if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) { | |
462 try { | |
463 $version = $this->normalize($matches[2]); | |
464 | |
465 if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') { | |
466 $version .= '-' . $stabilityModifier; | |
467 } elseif ('<' === $matches[1] || '>=' === $matches[1]) { | |
468 if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) { | |
469 if (substr($matches[2], 0, 4) !== 'dev-') { | |
470 $version .= '-dev'; | |
471 } | |
472 } | |
473 } | |
474 | |
475 return array(new Constraint($matches[1] ?: '=', $version)); | |
476 } catch (\Exception $e) { | |
477 } | |
478 } | |
479 | |
480 $message = 'Could not parse version constraint ' . $constraint; | |
481 if (isset($e)) { | |
482 $message .= ': ' . $e->getMessage(); | |
483 } | |
484 | |
485 throw new \UnexpectedValueException($message); | |
486 } | |
487 | |
488 /** | |
489 * Increment, decrement, or simply pad a version number. | |
490 * | |
491 * Support function for {@link parseConstraint()} | |
492 * | |
493 * @param array $matches Array with version parts in array indexes 1,2,3,4 | |
494 * @param int $position 1,2,3,4 - which segment of the version to increment/decrement | |
495 * @param int $increment | |
496 * @param string $pad The string to pad version parts after $position | |
497 * | |
498 * @return string The new version | |
499 */ | |
500 private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0') | |
501 { | |
502 for ($i = 4; $i > 0; --$i) { | |
503 if ($i > $position) { | |
504 $matches[$i] = $pad; | |
505 } elseif ($i === $position && $increment) { | |
506 $matches[$i] += $increment; | |
507 // If $matches[$i] was 0, carry the decrement | |
508 if ($matches[$i] < 0) { | |
509 $matches[$i] = $pad; | |
510 --$position; | |
511 | |
512 // Return null on a carry overflow | |
513 if ($i === 1) { | |
514 return; | |
515 } | |
516 } | |
517 } | |
518 } | |
519 | |
520 return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; | |
521 } | |
522 | |
523 /** | |
524 * Expand shorthand stability string to long version. | |
525 * | |
526 * @param string $stability | |
527 * | |
528 * @return string | |
529 */ | |
530 private function expandStability($stability) | |
531 { | |
532 $stability = strtolower($stability); | |
533 | |
534 switch ($stability) { | |
535 case 'a': | |
536 return 'alpha'; | |
537 case 'b': | |
538 return 'beta'; | |
539 case 'p': | |
540 case 'pl': | |
541 return 'patch'; | |
542 case 'rc': | |
543 return 'RC'; | |
544 default: | |
545 return $stability; | |
546 } | |
547 } | |
548 } |