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 }