1 /**
2  * Provide utilities to represent & manipulate versions and version ranges
3  * following the Semantic Versioning 2.0 specifications.
4  *
5  * Semantic versioning define a syntax and a semantic to represent version
6  * numbers, composed of five `VersionPart`.
7  * Each difference in `VersionPart` represent a different expectation
8  * with regards to the scope of the difference between two versions.
9  *
10  * `VersionPart.MAJOR` can differ in any way and are not considered to
11  * be compatible with one another.
12  * `VersionPart.MINOR` do not contain breaking changes but can contain new features,
13  * hence newer versions are backward compatible but not forward compatible.
14  * `VersionPart.PATCH` are both forward and backward compatible.
15  * `VersionPart.PRERELEASE` are preview versions of their non-prerelease
16  * counterpart, while `VersionPart.BUILD` are metadata without influence
17  * on the version matching.
18  *
19  * In addition to the `SemVer` type to represent versions, a `SemVerRange`
20  * type exists to express version constraints.
21  *
22  * For more information about SemVer, rules and practice,
23  * see [the official website](https://semver.org).
24  *
25  * License: [MIT](http://opensource.org/licenses/MIT)
26  * Authors: Dragos Carp
27  *
28  * See_Also:
29  * - [Semantic Versioning 2.0](http://semver.org)
30  * - [The semantic versioner for npm](https://github.com/isaacs/node-semver)
31  */
32 
33 module semver;
34 
35 import std.algorithm;
36 import std.range;
37 import std.regex : matchAll, matchFirst, ctRegex;
38 
39 /**
40  * The different components of a version number.
41  */
42 enum VersionPart
43 {
44     /** major number */
45     MAJOR,
46     /** minor number */
47     MINOR,
48     /** patch number */
49     PATCH,
50     /** prerelease suffix */
51     PRERELEASE,
52     /** build suffix */
53     BUILD,
54 }
55 
56 /**
57  * Represent a semantic version number
58  *
59  * Semantic versions are usually represented as string as:
60  * `MAJOR[.MINOR[.PATCH]][-PRERELEASE][+BUILD]`.
61  * For ease of use, a leading `v` or a leading `=` are also accepted.
62  * Invalid input to the constructor will not throw, but the version
63  * will be marked as invalid. This can be checked via `isValid`.
64  */
65 struct SemVer
66 {
67     private uint[3] ids;
68     private string[] prerelease;
69     private string[] build;
70 
71     private bool _isValid;
72 
73     private static immutable RegExp = ctRegex!(
74         `^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([a-zA-Z\d-.]+))?(?:\+([a-zA-Z\d-.]+))?$`);
75 
76     /**
77      * Creates and validates a version number from a string.
78      *
79      * If string format is invalid it just sets the $(D_PARAM isValid) property to $(D_KEYWORD false).
80      */
81     this(string semVer) @safe
82     {
83         import std.array : array;
84         import std.conv : to;
85 
86         if (semVer.length == 0)
87             return;
88         if (semVer[0] == 'v')
89             semVer = semVer[1 .. $];
90         else if (semVer[0] == '=')
91             semVer = semVer[1 .. $];
92 
93         auto m = semVer.matchAll(RegExp);
94         if (m.empty)
95             return;
96 
97         foreach (i, ref id; ids)
98         {
99             if (!m.captures[i+1].empty)
100                 id = m.captures[i+1].to!uint;
101         }
102 
103         if (!m.captures[4].empty)
104         {
105             prerelease = m.captures[4].splitter('.').array;
106             if (prerelease.any!empty)
107                 return;
108         }
109 
110         if (!m.captures[5].empty)
111         {
112             build = m.captures[5].splitter('.').array;
113             if (build.any!empty)
114                 return;
115         }
116 
117         _isValid = true;
118     }
119 
120     /**
121      * Creates a 'simple' version with only major, minor, patch components
122      */
123     public this (uint major, uint minor = 0, uint patch = 0)
124         @safe pure nothrow @nogc
125     {
126         this.ids[VersionPart.MAJOR] = major;
127         this.ids[VersionPart.MINOR] = minor;
128         this.ids[VersionPart.PATCH] = patch;
129         this._isValid = true;
130     }
131 
132     /**
133      * Return the canonical string format.
134      */
135     string toString() const scope @safe
136     {
137         import std.string : format;
138 
139         if (!_isValid)
140             return "<invalid_semver>";
141 
142         string semVer = "%(%s.%)".format(ids);
143         if (!prerelease.empty)
144             semVer ~= "-" ~ "%-(%s.%)".format(prerelease);
145         if (!build.empty)
146             semVer ~= "+" ~ "%-(%s.%)".format(build);
147         return semVer;
148     }
149 
150     /**
151      * Property that indicates whether this $(D_PSYMBOL SemVer) is valid.
152      */
153     bool isValid() const scope @safe pure nothrow @nogc
154     {
155         return _isValid;
156     }
157 
158     /**
159      * Property that indicates whether this $(D_PSYMBOL SemVer) is stable.
160      */
161     bool isStable() const scope @safe pure nothrow @nogc
162     {
163         return prerelease.empty;
164     }
165 
166     /**
167      * Increment version number.
168      */
169     SemVer increment(VersionPart versionPart) const scope @safe pure nothrow @nogc
170     in
171     {
172         assert(this.isValid);
173     }
174     out(result)
175     {
176         assert(result.isValid);
177     }
178     do
179     {
180         SemVer result = SemVer(0);
181         foreach (i; VersionPart.MAJOR .. versionPart)
182             result.ids[i] = this.ids[i];
183         if (versionPart != VersionPart.PRERELEASE)
184             result.ids[versionPart] = this.ids[versionPart]+1;
185         return result;
186     }
187 
188     private SemVer appendPrerelease0() scope @safe pure nothrow
189     {
190         if (prerelease.empty)
191             prerelease ~= "0";
192         return this;
193     }
194 
195     unittest
196     {
197         assert(SemVer("1.2.3").increment(VersionPart.MAJOR) == SemVer("2.0.0"));
198         assert(SemVer("1.2.3").increment(VersionPart.MINOR) == SemVer("1.3.0"));
199         assert(SemVer("1.2.3-alpha").increment(VersionPart.MINOR) == SemVer("1.3.0"));
200         assert(SemVer("1.2.3").increment(VersionPart.PATCH) == SemVer("1.2.4"));
201         assert(SemVer("1.2.3-alpha").increment(VersionPart.PATCH) == SemVer("1.2.4"));
202         assert(SemVer("1.2.3").increment(VersionPart.PRERELEASE) == SemVer("1.2.3"));
203         assert(SemVer("1.2.3-alpha").increment(VersionPart.PRERELEASE) == SemVer("1.2.3"));
204     }
205 
206     /**
207      * Query a Major, Minor and Patch as integers
208      */
209     int query(VersionPart versionPart) const
210     in
211     {
212         assert(this.isValid);
213     }
214     do
215     {
216         import std.conv : to;
217         import std.exception : enforce;
218         int result;
219         switch (versionPart)
220         {
221         case VersionPart.MAJOR:
222             result = ids[0];
223             break;
224         case VersionPart.MINOR:
225             result = ids[1];
226             break;
227         case VersionPart.PATCH:
228             result = ids[2];
229             break;
230         default:
231             enforce(false, "Can't query " ~ versionPart.to!string ~ " as an integer.");
232             break;
233         }
234         return result;
235     }
236 
237     unittest
238     {
239         import std.exception : assertThrown;
240         assert(SemVer("1.2.3").query(VersionPart.MAJOR) == 1);
241         assert(SemVer("1.2.3").query(VersionPart.MINOR) == 2);
242         assert(SemVer("1.2.3").query(VersionPart.PATCH) == 3);
243         assertThrown(SemVer("1.2.3-alpha").query(VersionPart.BUILD));
244         assertThrown(SemVer("1.2.3-alpha+build").query(VersionPart.PRERELEASE));
245     }
246 
247     /**
248      * Query a possibly decoded PRERELEASE and BUILD string
249      */
250     string queryAsString(VersionPart versionPart) const
251     in
252     {
253         assert(this.isValid);
254     }
255     do
256     {
257         import std.conv : to;
258         import std.exception : enforce;
259         string result;
260         switch (versionPart)
261         {
262         case VersionPart.MAJOR:
263             result = ids[0].to!string;
264             break;
265         case VersionPart.MINOR:
266             result = ids[1].to!string;
267             break;
268         case VersionPart.PATCH:
269             result = ids[2].to!string;
270             break;
271         case VersionPart.PRERELEASE:
272             result = prerelease.join(".");
273             break;
274         case VersionPart.BUILD:
275             result = build.join(".");
276             break;
277         default:
278             enforce(false, "Can't query unknown " ~ versionPart.to!string ~ " as a string.");
279             break;
280         }
281         return result;
282     }
283 
284     unittest
285     {
286         import std.exception : assertThrown;
287         assert(SemVer("1.2.3").queryAsString(VersionPart.MAJOR) == "1");
288         assert(SemVer("1.2.3").queryAsString(VersionPart.MINOR) == "2");
289         assert(SemVer("1.2.3").queryAsString(VersionPart.PATCH) == "3");
290         assert(SemVer("1.2.3-alpha-beta.2+build-seq.3").queryAsString(VersionPart.PRERELEASE) == "alpha-beta.2");
291         assert(SemVer("1.2.3-alpha-beta.2+build-seq.3").queryAsString(VersionPart.BUILD) == "build-seq.3");
292     }
293 
294     /**
295      * Compare this $(D_PSYMBOL SemVer) with the $(D_PARAM other) $(D_PSYMBOL SemVer).
296      *
297      * Note that the build parts are considered for this operation.
298      * Please use $(D_PSYMBOL differAt) to find whether the versions differ only on the build part.
299      */
300     int opCmp(ref const SemVer other) const scope @safe pure
301     in
302     {
303         assert(this.isValid);
304         assert(other.isValid);
305     }
306     do
307     {
308         foreach (i; 0..ids.length)
309         {
310             if (ids[i] != other.ids[i])
311                 return ids[i] < other.ids[i] ? -1 : 1;
312         }
313 
314         int compareSufix(const string[] suffix, const string[] anotherSuffix) @safe pure
315         {
316             import std.conv : to;
317             import std.string : isNumeric;
318 
319             if (!suffix.empty && anotherSuffix.empty)
320                 return -1;
321             if (suffix.empty && !anotherSuffix.empty)
322                 return 1;
323 
324             foreach (a, b; zip(suffix, anotherSuffix))
325             {
326                 if (a.isNumeric && b.isNumeric)
327                 {
328                     if (a.to!uint != b.to!uint)
329                         return a.to!uint < b.to!uint ? -1 : 1;
330                     else
331                         continue;
332                 }
333                 if (a != b)
334                     return a < b ? -1 : 1;
335             }
336             if (suffix.length != anotherSuffix.length)
337                 return suffix.length < anotherSuffix.length ? -1 : 1;
338             else
339                 return 0;
340         }
341 
342         auto result = compareSufix(prerelease, other.prerelease);
343         if (result != 0)
344             return result;
345         else
346             return compareSufix(build, other.build);
347     }
348 
349     /// ditto
350     int opCmp(const SemVer other) const scope @safe pure
351     {
352         return this.opCmp(other);
353     }
354 
355     /**
356      * Check for equality between this $(D_PSYMBOL SemVer) and the $(D_PARAM other) $(D_PSYMBOL SemVer).
357      *
358      * Note that the build parts are considered for this operation.
359      * Please use $(D_PSYMBOL differAt) to find whether the versions differ only on the build part.
360      */
361     bool opEquals(ref const SemVer other) const scope @safe pure
362     {
363         return this.opCmp(other) == 0;
364     }
365 
366     /// ditto
367     bool opEquals(const SemVer other) const scope @safe pure
368     {
369         return this.opEquals(other);
370     }
371 
372     /**
373      * Compare two $(B different) versions and return the parte they differ on.
374      */
375     VersionPart differAt(ref const SemVer other) const scope @safe pure nothrow
376     in
377     {
378         assert(this != other);
379     }
380     do
381     {
382         foreach (i; VersionPart.MAJOR .. VersionPart.PRERELEASE)
383         {
384             if (ids[i] != other.ids[i])
385                 return i;
386         }
387 
388         if (prerelease != other.prerelease)
389             return VersionPart.PRERELEASE;
390 
391         if (build != other.build)
392             return VersionPart.BUILD;
393 
394         assert(0, "Call 'differAt' for unequal versions only");
395     }
396 
397     /// ditto
398     VersionPart differAt(const SemVer other) const scope @safe pure nothrow
399     {
400         return this.differAt(other);
401     }
402 }
403 
404 unittest
405 {
406     assert(!SemVer().isValid);
407     assert(!SemVer("1.2-.alpha.32").isValid);
408     assert(!SemVer("1.2-alpha+").isValid);
409     assert(!SemVer("1.2-alpha_").isValid);
410     assert(!SemVer("1.2+32.").isValid);
411     assert(!SemVer("1.2.5.6").isValid);
412     assert(!SemVer("").isValid);
413     assert(SemVer("1").isStable);
414     assert(SemVer("1.0").isStable);
415     assert(SemVer("1.0.0").isStable);
416     assert(SemVer("1.0+build3.").isStable);
417     assert(SemVer("1.0.0+build.5").isStable);
418     assert(!SemVer("1.0.0-alpha").isStable);
419     assert(!SemVer("1.0.0-alpha.1").isStable);
420 
421     assert(SemVer("1.0.0-alpha") < SemVer("1.0.0-alpha.1"));
422     assert(SemVer("1.0.0-alpha.1") < SemVer("1.0.0-alpha.beta"));
423     assert(SemVer("1.0.0-alpha.beta") < SemVer("1.0.0-beta"));
424     assert(SemVer("1.0.0-beta") < SemVer("1.0.0-beta.2"));
425     assert(SemVer("1.0.0-beta.2") < SemVer("1.0.0-beta.11"));
426     assert(SemVer("1.0.0-beta.11") < SemVer("1.0.0-rc.1"));
427     assert(SemVer("1.0.0-rc.1") < SemVer("1.0.0"));
428     assert(SemVer("1.0.0-rc.1") > SemVer("1.0.0-rc.1+build.5"));
429     assert(SemVer("1.0.0-rc.1+build.5") == SemVer("1.0.0-rc.1+build.5"));
430 
431     assert(SemVer("1.0.0").differAt(SemVer("2")) == VersionPart.MAJOR);
432     assert(SemVer("1.0.0").differAt(SemVer("1.1.1")) == VersionPart.MINOR);
433     assert(SemVer("1.0.0-rc.1").differAt(SemVer("1.0.1-rc.1")) == VersionPart.PATCH);
434     assert(SemVer("1.0.0-alpha").differAt(SemVer("1.0.0-beta")) == VersionPart.PRERELEASE);
435     assert(SemVer("1.0.0-rc.1").differAt(SemVer("1.0.0")) == VersionPart.PRERELEASE);
436     assert(SemVer("1.0.0-rc.1").differAt(SemVer("1.0.0-rc.1+build.5")) == VersionPart.BUILD);
437 }
438 
439 /**
440  * Represent a semantic version range [~|~>|^|<|<=|=|>=|>]MAJOR[.MINOR[.PATCH]].
441  */
442 struct SemVerRange
443 {
444     private static immutable RegExp = ctRegex!(
445         `(~|~>|\^|<|<=|=|>=|>)?[v]?(\d+|\*|X|x)(?:\.(\d+|\*|X|x))?(?:\.(\d+|\*|X|x))?([\S]*)`);
446 
447     private static immutable Wildcards = [ "", "*", "X", "x" ];
448 
449     private struct SimpleRange
450     {
451         string op;
452         SemVer semVer;
453 
454         string toString() const
455         {
456             return op ~ semVer.toString;
457         }
458     }
459 
460     private SimpleRange[][] ranges;
461 
462     invariant()
463     {
464         assert(ranges.all!(r => r.all!(r => ["<", "<=", "=", ">=", ">"].canFind(r.op))));
465     }
466 
467     private bool _isValid;
468 
469     /**
470      * Creates and validates a semantic version range from a string.
471      *
472      * If string format is invalid it just sets the $(D_PARAM isValid) property to $(D_KEYWORD false).
473      */
474     this(string semVerRange)
475     {
476         import std.exception : enforce;
477         import std.string : format, strip, stripLeft;
478 
479         ranges = [SimpleRange[].init];
480 
481         while (!semVerRange.stripLeft.empty)
482         {
483             auto m = semVerRange.matchFirst(RegExp);
484             if (m.empty)
485                 return;
486 
487             auto operator = m.captures[1];
488             auto wildcard = wildcardAt([m.captures[2], m.captures[3], m.captures[4]]);
489             auto expanded = expand([m.captures[2], m.captures[3], m.captures[4], m.captures[5]]);
490             if (expanded.empty)
491                 return;
492 
493             auto semVer = SemVer(expanded);
494             if (!semVer.isValid)
495                 return;
496 
497             switch (m.captures.pre.strip)
498             {
499                 case "":
500                     break;
501                 case "-":
502                     if (ranges[$-1].empty || ranges[$-1][$-1].op != "=" ||
503                         operator != "" || wildcard != VersionPart.PRERELEASE)
504                         return;
505                     ranges[$-1][$-1].op = ">=";
506                     operator = "<=";
507                     break;
508                 case "||":
509                     ranges ~= SimpleRange[].init;
510                     break;
511                 default:
512                     return;
513             }
514 
515             switch (operator)
516             {
517                 case "":
518                 case "=":
519                     final switch (wildcard)
520                     {
521                         case VersionPart.MAJOR:
522                             assert(semVer == SemVer("0.0.0"));
523                             ranges[$-1] ~= SimpleRange(">=", semVer.appendPrerelease0);
524                             break;
525                         case VersionPart.MINOR:
526                         case VersionPart.PATCH:
527                             ranges[$-1] ~= SimpleRange(">=", semVer.appendPrerelease0);
528                             ranges[$-1] ~= SimpleRange("<", semVer.increment(--wildcard).appendPrerelease0);
529                             break;
530                         case VersionPart.PRERELEASE:
531                             ranges[$-1] ~= SimpleRange("=", semVer);
532                             break;
533                         case VersionPart.BUILD:
534                             assert(0, "Unexpected build part wildcard");
535                     }
536                     break;
537                 case "<":
538                     ranges[$-1] ~= SimpleRange(operator, semVer.appendPrerelease0);
539                     break;
540                 case "<=":
541                 case ">=":
542                 case ">":
543                     if (wildcard < VersionPart.PRERELEASE)
544                         semVer.appendPrerelease0;
545                     ranges[$-1] ~= SimpleRange(operator, semVer);
546                     break;
547                 case "~":
548                     final switch (wildcard)
549                     {
550                         case VersionPart.MAJOR:
551                             return;
552                         case VersionPart.MINOR:
553                         case VersionPart.PATCH:
554                             --wildcard;
555                             break;
556                         case VersionPart.PRERELEASE:
557                             --wildcard;
558                             --wildcard;
559                             break;
560                         case VersionPart.BUILD:
561                             assert(0, "Unexpected build part wildcard");
562                     }
563                     ranges[$-1] ~= SimpleRange(">=", semVer.appendPrerelease0);
564                     ranges[$-1] ~= SimpleRange("<", semVer.increment(wildcard).appendPrerelease0);
565                     break;
566                 case "~>":
567                     final switch (wildcard)
568                     {
569                         case VersionPart.MAJOR:
570                             return;
571                         case VersionPart.MINOR:
572                             --wildcard;
573                             break;
574                         case VersionPart.PATCH:
575                         case VersionPart.PRERELEASE:
576                             --wildcard;
577                             --wildcard;
578                             break;
579                         case VersionPart.BUILD:
580                             assert(0, "Unexpected build part wildcard");
581                     }
582                     ranges[$-1] ~= SimpleRange(">=", semVer.appendPrerelease0);
583                     ranges[$-1] ~= SimpleRange("<", semVer.increment(wildcard).appendPrerelease0);
584                     break;
585                 case "^":
586                     if (wildcard == VersionPart.MAJOR || !semVer.prerelease.empty)
587                         return;
588                     if (semVer.ids[VersionPart.MAJOR] != 0)
589                     {
590                         ranges[$-1] ~= SimpleRange(">=", semVer.appendPrerelease0);
591                         ranges[$-1] ~= SimpleRange("<", semVer.increment(VersionPart.MAJOR).appendPrerelease0);
592                     }
593                     else if (semVer.ids[VersionPart.MINOR] != 0)
594                     {
595                         ranges[$-1] ~= SimpleRange(">=", semVer.appendPrerelease0);
596                         ranges[$-1] ~= SimpleRange("<", semVer.increment(VersionPart.MINOR).appendPrerelease0);
597                     }
598                     else
599                     {
600                         ranges[$-1] ~= SimpleRange(">=", semVer.appendPrerelease0);
601                         ranges[$-1] ~= SimpleRange("<", semVer.increment(VersionPart.PATCH).appendPrerelease0);
602                     }
603                     break;
604                 default:
605                     enforce(false, "Unexpected operator %s".format(operator));
606                     break;
607             }
608             semVerRange = m.captures.post;
609         }
610         _isValid = true;
611     }
612 
613     private static VersionPart wildcardAt(string[3] semVer)
614     {
615         foreach (i; VersionPart.MAJOR..VersionPart.PRERELEASE)
616         {
617             if (Wildcards.canFind(semVer[i]))
618                 return i;
619         }
620         return VersionPart.PRERELEASE;
621     }
622 
623     unittest
624     {
625         assert(wildcardAt(["*", "", ""]) == VersionPart.MAJOR);
626         assert(wildcardAt(["X", "", ""]) == VersionPart.MAJOR);
627         assert(wildcardAt(["1", "", ""]) == VersionPart.MINOR);
628         assert(wildcardAt(["1", "x", ""]) == VersionPart.MINOR);
629         assert(wildcardAt(["1", "2", ""]) == VersionPart.PATCH);
630         assert(wildcardAt(["1", "2", "x"]) == VersionPart.PATCH);
631         assert(wildcardAt(["1", "2", "3"]) == VersionPart.PRERELEASE);
632     }
633 
634     private static string expand(string[4] semVer)
635     {
636         import std.string : format;
637 
638         VersionPart wildcard = wildcardAt(semVer[0..3]);
639         if (wildcard != VersionPart.PRERELEASE)
640         {
641             if (semVer[wildcard+1..$].any!((a) => !Wildcards.canFind(a)))
642                 return "";
643             foreach (j; wildcard..VersionPart.PRERELEASE)
644                 semVer[j] = "0";
645         }
646         string result = "%-(%s.%)".format(semVer[0..3]);
647         if (!semVer[3].empty)
648             result ~= semVer[3];
649         return result;
650     }
651 
652     unittest
653     {
654         assert(expand(["*", "", "", ""]) == "0.0.0");
655         assert(expand(["X", "", "", ""]) == "0.0.0");
656         assert(expand(["1", "2", "3", ""]) == "1.2.3");
657         assert(expand(["1", "2", "3", "-abc"]) == "1.2.3-abc");
658         assert(expand(["1", "2", "", ""]) == "1.2.0");
659         assert(expand(["1", "2", "", "-abc"]) == "");
660         assert(expand(["1", "2", "x", ""]) == "1.2.0");
661         assert(expand(["1", "", "", ""]) == "1.0.0");
662         assert(expand(["1", "x", "", ""]) == "1.0.0");
663     }
664 
665     /**
666      * Return expanded string representation.
667      */
668     string toString() const
669     {
670         import std.string : format;
671 
672         if (!_isValid)
673             return "<invalid_semver_range>";
674 
675         return "%(%(%s %) || %)".format(ranges);
676     }
677 
678     /**
679      * Property that indicates whether this $(D_PSYMBOL SemVerRange) is valid.
680      */
681     bool isValid() const scope @safe pure nothrow @nogc
682     {
683         return _isValid;
684     }
685 
686     private static bool simpleRangeSatisfiedBy(SimpleRange simpleRange, SemVer semVer)
687     in
688     {
689         assert(semVer.isValid);
690         assert(["<", "<=", "=", ">=", ">"].canFind(simpleRange.op));
691         assert(simpleRange.semVer.isValid);
692     }
693     do
694     {
695         semVer.build = null;
696 
697         switch (simpleRange.op)
698         {
699             case "<":
700                 return semVer < simpleRange.semVer;
701             case "<=":
702                 return semVer <= simpleRange.semVer;
703             case "=":
704                 return semVer == simpleRange.semVer;
705             case ">=":
706                 return semVer >= simpleRange.semVer;
707             case ">":
708                 return semVer > simpleRange.semVer;
709             default:
710                 return false;
711         }
712     }
713 
714     /**
715      * Check if the $(D_PSYMBOL SemVer) $(D_PARAM semVer) satisfies this $(D_PSYMBOL SemVerRange).
716      */
717     bool satisfiedBy(SemVer semVer)
718     in
719     {
720         assert(semVer.isValid);
721         assert(isValid);
722     }
723     do
724     {
725         return ranges.any!(r => r.all!(s => simpleRangeSatisfiedBy(s, semVer)));
726     }
727 
728 }
729 
730 /**
731  * Check if the $(D_PSYMBOL SemVer) $(D_PARAM semVer) satisfies $(LREF SemVerRange) $(D_PARAM semVerRange).
732  */
733 bool satisfies(SemVer semVer, SemVerRange semVerRange)
734 {
735     return semVerRange.satisfiedBy(semVer);
736 }
737 
738 /**
739  * Return the latest $(D_PSYMBOL Semver) from $(D_PARAM semVers) array that satisfies
740  * $(D_PARAM semVerRange) $(D_PSYMBOL SemVerRange).
741  */
742 SemVer maxSatisfying(SemVer[] semVers, SemVerRange semVerRange)
743 in
744 {
745     assert(semVers.all!"a.isValid");
746     assert(semVerRange.isValid);
747 }
748 do
749 {
750     auto found = semVers.sort!"a > b".find!(a => satisfies(a, semVerRange));
751     return found.empty ? SemVer("invalid") : found[0];
752 }
753 
754 unittest
755 {
756     assert(!SemVerRange().isValid);
757     assert(SemVerRange("1.x || >=2.5.0 || 5.0.0 - 7.2.3").isValid);
758     assert(!SemVerRange("blerg").isValid);
759     assert(!SemVerRange("git+https://user:password0123@github.com/foo").isValid);
760 
761     assert(SemVer("1.2.3").satisfies(SemVerRange("1.x || >=2.5.0 || 5.0.0 - 7.2.3")));
762 
763     assert(SemVer("1.2.3").satisfies(SemVerRange("1.0.0 - 2.0.0")));
764     assert(SemVer("1.0.0").satisfies(SemVerRange("1.0.0")));
765     assert(SemVer("1.0.0+build.5").satisfies(SemVerRange("1.0.0")));
766     assert(SemVer("0.2.4").satisfies(SemVerRange(">=*")));
767     assert(SemVer("1.2.3").satisfies(SemVerRange("*")));
768     assert(SemVer("v1.2.3-foo").satisfies(SemVerRange("*")));
769     assert(SemVer("1.0.0").satisfies(SemVerRange(">=1.0.0")));
770     assert(SemVer("1.0.1").satisfies(SemVerRange(">=1.0.0")));
771     assert(SemVer("1.1.0").satisfies(SemVerRange(">=1.0.0")));
772     assert(SemVer("1.0.1").satisfies(SemVerRange(">1.0.0")));
773     assert(SemVer("1.1.0").satisfies(SemVerRange(">1.0.0")));
774     assert(SemVer("2.0.0").satisfies(SemVerRange("<=2.0.0")));
775     assert(SemVer("1.9999.9999").satisfies(SemVerRange("<=2.0.0")));
776     assert(SemVer("0.2.9").satisfies(SemVerRange("<=2.0.0")));
777     assert(SemVer("1.9999.9999").satisfies(SemVerRange("<2.0.0")));
778     assert(SemVer("0.2.9").satisfies(SemVerRange("<2.0.0")));
779     assert(SemVer("1.0.0").satisfies(SemVerRange(">=1.0.0")));
780     assert(SemVer("1.0.1").satisfies(SemVerRange(">=1.0.0")));
781     assert(SemVer("1.1.0").satisfies(SemVerRange(">=1.0.0")));
782     assert(SemVer("1.0.1").satisfies(SemVerRange(">1.0.0")));
783     assert(SemVer("1.1.0").satisfies(SemVerRange(">1.0.0")));
784     assert(SemVer("2.0.0").satisfies(SemVerRange("<=2.0.0")));
785     assert(SemVer("1.9999.9999").satisfies(SemVerRange("<=2.0.0")));
786     assert(SemVer("0.2.9").satisfies(SemVerRange("<=2.0.0")));
787     assert(SemVer("1.9999.9999").satisfies(SemVerRange("<2.0.0")));
788     assert(SemVer("0.2.9").satisfies(SemVerRange("<2.0.0")));
789     assert(SemVer("v0.1.97").satisfies(SemVerRange(">=0.1.97")));
790     assert(SemVer("0.1.97").satisfies(SemVerRange(">=0.1.97")));
791     assert(SemVer("1.2.4").satisfies(SemVerRange("0.1.20 || 1.2.4")));
792     assert(SemVer("0.0.0").satisfies(SemVerRange(">=0.2.3 || <0.0.1")));
793     assert(SemVer("0.2.3").satisfies(SemVerRange(">=0.2.3 || <0.0.1")));
794     assert(SemVer("0.2.4").satisfies(SemVerRange(">=0.2.3 || <0.0.1")));
795     assert(SemVer("2.1.3").satisfies(SemVerRange("2.x.x")));
796     assert(SemVer("1.2.3").satisfies(SemVerRange("1.2.x")));
797     assert(SemVer("2.1.3").satisfies(SemVerRange("1.2.x || 2.x")));
798     assert(SemVer("1.2.3").satisfies(SemVerRange("1.2.x || 2.x")));
799     assert(SemVer("1.2.3").satisfies(SemVerRange("x")));
800     assert(SemVer("2.1.3").satisfies(SemVerRange("2.*.*")));
801     assert(SemVer("1.2.3").satisfies(SemVerRange("1.2.*")));
802     assert(SemVer("2.1.3").satisfies(SemVerRange("1.2.* || 2.*")));
803     assert(SemVer("1.2.3").satisfies(SemVerRange("1.2.* || 2.*")));
804     assert(SemVer("1.2.3").satisfies(SemVerRange("*")));
805     assert(SemVer("2.1.2").satisfies(SemVerRange("2")));
806     assert(SemVer("2.3.1").satisfies(SemVerRange("2.3")));
807     assert(SemVer("2.4.0").satisfies(SemVerRange("~2.4")));
808     assert(SemVer("2.4.5").satisfies(SemVerRange("~2.4")));
809     assert(SemVer("3.2.2").satisfies(SemVerRange("~>3.2.1")));
810     assert(SemVer("1.2.3").satisfies(SemVerRange("~1")));
811     assert(SemVer("1.2.3").satisfies(SemVerRange("~>1")));
812     assert(SemVer("1.0.2").satisfies(SemVerRange("~1.0")));
813     assert(SemVer("1.0.12").satisfies(SemVerRange("~1.0.3")));
814     assert(SemVer("1.0.0").satisfies(SemVerRange(">=1")));
815     assert(SemVer("1.1.1").satisfies(SemVerRange("<1.2")));
816     assert(SemVer("1.1.9").satisfies(SemVerRange("<=1.2")));
817     assert(SemVer("1.0.0-bet").satisfies(SemVerRange("1")));
818     assert(SemVer("0.5.5").satisfies(SemVerRange("~v0.5.4-pre")));
819     assert(SemVer("0.5.4").satisfies(SemVerRange("~v0.5.4-pre")));
820     assert(SemVer("0.7.2").satisfies(SemVerRange("=0.7.x")));
821     assert(SemVer("0.7.2").satisfies(SemVerRange(">=0.7.x")));
822     assert(SemVer("0.7.0-asdf").satisfies(SemVerRange("=0.7.x")));
823     assert(SemVer("0.7.0-asdf").satisfies(SemVerRange(">=0.7.x")));
824     assert(SemVer("0.6.2").satisfies(SemVerRange("<=0.7.x")));
825     assert(SemVer("1.2.3").satisfies(SemVerRange("~1.2.1 >=1.2.3")));
826     assert(SemVer("1.2.3").satisfies(SemVerRange("~1.2.1 =1.2.3")));
827     assert(SemVer("1.2.3").satisfies(SemVerRange("~1.2.1 1.2.3")));
828     assert(SemVer("1.2.3").satisfies(SemVerRange("~1.2.1 >=1.2.3 1.2.3")));
829     assert(SemVer("1.2.3").satisfies(SemVerRange("~1.2.1 1.2.3 >=1.2.3")));
830     assert(SemVer("1.2.3").satisfies(SemVerRange("~1.2.1 1.2.3")));
831     assert(SemVer("1.2.3").satisfies(SemVerRange(">=1.2.1 1.2.3")));
832     assert(SemVer("1.2.3").satisfies(SemVerRange("1.2.3 >=1.2.1")));
833     assert(SemVer("1.2.3").satisfies(SemVerRange(">=1.2.3 >=1.2.1")));
834     assert(SemVer("1.2.3").satisfies(SemVerRange(">=1.2.1 >=1.2.3")));
835     assert(SemVer("1.2.3-beta").satisfies(SemVerRange("<=1.2.3")));
836     assert(SemVer("1.3.0-beta").satisfies(SemVerRange(">1.2")));
837     assert(SemVer("1.2.8").satisfies(SemVerRange(">=1.2")));
838     assert(SemVer("1.8.1").satisfies(SemVerRange("^1.2.3")));
839     assert(SemVer("1.2.3-beta").satisfies(SemVerRange("^1.2.3")));
840     assert(SemVer("0.1.2").satisfies(SemVerRange("^0.1.2")));
841     assert(SemVer("0.1.2").satisfies(SemVerRange("^0.1")));
842     assert(SemVer("1.4.2").satisfies(SemVerRange("^1.2")));
843     assert(SemVer("1.4.2").satisfies(SemVerRange("^1.2 ^1")));
844     assert(SemVer("1.2.0-pre").satisfies(SemVerRange("^1.2")));
845     assert(SemVer("1.2.3-pre").satisfies(SemVerRange("^1.2.3")));
846 
847     assert(!SemVer("2.2.3").satisfies(SemVerRange("1.0.0 - 2.0.0")));
848     assert(!SemVer("1.0.1").satisfies(SemVerRange("1.0.0")));
849     assert(!SemVer("0.0.0").satisfies(SemVerRange(">=1.0.0")));
850     assert(!SemVer("0.0.1").satisfies(SemVerRange(">=1.0.0")));
851     assert(!SemVer("0.1.0").satisfies(SemVerRange(">=1.0.0")));
852     assert(!SemVer("0.0.1").satisfies(SemVerRange(">1.0.0")));
853     assert(!SemVer("0.1.0").satisfies(SemVerRange(">1.0.0")));
854     assert(!SemVer("3.0.0").satisfies(SemVerRange("<=2.0.0")));
855     assert(!SemVer("2.9999.9999").satisfies(SemVerRange("<=2.0.0")));
856     assert(!SemVer("2.2.9").satisfies(SemVerRange("<=2.0.0")));
857     assert(!SemVer("2.9999.9999").satisfies(SemVerRange("<2.0.0")));
858     assert(!SemVer("2.2.9").satisfies(SemVerRange("<2.0.0")));
859     assert(!SemVer("v0.1.93").satisfies(SemVerRange(">=0.1.97")));
860     assert(!SemVer("0.1.93").satisfies(SemVerRange(">=0.1.97")));
861     assert(!SemVer("1.2.3").satisfies(SemVerRange("0.1.20 || 1.2.4")));
862     assert(!SemVer("0.0.3").satisfies(SemVerRange(">=0.2.3 || <0.0.1")));
863     assert(!SemVer("0.2.2").satisfies(SemVerRange(">=0.2.3 || <0.0.1")));
864     assert(!SemVer("1.1.3").satisfies(SemVerRange("2.x.x")));
865     assert(!SemVer("3.1.3").satisfies(SemVerRange("2.x.x")));
866     assert(!SemVer("1.3.3").satisfies(SemVerRange("1.2.x")));
867     assert(!SemVer("3.1.3").satisfies(SemVerRange("1.2.x || 2.x")));
868     assert(!SemVer("1.1.3").satisfies(SemVerRange("1.2.x || 2.x")));
869     assert(!SemVer("1.1.3").satisfies(SemVerRange("2.*.*")));
870     assert(!SemVer("3.1.3").satisfies(SemVerRange("2.*.*")));
871     assert(!SemVer("1.3.3").satisfies(SemVerRange("1.2.*")));
872     assert(!SemVer("3.1.3").satisfies(SemVerRange("1.2.* || 2.*")));
873     assert(!SemVer("1.1.3").satisfies(SemVerRange("1.2.* || 2.*")));
874     assert(!SemVer("1.1.2").satisfies(SemVerRange("2")));
875     assert(!SemVer("2.4.1").satisfies(SemVerRange("2.3")));
876     assert(!SemVer("2.5.0").satisfies(SemVerRange("~2.4")));
877     assert(!SemVer("2.3.9").satisfies(SemVerRange("~2.4")));
878     assert(!SemVer("3.3.2").satisfies(SemVerRange("~>3.2.1")));
879     assert(!SemVer("3.2.0").satisfies(SemVerRange("~>3.2.1")));
880     assert(!SemVer("0.2.3").satisfies(SemVerRange("~1")));
881     assert(!SemVer("2.2.3").satisfies(SemVerRange("~>1")));
882     assert(!SemVer("1.1.0").satisfies(SemVerRange("~1.0")));
883     assert(!SemVer("1.0.0").satisfies(SemVerRange("<1")));
884     assert(!SemVer("1.1.1").satisfies(SemVerRange(">=1.2")));
885     assert(!SemVer("1.3.0").satisfies(SemVerRange("<=1.2")));
886     assert(!SemVer("2.0.0-beta").satisfies(SemVerRange("1")));
887     assert(!SemVer("0.5.4-alpha").satisfies(SemVerRange("~v0.5.4-beta")));
888     assert(!SemVer("1.0.0-beta").satisfies(SemVerRange("<1")));
889     assert(!SemVer("0.8.2").satisfies(SemVerRange("=0.7.x")));
890     assert(!SemVer("0.6.2").satisfies(SemVerRange(">=0.7.x")));
891     assert(!SemVer("0.7.2").satisfies(SemVerRange("<=0.7.x")));
892     assert(!SemVer("1.2.3-beta").satisfies(SemVerRange("<1.2.3")));
893     assert(!SemVer("1.2.3-beta").satisfies(SemVerRange("=1.2.3")));
894     assert(!SemVer("1.2.8").satisfies(SemVerRange(">1.3")));
895     assert(!SemVer("2.0.0-alpha").satisfies(SemVerRange("^1.2.3")));
896     assert(!SemVer("1.2.2").satisfies(SemVerRange("^1.2.3")));
897     assert(!SemVer("1.1.9").satisfies(SemVerRange("^1.2")));
898     assert(!SemVer("2.0.0-pre").satisfies(SemVerRange("^1.2.3")));
899 
900     auto semVers = [SemVer("1.1.0"), SemVer("1.0.0"), SemVer("0.8.0")];
901     assert(semVers.maxSatisfying(SemVerRange("<=1.0.0")) == SemVer("1.0.0"));
902     assert(semVers.maxSatisfying(SemVerRange(">=1.0")) == SemVer("1.1.0"));
903 
904     semVers = [SemVer("1.0.0+build.3"), SemVer("1.0.0+build.1"), SemVer("1.1.0")];
905     assert(semVers.maxSatisfying(SemVerRange("<=1.0.0")) == SemVer("1.0.0+build.3"));
906     assert(semVers.maxSatisfying(SemVerRange(">=1.0")) == SemVer("1.1.0"));
907 }