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 std.array;
13     import core.exception: RangeError;
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 hasTargets() {
28                 try
29                     return ("targets" in json.object) !is null;
30                 catch(JSONException ex)
31                     return false;
32             }
33 
34             if(!hasTargets) {
35                 continue;
36             }
37 
38             auto targets = json.byKey("targets").array;
39             auto info = DubInfo(targets
40                                 .map!((a) {
41                                     auto bs = a.object["buildSettings"];
42                                     return DubPackage(
43                                         bs.byKey("targetName").str,
44                                         bs.byKey("targetPath").str,
45                                         bs.getOptional("mainSourceFile"),
46                                         bs.getOptional("targetName"),
47                                         bs.byKey("dflags").jsonValueToStrings,
48                                         bs.byKey("lflags").jsonValueToStrings,
49                                         bs.byKey("importPaths").jsonValueToStrings,
50                                         bs.byKey("stringImportPaths").jsonValueToStrings,
51                                         bs.byKey("sourceFiles").jsonValueToStrings,
52                                         bs.getOptionalEnum!TargetType("targetType"),
53                                         bs.getOptionalList("versions"),
54                                         a.getOptionalList("dependencies"),
55                                         bs.getOptionalList("libs"),
56                                         true, // backwards compatibility (active)
57                                         bs.getOptionalList("preBuildCommands"),
58                                         bs.getOptionalList("postBuildCommands"),
59                                     );
60                                 })
61                                 .filter!(a => a.active)
62                                 .array);
63 
64 
65 
66             // in dub.json/dub.sdl, $PACKAGE_DIR is a variable that refers to the root
67             // of the dub package
68             void resolvePackageDir(in DubPackage dubPackage, ref string str) {
69                 str = str.replace("$PACKAGE_DIR", dubPackage.path);
70             }
71 
72             foreach(ref dubPackage; info.packages) {
73                 foreach(ref member; dubPackage.tupleof) {
74 
75                     static if(is(typeof(member) == string)) {
76                         resolvePackageDir(dubPackage, member);
77                     } else static if(is(typeof(member) == string[])) {
78                         foreach(ref elt; member)
79                             resolvePackageDir(dubPackage, elt);
80                     }
81                 }
82             }
83 
84             return info;
85         }
86     } catch(RangeError e) {
87         import std.stdio;
88         stderr.writeln("Could not parse the output of dub describe:\n", origString);
89         throw e;
90     }
91 }
92 
93 
94 private string[] jsonValueToFiles(JSONValue files) @trusted {
95     import std.array;
96 
97     return files.array.
98         filter!(a => ("type" in a && a.byKey("type").str == "source") ||
99                      ("role" in a && a.byKey("role").str == "source") ||
100                      ("type" !in a && "role" !in a)).
101         map!(a => a.byKey("path").str).
102         array;
103 }
104 
105 private string[] jsonValueToStrings(JSONValue json) @trusted {
106     import std.array;
107     return json.array.map!(a => a.str).array;
108 }
109 
110 
111 private JSONValue byKey(JSONValue json, in string key) @trusted {
112     import core.exception: RangeError;
113     try
114         return json.object[key];
115     catch(RangeError e) {
116         throw new Exception("Could not find key " ~ key);
117     }
118 }
119 
120 private bool byOptionalKey(JSONValue json, in string key, bool def) {
121     import std.conv: to;
122     auto value = json.object;
123     return key in value ? value[key].boolean : def;
124 }
125 
126 //std.json has no conversion to bool
127 private bool boolean(JSONValue json) @trusted {
128     import std.exception: enforce;
129     enforce!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE,
130                           "JSONValue is not a boolean");
131     return json.type == JSON_TYPE.TRUE;
132 }
133 
134 private string getOptional(JSONValue json, in string key) @trusted {
135     auto aa = json.object;
136     return key in aa ? aa[key].str : "";
137 }
138 
139 private T getOptionalEnum(T)(JSONValue json, in string key) @trusted {
140     auto aa = json.object;
141     return key in aa ? cast(T)aa[key].integer : T.init;
142 }
143 
144 private string[] getOptionalList(JSONValue json, in string key) @trusted {
145     auto aa = json.object;
146     return key in aa ? aa[key].jsonValueToStrings : [];
147 }