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 
56 private string[] jsonValueToFiles(JSONValue files) @trusted {
57     return files.array.
58         filter!(a => a.byKey("type").str == "source").
59         map!(a => a.byKey("path").str).
60         array;
61 }
62 
63 private string[] jsonValueToStrings(JSONValue json) @trusted {
64     return json.array.map!(a => a.str).array;
65 }
66 
67 
68 private auto byKey(JSONValue json, in string key) @trusted {
69     return json.object[key];
70 }
71 
72 private auto byOptionalKey(JSONValue json, in string key, bool def) {
73     import std.conv: to;
74     auto value = json.object;
75     return key in value ? value[key].boolean : def;
76 }
77 
78 //std.json has no conversion to bool
79 private bool boolean(JSONValue json) @trusted {
80     import std.exception: enforce;
81     enforce!JSONException(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE,
82                           "JSONValue is not a boolean");
83     return json.type == JSON_TYPE.TRUE;
84 }
85 
86 private string getOptional(JSONValue json, in string key) @trusted {
87     auto aa = json.object;
88     return key in aa ? aa[key].str : "";
89 }
90 
91 private string[] getOptionalList(JSONValue json, in string key) @trusted {
92     auto aa = json.object;
93     return key in aa ? aa[key].jsonValueToStrings : [];
94 }
95 
96 
97 DubInfo getDubInfo(in bool verbose) {
98     if(verbose)
99         writeln("Running dub describe");
100 
101     immutable args = ["dub", "describe", "-c", "unittest"];
102     immutable res = execute(args);
103     enforce(res.status == 0, text("Could not execute ", args.join(" "), ":\n", res.output));
104     return getDubInfo(res.output.find("{"));
105 }
106 
107 bool isDubProject() {
108     import std.file;
109     return "dub.sdl".exists || "dub.json".exists || "package.json".exists;
110 }
111 
112 
113 // set import paths from dub information
114 void dubify(ref Options options) {
115     if(!isDubProject) return;
116     auto dubInfo = getDubInfo(options.verbose);
117     options.includes = dubInfo.packages.
118         map!(a => a.importPaths.map!(b => buildPath(a.path, b)).array).
119         reduce!((a, b) => a ~ b).array;
120     options.files = dubInfo.packages[0].files;
121 }