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 }