359c18c4686e11004e4a74a0ec6ea54dd518981a
[yaffs-website] / vendor / composer / semver / src / VersionParser.php
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 }