1 module workspaced.api;
2 
3 import std.conv;
4 import std.json;
5 import std.file;
6 import std.path;
7 import std.regex;
8 import core.time;
9 import painlessjson;
10 import standardpaths;
11 
12 ///
13 alias AsyncCallback = void delegate(Throwable, JSONValue);
14 
15 /// Will get called asynchronously (Must prepend AsyncCallback as argument)
16 enum async = 2603248026;
17 
18 /// Will get called for loading components
19 enum load = 2603248027;
20 
21 /// Will get called for unloading components
22 enum unload = 2603248028;
23 
24 /// Will call this function in any case (cmd: component)
25 enum any = 2603248029;
26 
27 /// Component call
28 struct component
29 {
30 	/// Name of the component
31 	string name;
32 }
33 
34 /// Will get called when some argument matches
35 struct Arguments
36 {
37 	/// Arguments to match
38 	Argument[] arguments;
39 }
40 
41 private struct Argument
42 {
43 	/// Key in JSON object node at root level to match
44 	string key;
45 	/// Value in JSON object node at root level to match
46 	JSONValue value;
47 }
48 
49 private template ArgumentPair(size_t i)
50 {
51 	static if (i > 0)
52 		enum ArgumentPair = "ret.arguments[" ~ (i / 2 - 1)
53 				.to!string ~ "] = Argument(args[" ~ (i - 2).to!string ~ "], args[" ~ (i - 1)
54 				.to!string ~ "].toJSON);" ~ ArgumentPair!(i - 2);
55 	else
56 					enum ArgumentPair = "";
57 }
58 
59 package Arguments arguments(T...)(T args)
60 {
61 	if (args.length < 2)
62 		return Arguments.init;
63 	Arguments ret;
64 	ret.arguments.length = args.length / 2;
65 	mixin(ArgumentPair!(args.length));
66 	return ret;
67 }
68 
69 unittest
70 {
71 	Arguments args = arguments("foo", 5, "bar", "str");
72 	assert(args.arguments[0].key == "foo");
73 	assert(args.arguments[0].value.integer == 5);
74 	assert(args.arguments[1].key == "bar");
75 	assert(args.arguments[1].value.str == "str");
76 }
77 
78 package bool getConfigPath(string file, ref string retPath)
79 {
80 	foreach (dir; standardPaths(StandardPath.config, "workspace-d"))
81 	{
82 		auto path = buildPath(dir, file);
83 		if (path.exists)
84 		{
85 			retPath = path;
86 			return true;
87 		}
88 	}
89 	return false;
90 }
91 
92 alias ImportPathProvider = string[]function();
93 
94 private string[] noImports()
95 {
96 	return [];
97 }
98 
99 ImportPathProvider importPathProvider = &noImports, stringImportPathProvider = &noImports;
100 
101 enum verRegex = ctRegex!`(\d+)\.(\d+)\.(\d+)`;
102 bool checkVersion(string ver, int[3] target)
103 {
104 	auto match = ver.matchFirst(verRegex);
105 	assert(match);
106 	int major = match[1].to!int;
107 	int minor = match[2].to!int;
108 	int patch = match[3].to!int;
109 	if (major > target[0])
110 		return true;
111 	if (major == target[0] && minor >= target[1])
112 		return true;
113 	if (major == target[0] && minor == target[1] && patch >= target[2])
114 		return true;
115 	return false;
116 }
117 
118 alias BroadcastCallback = void function(JSONValue);
119 /// Broadcast callback which might get called by commands. For example when a component is outdated. Will be called in caller thread of function / while function executes.
120 BroadcastCallback broadcastCallback;
121 /// Must get called in caller thread
122 package void broadcast(JSONValue value)
123 {
124 	if (broadcastCallback)
125 		broadcastCallback(value);
126 	else
127 		throw new Exception("broadcastCallback not set!");
128 }
129 
130 package string getVersionAndFixPath(ref string execPath)
131 {
132 	import std.process;
133 
134 	try
135 	{
136 		return execute([execPath, "--version"]).output;
137 	}
138 	catch (ProcessException e)
139 	{
140 		auto newPath = buildPath(thisExePath.dirName, execPath.baseName);
141 		if (exists(newPath))
142 		{
143 			execPath = newPath;
144 			return execute([execPath, "--version"]).output;
145 		}
146 		throw e;
147 	}
148 }
149 
150 /// Calls an asynchronous function and blocks until it returns using Thread.sleep
151 JSONValue syncBlocking(alias fn, alias sleepDur = 1.msecs, Args...)(Args args)
152 {
153 	import core.thread;
154 
155 	Throwable ex;
156 	JSONValue ret;
157 	bool done = false;
158 	AsyncCallback cb = (err, data) { ex = err; ret = data; done = true; };
159 	fn(cb, args);
160 	while (!done)
161 		Thread.sleep(sleepDur);
162 	if (ex)
163 		throw ex;
164 	return ret;
165 }
166 
167 /// Calls an asynchronous function and blocks until it returns using Fiber.yield
168 JSONValue syncYield(alias fn, Args...)(Args args)
169 {
170 	import core.thread;
171 
172 	Throwable ex;
173 	JSONValue ret;
174 	bool done = false;
175 	AsyncCallback cb = (err, data) { ex = err; ret = data; done = true; };
176 	fn(cb, args);
177 	while (!done)
178 		Fiber.yield;
179 	if (ex)
180 		throw ex;
181 	return ret;
182 }