1 module dubproxy; 2 3 import std.array : empty; 4 import std.typecons : nullable, Nullable; 5 import std.json; 6 import std.format : format, formattedWrite; 7 import std.file : exists, readText; 8 import std.exception : enforce; 9 import std.stdio; 10 11 @safe: 12 13 struct DubProxyFile { 14 string[string] packages; 15 16 string getPath(string pkg) const { 17 const string* pkgPath = pkg in this.packages; 18 enforce(pkgPath, format!"No package with name '%s' found in DPF"(pkg)); 19 return *pkgPath; 20 } 21 22 bool pkgExists(string pkg) const { 23 return (pkg in this.packages) !is null; 24 } 25 26 void insertPath(string pkg, string path) { 27 const(string)* oldPath = pkg in this.packages; 28 enforce(oldPath is null, format!"Package '%s' already with path '%s'" 29 (pkg, *oldPath)); 30 this.packages[pkg] = path; 31 } 32 33 void updatePath(string pkg, string path) { 34 enforce(this.pkgExists(pkg), format!"Package '%s' must exist for update" 35 (pkg)); 36 this.packages[pkg] = path; 37 } 38 39 void removePackage(string pkg) { 40 enforce(this.pkgExists(pkg), format!"Package '%s' does not exist in DPF" 41 (pkg)); 42 this.packages.remove(pkg); 43 } 44 } 45 46 DubProxyFile fromFile(string path) @safe { 47 enforce(exists(path), format!"No DPF exists with path '%s'"( path)); 48 return fromString(readText(path)); 49 } 50 51 DubProxyFile fromString(string jsonText) @safe { 52 JSONValue j = parseJSON(jsonText); 53 54 enforce(j.type == JSONType.object, "Parsed DPF top level is " 55 ~ "not an object"); 56 57 const(JSONValue)* pkg = "packages" in j; 58 enforce(pkg !is null, "Key 'packages does not exist in parsed DPF"); 59 enforce(pkg.type == JSONType.object, 60 "Value of 'packages' must be object"); 61 62 DubProxyFile ret; 63 64 JSONValue pkgCopy = *pkg; 65 66 () @trusted { 67 foreach(string key, ref JSONValue value; pkgCopy) { 68 enforce(value.type == JSONType..string, format! 69 ("Value type to key '%s' was '%s', type string " 70 ~ "was expected")(key, value.type)); 71 ret.packages[key] = value.str(); 72 } 73 }(); 74 75 return ret; 76 } 77 78 void toFile(const(DubProxyFile) dpf, string path) { 79 auto f = File(path, "w"); 80 toImpl(f.lockingTextWriter(), dpf); 81 } 82 83 string toString(const(DubProxyFile) dpf, string path) { 84 import std.array : appender; 85 auto app = appender!string(); 86 toImpl(app, dpf); 87 return app.data; 88 } 89 90 private void toImpl(LTW)(auto ref LTW ltw, const(DubProxyFile) dpf) { 91 import std.algorithm.iteration : map, joiner; 92 import std.algorithm.mutation : copy; 93 94 formattedWrite(ltw, "{\n\t\"packages\" : {\n"); 95 dpf.packages.byKeyValue() 96 .map!(it => format!"\t\t\"%s\" : \"%s\""(it.key, it.value)) 97 .joiner(",\n") 98 .copy(ltw); 99 formattedWrite(ltw, "\n\t}"); 100 formattedWrite(ltw, "\n}"); 101 } 102 103 DubProxyFile getCodeDlangOrgCopy() { 104 return parseCodeDlangOrgData(getCodeDlangOrgData()); 105 } 106 107 string getCodeDlangOrgData() @trusted { 108 import std.exception : assumeUnique; 109 import std.net.curl; 110 import std.zlib; 111 112 auto data = get("https://code.dlang.org/api/packages/dump"); 113 114 auto uc = new UnCompress(); 115 116 const(void[]) un = uc.uncompress(data); 117 return assumeUnique(cast(const(char)[])un); 118 } 119 120 DubProxyFile parseCodeDlangOrgData(string data) { 121 string fixUpKind(string kind) { 122 switch(kind) { 123 case "github": return "github.com"; 124 case "bitbucket": return "bitbucket.org"; 125 case "gitlab": return "gitlab.com"; 126 default: 127 assert(false, kind); 128 } 129 } 130 131 JSONValue parsed = parseJSON(data); 132 DubProxyFile ret; 133 134 enforce(parsed.type == JSONType.array, 135 "Downloaded code.dlang.org dump was not an array"); 136 137 foreach(it; parsed.arrayNoRef()) { 138 enforce(it.type == JSONType.object, 139 format!"Expected object got '%s' from '%s'"(it.type, 140 it.toPrettyString())); 141 auto name = "name" in it; 142 enforce(name && name.type == JSONType..string, 143 format!"no name found in '%s'"(it.toPrettyString())); 144 string nameStr = name.str; 145 //write(nameStr, " : "); 146 auto repo = "repository" in it; 147 if(repo && repo.type == JSONType.object) { 148 auto kind = "kind" in (*repo); 149 auto owner = "owner" in (*repo); 150 auto project = "project" in (*repo); 151 152 enforce(kind && kind.type == JSONType..string, 153 format!"kind was null in '%s'" (repo.toPrettyString())); 154 enforce(owner && owner.type == JSONType..string, 155 format!"owner was null in '%s'" (repo.toPrettyString())); 156 enforce(project && project.type == JSONType..string, 157 format!"project was null in '%s'" (repo.toPrettyString())); 158 159 string url = format!"https://%s/%s/%s.git"(fixUpKind(kind.str), 160 owner.str, project.str); 161 //writeln(url); 162 ret.insertPath(nameStr, url); 163 } 164 } 165 166 return ret; 167 }