1 module unit_threaded.dub;
2 
3 import unit_threaded.runtime: Options;
4 import std.json: JSONValue;
5 
6 
7 struct DubPackage {
8     string name;
9     string path;
10     string mainSourceFile;
11     string targetFileName;
12     string[] flags;
13     string[] importPaths;
14     string[] stringImportPaths;
15     string[] files;
16     string targetType;
17     string[] versions;
18     string[] dependencies;
19     string[] libs;
20     bool active;
21 }
22 
23 struct DubInfo {
24     DubPackage[] packages;
25 }
26 
27 DubInfo getDubInfo(string jsonString) @trusted {
28     import std.json: parseJSON;
29     import std.algorithm: map, filter;
30     import std.array: array;
31 
32     auto json = parseJSON(jsonString);
33     auto packages = json.byKey("packages").array;
34     return DubInfo(packages.
35                    map!(a => DubPackage(a.byKey("name").str,
36                                         a.byKey("path").str,
37                                         a.getOptional("mainSourceFile"),
38                                         a.getOptional("targetFileName"),
39                                         a.byKey("dflags").jsonValueToStrings,
40                                         a.byKey("importPaths").jsonValueToStrings,
41                                         a.byKey("stringImportPaths").jsonValueToStrings,
42                                         a.byKey("files").jsonValueToFiles,
43                                         a.getOptional("targetType"),
44                                         a.getOptionalList("versions"),
45                                         a.getOptionalList("dependencies"),
46                                         a.getOptionalList("libs"),
47                                         a.byOptionalKey("active", true), //true for backwards compatibility
48                             )).
49                    filter!(a => a.active).
50                    array);
51 }
52 
53 private string[] jsonValueToFiles(JSONValue files) @trusted {
54     import std.algorithm: map, filter;
55     import std.array: array;
56 
57     return files.array.
58         filter!(a => ("type" in a && a.byKey("type").str == "source") ||
59                      ("role" in a && a.byKey("role").str == "source") ||
60                      ("type" !in a && "role" !in a)).
61         map!(a => a.byKey("path").str).
62         array;
63 }
64 
65 private string[] jsonValueToStrings(JSONValue json) @trusted {
66     import std.algorithm: map, filter;
67     import std.array: array;
68 
69     return json.array.map!(a => a.str).array;
70 }
71 
72 
73 private auto byKey(JSONValue json, in string key) @trusted {
74     return json.object[key];
75 }
76 
77 private auto byOptionalKey(JSONValue json, in string key, bool def) {
78     import std.conv: to;
79     auto value = json.object;
80     return key in value ? value[key].boolean : def;
81 }
82 
83 //std.json has no conversion to bool
84 private bool boolean(JSONValue json) @trusted {
85     import std.exception: enforce;
86     import std.json: JSONException, JSON_TYPE;
87     enforce!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE,
88                           "JSONValue is not a boolean");
89     return json.type == JSON_TYPE.TRUE;
90 }
91 
92 private string getOptional(JSONValue json, in string key) @trusted {
93     auto aa = json.object;
94     return key in aa ? aa[key].str : "";
95 }
96 
97 private string[] getOptionalList(JSONValue json, in string key) @trusted {
98     auto aa = json.object;
99     return key in aa ? aa[key].jsonValueToStrings : [];
100 }
101 
102 
103 DubInfo getDubInfo(in bool verbose) {
104     import core.exception: RangeError;
105     import std.conv: text;
106     import std.algorithm: find;
107     import std.stdio: writeln;
108     import std.exception: enforce;
109     import std.process: execute;
110     import std.array: join;
111 
112     if(verbose)
113         writeln("Running dub describe");
114 
115     immutable args = ["dub", "describe", "-c", "unittest"];
116     immutable res = execute(args);
117     enforce(res.status == 0, text("Could not execute ", args.join(" "), ":\n", res.output));
118     try {
119         return getDubInfo(res.output.find("{"));
120     } catch(RangeError e) {
121         throw new Exception(text("Could not parse the output of dub describe:\n", res.output, "\n", e.toString));
122     }
123 }
124 
125 bool isDubProject() {
126     import std.file;
127     return "dub.sdl".exists || "dub.json".exists || "package.json".exists;
128 }
129 
130 
131 // set import paths from dub information
132 void dubify(ref Options options) {
133 
134     import std.path: buildPath;
135     import std.algorithm: map, reduce;
136     import std.array: array;
137 
138     if(!isDubProject) return;
139 
140     auto dubInfo = getDubInfo(options.verbose);
141     options.includes = dubInfo.packages.
142         map!(a => a.importPaths.map!(b => buildPath(a.path, b)).array).
143         reduce!((a, b) => a ~ b).array;
144     options.files = dubInfo.packages[0].files;
145 }