1 module dud.resolve.providier;
2 
3 import std.algorithm.iteration : map, filter;
4 import std.algorithm.searching : find;
5 import std.algorithm.sorting : sort;
6 import std.array : array, empty, front;
7 import std.exception : enforce;
8 import std.typecons : Nullable;
9 import std.json;
10 import std.format : format;
11 import dud.pkgdescription : PackageDescription, jsonToPackageDescription;
12 import dud.pkgdescription.duplicate;
13 import dud.semver.semver;
14 import dud.semver.versionrange;
15 
16 @safe pure:
17 
18 interface PackageProvidier {
19 	const(PackageDescription)[] getPackage(string name,
20 			const(VersionRange) verRange);
21 
22 	const(PackageDescriptionVersionRange) getPackage(string name, string ver);
23 }
24 
25 struct PackageDescriptionVersionRange {
26 	PackageDescription pkg;
27 	VersionRange ver;
28 }
29 
30 PackageDescriptionVersionRange dup(const(PackageDescriptionVersionRange) i) {
31 	return PackageDescriptionVersionRange(
32 			dud.pkgdescription.duplicate.dup(i.pkg),
33 			i.ver.dup());
34 }
35 
36 struct DumpFileProvidier {
37 	// the cache either holds all or non
38 	bool isLoaded;
39 	const string dumpFileName;
40 	PackageDescriptionVersionRange[][string] cache;
41 	JSONValue[string] parsedPackages;
42 
43 	this(string dumpFileName) {
44 		this.dumpFileName = dumpFileName;
45 	}
46 
47 	private void makeSureIsLoaded() {
48 		import std.file : readText;
49 		if(!this.isLoaded) {
50 			JSONValue dump = parseJSON(readText(this.dumpFileName));
51 			enforce(dump.type == JSONType.array);
52 			foreach(value; dump.arrayNoRef()) {
53 				enforce(value.type == JSONType.object);
54 				enforce("name" in value && value["name"].type == JSONType..string);
55 				string name = value["name"].str();
56 				this.parsedPackages[name] = value;
57 			}
58 			this.isLoaded = true;
59 		}
60 	}
61 
62 	const(PackageDescriptionVersionRange)[] getPackages(string name,
63 			string verRange)
64 	{
65 		Nullable!VersionRange v = parseVersionRange(verRange);
66 		enforce(!v.isNull());
67 		return this.getPackages(name, v.get());
68 	}
69 
70 	const(PackageDescriptionVersionRange)[] getPackages(string name,
71 			const(VersionRange) verRange)
72 	{
73 		import dud.semver.checks : allowsAny;
74 		this.makeSureIsLoaded();
75 		auto pkgs = this.ensurePackageIsInCache(name);
76 		return (*pkgs)
77 			.filter!(pkg => !pkg.ver.isBranch())
78 			.filter!(pkg => allowsAny(verRange, pkg.ver))
79 			.array;
80 	}
81 
82 	PackageDescriptionVersionRange[]* ensurePackageIsInCache(string name) {
83 		auto pkgs = name in this.cache;
84 		if(pkgs is null) {
85 			auto ptr = name in parsedPackages;
86 			enforce(ptr !is null, format(
87 				"Couldn't find '%s' in dump.json", name));
88 			this.cache[name] = dumpJSONToPackage(*ptr);
89 			pkgs = name in this.cache;
90 		}
91 		return pkgs;
92 	}
93 
94 	const(PackageDescription) getPackage(string name, string ver) {
95 		this.makeSureIsLoaded();
96 		auto pkgs = this.ensurePackageIsInCache(name);
97 		Nullable!VersionRange v = parseVersionRange(ver);
98 		enforce(!v.isNull());
99 		const VersionRange vr = v.get();
100 
101 		auto f = (*pkgs).find!((it, s) => it.ver == s)(vr);
102 		enforce(!f.empty, format("No version '%s' for package '%s' could"
103 			~ " be found in versions [%s]", name, vr,
104 			(*pkgs).map!(it => it.ver)));
105 		return f.front.pkg;
106 	}
107 }
108 
109 private PackageDescriptionVersionRange[] dumpJSONToPackage(JSONValue jv) {
110 	enforce(jv.type == JSONType.object, format("Expected object got '%s'",
111 			jv.type));
112 	auto vers = "versions" in jv;
113 	enforce(vers !is null, "Couldn't find versions array");
114 	enforce((*vers).type == JSONType.array, format("Expected array got '%s'",
115 			(*vers).type));
116 
117 	return (*vers).arrayNoRef()
118 		.map!((it) {
119 			auto ptr = "packageDescription" in it;
120 			enforce(ptr !is null && (*ptr).type == JSONType.object);
121 			PackageDescription pkg = jsonToPackageDescription(*ptr);
122 
123 			auto ver = "version" in it;
124 			enforce(ver !is null && (*ver).type == JSONType..string);
125 			Nullable!VersionRange v = parseVersionRange((*ver).str());
126 			enforce(!v.isNull());
127 			const VersionRange vr = v.get();
128 			return PackageDescriptionVersionRange(pkg, vr.dup);
129 		})
130 		.array
131 		.sort!((a, b) => a.ver > b.ver)
132 		.array;
133 }