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(cast(const char[]) prerelease); 145 if (!build.empty) 146 semVer ~= "+" ~ "%-(%s.%)".format(cast(const char[]) 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() return 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(scope 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 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 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 }