1 module reggae.dub.json; 2 3 import reggae.dub.info; 4 import reggae.build; 5 import std.json; 6 import std.algorithm: map, filter; 7 8 9 DubInfo getDubInfo(in string origString) @trusted { 10 11 import std.string: indexOf; 12 import core.exception: RangeError; 13 import std.array; 14 15 string nextOpenCurly(string str) { 16 return str[str.indexOf("{") .. $]; 17 } 18 19 try { 20 21 // the output might contain non-JSON at the beginning in stderr 22 auto jsonString = nextOpenCurly(origString); 23 24 for(; ; jsonString = nextOpenCurly(jsonString[1..$])) { 25 auto json = parseJSON(jsonString); 26 27 bool hasPackages() { 28 try 29 return ("packages" in json.object) !is null; 30 catch(JSONException ex) 31 return false; 32 } 33 34 if(!hasPackages) { 35 continue; 36 } 37 38 auto packages = json.byKey("packages"); 39 auto info = DubInfo(packages.array. 40 map!(a => DubPackage(a.byKey("name").str, 41 a.byKey("path").str, 42 a.getOptional("mainSourceFile"), 43 a.getOptional("targetFileName"), 44 a.byKey("dflags").jsonValueToStrings, 45 a.byKey("lflags").jsonValueToStrings, 46 a.byKey("importPaths").jsonValueToStrings, 47 a.byKey("stringImportPaths").jsonValueToStrings, 48 a.byKey("files").jsonValueToFiles, 49 a.getOptional("targetType"), 50 a.getOptionalList("versions"), 51 a.getOptionalList("dependencies"), 52 a.getOptionalList("libs"), 53 a.byOptionalKey("active", true), //true for backwards compatibility 54 a.getOptionalList("preBuildCommands"), 55 )). 56 filter!(a => a.active). 57 array); 58 59 60 61 // in dub.json/dub.sdl, $PACKAGE_DIR is a variable that refers to the root 62 // of the dub package 63 void resolvePackageDir(in DubPackage dubPackage, ref string str) { 64 str = str.replace("$PACKAGE_DIR", dubPackage.path); 65 } 66 67 foreach(ref dubPackage; info.packages) { 68 foreach(ref member; dubPackage.tupleof) { 69 70 static if(is(typeof(member) == string)) { 71 resolvePackageDir(dubPackage, member); 72 } else static if(is(typeof(member) == string[])) { 73 foreach(ref elt; member) 74 resolvePackageDir(dubPackage, elt); 75 } 76 } 77 } 78 79 return info; 80 } 81 } catch(RangeError e) { 82 import std.stdio; 83 stderr.writeln("Could not parse the output of dub describe:\n", origString); 84 throw e; 85 } 86 } 87 88 89 private string[] jsonValueToFiles(JSONValue files) @trusted { 90 import std.array; 91 92 return files.array. 93 filter!(a => ("type" in a && a.byKey("type").str == "source") || 94 ("role" in a && a.byKey("role").str == "source") || 95 ("type" !in a && "role" !in a)). 96 map!(a => a.byKey("path").str). 97 array; 98 } 99 100 private string[] jsonValueToStrings(JSONValue json) @trusted { 101 import std.array; 102 return json.array.map!(a => a.str).array; 103 } 104 105 106 private JSONValue byKey(JSONValue json, in string key) @trusted { 107 return json.object[key]; 108 } 109 110 private bool byOptionalKey(JSONValue json, in string key, bool def) { 111 import std.conv: to; 112 auto value = json.object; 113 return key in value ? value[key].boolean : def; 114 } 115 116 //std.json has no conversion to bool 117 private bool boolean(JSONValue json) @trusted { 118 import std.exception: enforce; 119 enforce!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE, 120 "JSONValue is not a boolean"); 121 return json.type == JSON_TYPE.TRUE; 122 } 123 124 private string getOptional(JSONValue json, in string key) @trusted { 125 auto aa = json.object; 126 return key in aa ? aa[key].str : ""; 127 } 128 129 private string[] getOptionalList(JSONValue json, in string key) @trusted { 130 auto aa = json.object; 131 return key in aa ? aa[key].jsonValueToStrings : []; 132 }