1 module dud.pkgdescription.compare; 2 3 import std.stdio; 4 import std.array : empty, front; 5 import std.algorithm.searching : all, any, canFind, find; 6 import std.traits : Unqual, FieldNameTuple; 7 import std.typecons : nullable; 8 9 import dud.semver.semver : SemVer; 10 import dud.semver.versionrange; 11 import dud.pkgdescription; 12 import dud.pkgdescription.platformselection; 13 14 @safe pure: 15 16 bool areEqual(const PackageDescription a, const PackageDescription b) { 17 static foreach(mem; FieldNameTuple!PackageDescription) {{ 18 alias aMemType = typeof(__traits(getMember, a, mem)); 19 20 static if(is(aMemType == const(string)) 21 || is(aMemType == const(TargetType)) 22 || is(aMemType == const(string[])) 23 ) 24 { 25 if(__traits(getMember, a, mem) != __traits(getMember, b, mem)) { 26 return false; 27 } 28 } else static if(is(aMemType == const(SemVer))) { 29 auto aSem = __traits(getMember, a, mem); 30 auto bSem = __traits(getMember, b, mem); 31 if(aSem != bSem) { 32 return false; 33 } 34 } else static if(is(aMemType == const(Dependency[])) 35 || is(aMemType == const(String)) 36 || is(aMemType == const(Strings)) 37 || is(aMemType == const(Path)) 38 || is(aMemType == const(UnprocessedPath)) 39 || is(aMemType == const(Paths)) 40 || is(aMemType == const(PackageDescription)) 41 || is(aMemType == const(PackageDescription[string])) 42 || is(aMemType == const(SubPackage[])) 43 || is(aMemType == const(BuildRequirements)) 44 || is(aMemType == const(SubConfigs)) 45 || is(aMemType == const(BuildType[string])) 46 || is(aMemType == const(Platform[])) 47 || is(aMemType == const(Platform[][])) 48 || is(aMemType == const(ToolchainRequirement[Toolchain])) 49 || is(aMemType == const(BuildOptions)) 50 ) 51 { 52 if(!areEqual(__traits(getMember, a, mem), 53 __traits(getMember, b, mem))) 54 { 55 return false; 56 } 57 } else { 58 static assert(false, aMemType.stringof ~ " not handled"); 59 } 60 }} 61 return true; 62 } 63 64 // 65 // PackageDescriptionNoPlatform 66 // 67 68 bool areEqual(const PackageDescriptionNoPlatform a, 69 const PackageDescriptionNoPlatform b) 70 { 71 static foreach(mem; FieldNameTuple!PackageDescriptionNoPlatform) {{ 72 alias aMemType = typeof(__traits(getMember, a, mem)); 73 74 static if(is(aMemType == const(string)) 75 || is(aMemType == const(TargetType)) 76 || is(aMemType == const(string[])) 77 ) 78 { 79 if(__traits(getMember, a, mem) != __traits(getMember, b, mem)) { 80 return false; 81 } 82 } else static if(is(aMemType == const(SemVer))) { 83 auto aSem = __traits(getMember, a, mem); 84 auto bSem = __traits(getMember, b, mem); 85 if(aSem != bSem) { 86 return false; 87 } 88 } else static if(is(aMemType == const(DependencyNoPlatform[string])) 89 || is(aMemType == const(UnprocessedPath)) 90 || is(aMemType == const(UnprocessedPath[])) 91 || is(aMemType == const(PackageDescriptionNoPlatform[])) 92 || is(aMemType == const(SubPackageNoPlatform[])) 93 || is(aMemType == const(BuildRequirement[])) 94 || is(aMemType == const(BuildOption[])) 95 || is(aMemType == const(ToolchainRequirement[Toolchain])) 96 || is(aMemType == const(string[string])) 97 ) { 98 if(!areEqual(__traits(getMember, a, mem), 99 __traits(getMember, b, mem))) 100 { 101 return false; 102 } 103 } else { 104 static assert(false, aMemType.stringof ~ " not handled"); 105 } 106 }} 107 return true; 108 } 109 110 bool areEqual(const(PackageDescriptionNoPlatform[]) as, 111 const(PackageDescriptionNoPlatform[]) bs) 112 { 113 return as.all!(it => canFind!((a,b) => areEqual(a, b))(bs, it)) 114 && bs.all!(it => canFind!((a,b) => areEqual(a, b))(as, it)); 115 } 116 117 // 118 // BuildRequirement 119 // 120 121 bool areEqual(const(BuildRequirement[]) as, const(BuildRequirement[]) bs) { 122 if(as.length != bs.length) { 123 return false; 124 } 125 126 return as.all!(it => canFind(bs, it)) 127 && bs.all!(it => canFind(as, it)); 128 129 } 130 131 // 132 // SubPackageNoPlatform 133 // 134 135 bool areEqual(const(SubPackageNoPlatform[]) as, 136 const(SubPackageNoPlatform[]) bs) 137 { 138 if(as.length != bs.length) { 139 return false; 140 } 141 142 return as.all!(it => canFind!((a,b) => areEqual(a, b))(bs, it)) 143 && bs.all!(it => canFind!((a,b) => areEqual(a, b))(as, it)); 144 } 145 146 bool areEqual(const SubPackageNoPlatform a, const SubPackageNoPlatform b) { 147 return areEqual(a.path, b.path) 148 && a.inlinePkg.isNull() == b.inlinePkg.isNull() 149 && !a.inlinePkg.isNull() 150 ? areEqual(a.inlinePkg.get(), b.inlinePkg.get()) 151 : true; 152 } 153 154 // 155 // ToolchainRequirement 156 // 157 158 bool areEqual(const ToolchainRequirement as, const ToolchainRequirement bs) { 159 return as.no == bs.no && as.version_ == bs.version_; 160 } 161 162 bool areEqual(const ToolchainRequirement[Toolchain] as, 163 const ToolchainRequirement[Toolchain] bs) 164 { 165 if(as.length != bs.length) { 166 return false; 167 } 168 169 return aaCmp!simpleCmp(as, bs); 170 } 171 172 // 173 // BuildOption 174 // 175 176 bool areEqual(const BuildOption[] as, const BuildOption[] bs) { 177 if(as.length != bs.length) { 178 return false; 179 } 180 181 bool c(const(BuildOption) a, const(BuildOption) b) { 182 return a == b; 183 } 184 185 return as.all!(it => canFind!c(bs, it)) 186 && bs.all!(it => canFind!c(as, it)); 187 } 188 189 // 190 // BuildOptions[] 191 // 192 193 bool areEqual(const BuildOptions as, const BuildOptions bs) { 194 if(!areEqual(as.unspecifiedPlatform, bs.unspecifiedPlatform)) { 195 return false; 196 } 197 198 return aaCmp!(areEqual)(as.platforms, bs.platforms); 199 } 200 201 bool areEqual(const BuildOptions[] as, const BuildOptions[] bs) { 202 if(as.length != bs.length) { 203 return false; 204 } 205 206 return as.all!(a => canFind(bs, a)) && bs.all!(a => canFind(as, a)); 207 } 208 209 // 210 // BuildType[] 211 // 212 213 bool areEqual(const BuildType as, const BuildType bs) { 214 return as.name == bs.name 215 && areEqual(as.pkg, bs.pkg); 216 } 217 218 bool areEqual(const BuildType[string] as, const BuildType[string] bs) { 219 return aaCmp!((const(BuildType) a, const(BuildType) b) => areEqual(a, b)) 220 (as, bs); 221 } 222 223 // 224 // SubConfigs 225 // 226 227 bool areEqual(const SubConfigs as, const SubConfigs bs) { 228 if(as.unspecifiedPlatform.length != bs.unspecifiedPlatform.length) { 229 return false; 230 } 231 232 if(as.configs.length != bs.configs.length) { 233 return false; 234 } 235 236 if(!aaCmp!simpleCmp(as.unspecifiedPlatform, bs.unspecifiedPlatform)) { 237 return false; 238 } 239 240 return aaCmp!(aaCmp!(simpleCmp, const(string[string])))(as.configs, bs.configs); 241 } 242 243 // 244 // BuildRequirement 245 // 246 247 bool areEqual(const BuildRequirementPlatform as, 248 const BuildRequirementPlatform bs) 249 { 250 return areEqual(as.platforms, bs.platforms) 251 && as.requirements.all!(a => canFind(bs.requirements, a)) 252 && bs.requirements.all!(a => canFind(as.requirements, a)); 253 254 } 255 256 bool areEqual(const BuildRequirements as, const BuildRequirements bs) { 257 if(as.platforms.length != bs.platforms.length) { 258 return false; 259 } 260 261 alias cmp = (const BuildRequirementPlatform a, 262 const BuildRequirementPlatform b) { return areEqual(a, b); }; 263 264 return as.platforms.all!(a => canFind!cmp(bs.platforms, a)) 265 && bs.platforms.all!(b => canFind!cmp(as.platforms, b)); 266 } 267 268 // 269 // SubPackage 270 // 271 272 bool areEqual(const SubPackage as, const SubPackage bs) { 273 if(as.inlinePkg.isNull() != bs.inlinePkg.isNull()) { 274 return false; 275 } 276 277 return !as.inlinePkg.isNull() 278 ? areEqual(as.inlinePkg.get(), bs.inlinePkg.get()) 279 : areEqual(as.path, bs.path); 280 } 281 282 bool areEqual(const SubPackage[] as, const SubPackage[] bs) { 283 if(as.length != bs.length) { 284 return false; 285 } 286 287 return as.all!(a => canFind!areEqual(bs, a)) 288 && bs.all!(a => canFind!areEqual(as, a)); 289 } 290 291 // 292 // PackageDescription[] 293 // 294 295 bool areEqual(const PackageDescription[string] as, 296 const PackageDescription[string] bs) 297 { 298 return aaCmp!(simpleCmp)(as, bs); 299 } 300 301 // 302 // Paths 303 // 304 305 bool areEqual(const PathsPlatform a, const PathsPlatform b) { 306 return areEqual(a.platforms, b.platforms) && a.paths == b.paths; 307 } 308 309 bool areEqual(const Paths a, const Paths b) { 310 if(a.platforms.length != b.platforms.length) { 311 return false; 312 } 313 314 return a.platforms.all!(s => canFind!areEqual(b.platforms, s)) 315 && b.platforms.all!(s => canFind!areEqual(a.platforms, s)); 316 } 317 318 unittest { 319 assert(areEqual(Paths.init, Paths.init)); 320 } 321 322 unittest { 323 Platform[] p1 = [Platform.gnu, Platform.dmd]; 324 Platform[] p2 = [Platform.dmd, Platform.gnu, Platform.win32]; 325 PathsPlatform s1 = PathsPlatform( 326 [ UnprocessedPath("foobar"), UnprocessedPath("barfoo") ], p1); 327 PathsPlatform s2 = PathsPlatform( 328 [ UnprocessedPath("barfoo"), UnprocessedPath("foobar") ], p1); 329 PathsPlatform s3 = PathsPlatform( 330 [ UnprocessedPath("barfoo"), UnprocessedPath("foobar") ], p2); 331 PathsPlatform s4 = PathsPlatform( 332 [ UnprocessedPath("foobar"), UnprocessedPath("barfoo") ], p2); 333 PathsPlatform s5 = PathsPlatform( 334 [ UnprocessedPath("barfoo")], p1); 335 PathsPlatform s6 = PathsPlatform( 336 [ UnprocessedPath("barfoo")], p2); 337 338 assert( areEqual(Paths([s1]), Paths([s1]))); 339 assert( areEqual(Paths([s1, s2]), Paths([s1, s2]))); 340 assert( areEqual(Paths([s2, s1]), Paths([s1, s2]))); 341 assert(!areEqual(Paths([s2, s3]), Paths([s1, s2]))); 342 assert(!areEqual(Paths([s2, s3, s1]), Paths([s1, s2]))); 343 assert(!areEqual(Paths([s3]), Paths([s3, s2]))); 344 assert(!areEqual(Paths([s4]), Paths([s3]))); 345 assert( areEqual(Paths([s5]), Paths([s5]))); 346 assert( areEqual(Paths([s6]), Paths([s6]))); 347 assert(!areEqual(Paths([s5]), Paths([s6]))); 348 } 349 350 // 351 // VersionRange 352 // 353 354 bool areEqual(const VersionRange a, const VersionRange b) { 355 return a == b; 356 } 357 358 // 359 // Path 360 // 361 362 bool areEqual(const(UnprocessedPath[]) as, const(UnprocessedPath[]) bs) { 363 if(as.length != bs.length) { 364 return false; 365 } 366 367 bool cmp(const(UnprocessedPath) a, const(UnprocessedPath) b) { 368 return areEqual(a, b); 369 } 370 371 return as.all!(it => canFind!cmp(bs, it)) 372 && bs.all!(it => canFind!cmp(as, it)); 373 } 374 375 bool areEqual(const UnprocessedPath a, const UnprocessedPath b) { 376 return a.path == b.path; 377 } 378 379 bool areEqual(const PathPlatform a, const PathPlatform b) { 380 return areEqual(a.platforms, b.platforms) && a.path == b.path; 381 } 382 383 bool areEqual(const Path a, const Path b) { 384 if(a.platforms.length != b.platforms.length) { 385 return false; 386 } 387 388 return a.platforms.all!(s => canFind!areEqual(b.platforms, s)) 389 && b.platforms.all!(s => canFind!areEqual(a.platforms, s)); 390 } 391 392 unittest { 393 assert(areEqual(Path.init, Path.init)); 394 } 395 396 unittest { 397 Platform[] p1 = [Platform.gnu, Platform.dmd]; 398 Platform[] p2 = [Platform.dmd, Platform.gnu, Platform.win32]; 399 Platform[] p3 = [Platform.dmd, Platform.gnu, Platform.posix]; 400 PathPlatform s1 = PathPlatform(UnprocessedPath("foobar"), p1); 401 PathPlatform s2 = PathPlatform(UnprocessedPath("args"), p1); 402 PathPlatform s3 = PathPlatform(UnprocessedPath("args"), p2); 403 PathPlatform s4 = PathPlatform(UnprocessedPath("foobar"), p2); 404 PathPlatform s5 = PathPlatform(UnprocessedPath("args"), p3); 405 PathPlatform s6 = PathPlatform(UnprocessedPath("foobar"), p3); 406 407 assert( areEqual(Path([s1]), Path([s1]))); 408 assert(!areEqual(Path([s1]), Path([s2]))); 409 assert(!areEqual(Path([s3]), Path([s2]))); 410 assert( areEqual(Path([s1, s2]), Path([s2, s1]))); 411 assert(!areEqual(Path([s1, s2, s1]), Path([s2, s1]))); 412 assert(!areEqual(Path([s3, s2, s1]), Path([s2, s4, s1]))); 413 assert(!areEqual(Path([s3, s2, s4]), Path([s2, s3, s5]))); 414 assert( areEqual(Path([s6, s6, s6]), Path([s6, s6, s6]))); 415 } 416 417 // 418 // Strings 419 // 420 421 bool areEqual(const StringsPlatform a, const StringsPlatform b) { 422 return areEqual(a.platforms, b.platforms) && a.strs == b.strs; 423 } 424 425 bool areEqual(const Strings a, const Strings b) { 426 if(a.platforms.length != b.platforms.length) { 427 return false; 428 } 429 430 return a.platforms.all!(s => canFind!areEqual(b.platforms, s)) 431 && b.platforms.all!(s => canFind!areEqual(a.platforms, s)); 432 } 433 434 unittest { 435 Strings a; 436 Strings b; 437 assert(areEqual(a, b)); 438 } 439 440 unittest { 441 Platform[] p1 = [Platform.gnu, Platform.dmd]; 442 Platform[] p2 = [Platform.dmd, Platform.gnu, Platform.win32]; 443 Platform[] p3 = [Platform.dmd, Platform.gnu, Platform.posix]; 444 StringsPlatform s1 = StringsPlatform(["Hello World"], p1); 445 StringsPlatform s2 = StringsPlatform(["Foobar", "Hello World"], p1); 446 StringsPlatform s3 = StringsPlatform(["Hello World"], p2); 447 StringsPlatform s4 = StringsPlatform(["Hello World"], p3); 448 449 assert( areEqual(Strings([s1]), Strings([s1]))); 450 assert(!areEqual(Strings([s1]), Strings([s2]))); 451 assert( areEqual(Strings([s1, s2]), Strings([s2, s1]))); 452 assert( areEqual(Strings([s1, s3]), Strings([s3, s1]))); 453 assert(!areEqual(Strings([s1, s2, s3]), Strings([s2, s1]))); 454 assert( areEqual(Strings([s1, s2, s3]), Strings([s2, s3, s1]))); 455 assert(!areEqual(Strings([s4, s2, s1]), Strings([s2, s3, s1]))); 456 assert(!areEqual(Strings([s1, s2, s1]), Strings([s2, s3, s1]))); 457 assert(!areEqual(Strings([s4]), Strings([s1]))); 458 } 459 460 // 461 // String 462 // 463 464 bool areEqual(const StringPlatform a, const StringPlatform b) { 465 return areEqual(a.platforms, b.platforms) && a.str == b.str; 466 } 467 468 bool areEqual(const String a, const String b) { 469 if(a.platforms.length != b.platforms.length) { 470 return false; 471 } 472 473 return a.platforms.all!(s => canFind!areEqual(b.platforms, s)) 474 && b.platforms.all!(s => canFind!areEqual(a.platforms, s)); 475 } 476 477 unittest { 478 String a; 479 String b; 480 assert(areEqual(a, b)); 481 } 482 483 unittest { 484 Platform[] p1 = [Platform.gnu, Platform.dmd]; 485 Platform[] p2 = [Platform.dmd, Platform.gnu]; 486 Platform[] p3 = [Platform.dmd, Platform.gnu, Platform.posix]; 487 StringPlatform s1 = StringPlatform("Hello World", p1); 488 StringPlatform s2 = StringPlatform("Hello World", p2); 489 StringPlatform s3 = StringPlatform("Hello World", p3); 490 StringPlatform s4 = StringPlatform("Hello", p1); 491 492 assert( areEqual(String([s1]), String([s1]))); 493 assert( areEqual(String([s1, s2]), String([s2, s1]))); 494 assert(!areEqual(String([s1, s2, s3]), String([s2, s1]))); 495 assert( areEqual(String([s1, s2, s3]), String([s2, s3, s1]))); 496 assert(!areEqual(String([s4, s2, s1]), String([s2, s3, s1]))); 497 assert(!areEqual(String([s1, s2, s1]), String([s2, s3, s1]))); 498 assert(!areEqual(String([s4]), String([s1]))); 499 } 500 501 // 502 // DependencyNoPlatform 503 // 504 505 bool areEqual(const(DependencyNoPlatform[string]) as, 506 const(DependencyNoPlatform[string]) bs) 507 { 508 if(as.length != bs.length) { 509 return false; 510 } 511 512 return as.byKeyValue 513 .all!(it => it.key in bs && areEqual(bs[it.key], it.value)); 514 } 515 516 bool areEqual(const(DependencyNoPlatform) a, const(DependencyNoPlatform) b) { 517 return a.name == b.name 518 && a.version_.isNull() == b.version_.isNull() 519 && a.version_.isNull() 520 ? true 521 : areEqual(a.version_.get(), b.version_.get()) 522 && a.optional.isNull() == b.optional.isNull() 523 && a.optional.isNull() 524 ? true 525 : a.optional.get() == b.optional.get() 526 && a.default_.isNull() == b.default_.isNull() 527 && a.default_.isNull() 528 ? true 529 : a.default_.get() == b.default_.get(); 530 } 531 532 // 533 // Dependency 534 // 535 536 bool areEqual(const(Dependency)[] as, const(Dependency)[] bs) { 537 if(as.length != bs.length) { 538 return false; 539 } 540 541 return as.all!(it => canFind!((a, b) => areEqual(a, b))(bs, it)) 542 && bs.all!(it => canFind!((a, b) => areEqual(a, b))(as, it)); 543 } 544 545 bool areEqual(const ref Dependency a, ref const Dependency b) { 546 return a.name == b.name 547 && a.version_.isNull() == b.version_.isNull() 548 && (!a.version_.isNull() ? a.version_ == b.version_ : true) 549 && a.path == b.path 550 && areEqual(a.platforms, b.platforms) 551 && a.optional.isNull() == b.optional.isNull() 552 && (!a.optional.isNull() ? a.optional == b.optional : true) 553 && a.default_.isNull() == b.default_.isNull() 554 && (!a.default_.isNull() ? a.default_ == b.default_ : true); 555 } 556 557 unittest { 558 Dependency a; 559 Dependency b; 560 assert(areEqual(a, b)); 561 assert(areEqual(b, a)); 562 563 a.path = UnprocessedPath("foobar"); 564 assert(!areEqual(a, b)); 565 assert(!areEqual(b, a)); 566 567 b.path = UnprocessedPath("foobar"); 568 assert(areEqual(a, b)); 569 assert(areEqual(b, a)); 570 } 571 572 unittest { 573 Dependency a; 574 a.path = UnprocessedPath("args"); 575 Dependency b; 576 b.path = UnprocessedPath("args2"); 577 Dependency c; 578 c.path = UnprocessedPath("args3"); 579 c.default_ = nullable(true); 580 581 assert( areEqual([a, b], [b, a])); 582 assert(!areEqual([a, b, a], [b, a])); 583 assert(!areEqual([a], [a, a])); 584 assert(!areEqual([a, b, c], [b, a])); 585 assert( areEqual([a, b, c], [b, c, a])); 586 } 587 588 // 589 // Platform 590 // 591 592 bool areEqual(const Platform[][] a, const Platform[][] b) { 593 if(a.length != b.length) { 594 return false; 595 } else if(a.length == 0) { 596 return true; 597 } 598 auto cmp = (const(Platform)[] a, const(Platform)[] b) => areEqual(a, b); 599 return a.all!(it => canFind!cmp(b, it)) && b.all!(it => canFind!cmp(a, it)); 600 } 601 602 603 bool areEqual(const Platform[] a, const Platform[] b) { 604 if(a.length != b.length) { 605 return false; 606 } else if(a.length == 0) { 607 return true; 608 } 609 return a.all!(it => canFind(b, it)); 610 } 611 612 unittest { 613 Platform[] a; 614 Platform[] b; 615 assert(areEqual(a, b)); 616 } 617 618 unittest { 619 Platform[] a; 620 Platform[] b = [Platform.gnu]; 621 assert(!areEqual(a, b)); 622 } 623 624 unittest { 625 Platform[] a = [Platform.gnu, Platform.bsd]; 626 Platform[] b = [Platform.bsd, Platform.gnu]; 627 assert(areEqual(a, b)); 628 } 629 630 // 631 // string[string] 632 // 633 bool areEqual(const(string[string]) as, const(string[string]) bs) { 634 return as == bs; 635 } 636 637 // 638 // Helper 639 // 640 641 bool aaCmp(alias cmp, AA)(AA a, AA b) { 642 if(a.length != b.length) { 643 return false; 644 } 645 646 foreach(key, value; a) { 647 auto bVal = key in b; 648 if(bVal is null) { 649 return false; 650 } 651 652 if(!cmp(value, *bVal)) { 653 return false; 654 } 655 } 656 657 foreach(key, value; b) { 658 auto aVal = key in a; 659 if(aVal is null) { 660 return false; 661 } 662 663 if(!cmp(value, *aVal)) { 664 return false; 665 } 666 } 667 668 return true; 669 } 670 671 bool simpleCmp(T)(auto ref T a, auto ref T b) { 672 return a == b; 673 }