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