1 module workspaced.api;
2 
3 import core.time;
4 import dparse.lexer;
5 import painlessjson;
6 import standardpaths;
7 import std.algorithm;
8 import std.conv;
9 import std.exception;
10 import std.file;
11 import std.json;
12 import std.meta;
13 import std.path;
14 import std.regex;
15 import std.traits;
16 import std.typecons;
17 
18 ///
19 alias ImportPathProvider = string[]delegate();
20 ///
21 alias BroadcastCallback = void delegate(WorkspaceD, WorkspaceD.Instance, JSONValue);
22 ///
23 alias ComponentBindFailCallback = void delegate(WorkspaceD.Instance, ComponentFactory, Exception);
24 
25 /// Will never call this function
26 enum ignoredFunc;
27 
28 /// Component call
29 struct ComponentInfo
30 {
31 	/// Name of the component
32 	string name;
33 }
34 
35 ComponentInfo component(string name)
36 {
37 	return ComponentInfo(name);
38 }
39 
40 mixin template DefaultComponentWrapper(bool withDtor = true)
41 {
42 	@ignoredFunc
43 	{
44 		import core.thread : ThreadGroup;
45 
46 		WorkspaceD workspaced;
47 		WorkspaceD.Instance refInstance;
48 
49 		ThreadGroup _threads;
50 
51 		static if (withDtor)
52 		{
53 			~this()
54 			{
55 				shutdown();
56 			}
57 		}
58 
59 		ThreadGroup threads()
60 		{
61 			if (!_threads)
62 				synchronized (this)
63 					if (!_threads)
64 						_threads = new ThreadGroup();
65 			return _threads;
66 		}
67 
68 		WorkspaceD.Instance instance() const @property
69 		{
70 			if (refInstance)
71 				return cast() refInstance;
72 			else
73 				throw new Exception("Attempted to access instance in a global context");
74 		}
75 
76 		WorkspaceD.Instance instance(WorkspaceD.Instance instance) @property
77 		{
78 			return refInstance = instance;
79 		}
80 
81 		string[] importPaths() const @property
82 		{
83 			return instance.importPathProvider ? instance.importPathProvider() : [];
84 		}
85 
86 		string[] stringImportPaths() const @property
87 		{
88 			return instance.stringImportPathProvider ? instance.stringImportPathProvider() : [];
89 		}
90 
91 		string[] importFiles() const @property
92 		{
93 			return instance.importFilesProvider ? instance.importFilesProvider() : [];
94 		}
95 
96 		ref ImportPathProvider importPathProvider() @property
97 		{
98 			return instance.importPathProvider;
99 		}
100 
101 		ref ImportPathProvider stringImportPathProvider() @property
102 		{
103 			return instance.stringImportPathProvider;
104 		}
105 
106 		ref ImportPathProvider importFilesProvider() @property
107 		{
108 			return instance.importFilesProvider;
109 		}
110 
111 		ref Configuration config() @property
112 		{
113 			if (refInstance)
114 				return refInstance.config;
115 			else if (workspaced)
116 				return workspaced.globalConfiguration;
117 			else
118 				assert(false, "Unbound component trying to access config.");
119 		}
120 
121 		T get(T)()
122 		{
123 			if (refInstance)
124 				return refInstance.get!T;
125 			else if (workspaced)
126 				return workspaced.get!T;
127 			else
128 				assert(false, "Unbound component trying to get component " ~ T.stringof ~ ".");
129 		}
130 
131 		string cwd() @property const
132 		{
133 			return instance.cwd;
134 		}
135 
136 		override void shutdown()
137 		{
138 			if (_threads)
139 				_threads.joinAll();
140 		}
141 
142 		override void bind(WorkspaceD workspaced, WorkspaceD.Instance instance)
143 		{
144 			this.workspaced = workspaced;
145 			this.instance = instance;
146 			static if (__traits(hasMember, typeof(this).init, "load"))
147 				load();
148 		}
149 
150 		import std.conv;
151 		import std.json : JSONValue;
152 		import std.traits : isFunction, hasUDA, ParameterDefaults, Parameters, ReturnType;
153 		import painlessjson;
154 
155 		override Future!JSONValue run(string method, JSONValue[] args)
156 		{
157 			static foreach (member; __traits(derivedMembers, typeof(this)))
158 				static if (member[0] != '_' && __traits(compiles, __traits(getMember,
159 						typeof(this).init, member)) && __traits(getProtection, __traits(getMember, typeof(this).init,
160 						member)) == "public" && __traits(compiles, isFunction!(__traits(getMember,
161 						typeof(this).init, member))) && isFunction!(__traits(getMember,
162 						typeof(this).init, member)) && !hasUDA!(__traits(getMember, typeof(this).init,
163 						member), ignoredFunc) && !__traits(isTemplate, __traits(getMember,
164 						typeof(this).init, member)))
165 					if (method == member)
166 						return runMethod!member(args);
167 			throw new Exception("Method " ~ method ~ " not found.");
168 		}
169 
170 		Future!JSONValue runMethod(string method)(JSONValue[] args)
171 		{
172 			int matches;
173 			static foreach (overload; __traits(getOverloads, typeof(this), method))
174 			{
175 				if (matchesOverload!overload(args))
176 					matches++;
177 			}
178 			if (matches == 0)
179 				throw new Exception("No suitable overload found for " ~ method ~ ".");
180 			if (matches > 1)
181 				throw new Exception("Multiple overloads found for " ~ method ~ ".");
182 			static foreach (overload; __traits(getOverloads, typeof(this), method))
183 			{
184 				if (matchesOverload!overload(args))
185 					return runOverload!overload(args);
186 			}
187 			assert(false);
188 		}
189 
190 		Future!JSONValue runOverload(alias fun)(JSONValue[] args)
191 		{
192 			mixin(generateOverloadCall!fun);
193 		}
194 
195 		static string generateOverloadCall(alias fun)()
196 		{
197 			string call = "fun(";
198 			static foreach (i, T; Parameters!fun)
199 				call ~= "args[" ~ i.to!string ~ "].fromJSON!(" ~ T.stringof ~ "), ";
200 			call ~= ")";
201 			static if (is(ReturnType!fun : Future!T, T))
202 			{
203 				static if (is(T == void))
204 					string conv = "ret.finish(JSONValue(null));";
205 				else
206 					string conv = "ret.finish(v.value.toJSON);";
207 				return "auto ret = new Future!JSONValue; auto v = " ~ call
208 					~ "; v.onDone = { if (v.exception) ret.error(v.exception); else "
209 					~ conv ~ " }; return ret;";
210 			}
211 			else static if (is(ReturnType!fun == void))
212 				return call ~ "; return Future!JSONValue.fromResult(JSONValue(null));";
213 			else
214 				return "return Future!JSONValue.fromResult(" ~ call ~ ".toJSON);";
215 		}
216 	}
217 }
218 
219 bool matchesOverload(alias fun)(JSONValue[] args)
220 {
221 	if (args.length > Parameters!fun.length)
222 		return false;
223 	static foreach (i, def; ParameterDefaults!fun)
224 	{
225 		static if (is(def == void))
226 		{
227 			if (i >= args.length)
228 				return false;
229 			else if (!checkType!(Parameters!fun[i])(args[i]))
230 				return false;
231 		}
232 	}
233 	return true;
234 }
235 
236 bool checkType(T)(JSONValue value)
237 {
238 	final switch (value.type)
239 	{
240 	case JSON_TYPE.ARRAY:
241 		static if (isStaticArray!T)
242 			return T.length == value.array.length
243 				&& value.array.all!(checkType!(typeof(T.init[0])));
244 		else static if (isDynamicArray!T)
245 			return value.array.all!(checkType!(typeof(T.init[0])));
246 		else static if (is(T : Tuple!Args, Args...))
247 		{
248 			if (value.array.length != Args.length)
249 				return false;
250 			static foreach (i, Arg; Args)
251 				if (!checkType!Arg(value.array[i]))
252 					return false;
253 			return true;
254 		}
255 		else
256 			return false;
257 	case JSON_TYPE.FALSE:
258 	case JSON_TYPE.TRUE:
259 		return is(T : bool);
260 	case JSON_TYPE.FLOAT:
261 		return isNumeric!T;
262 	case JSON_TYPE.INTEGER:
263 	case JSON_TYPE.UINTEGER:
264 		return isIntegral!T;
265 	case JSON_TYPE.NULL:
266 		static if (is(T == class) || isArray!T || isPointer!T || is(T : Nullable!U, U))
267 			return true;
268 		else
269 			return false;
270 	case JSON_TYPE.OBJECT:
271 		return is(T == class) || is(T == struct);
272 	case JSON_TYPE.STRING:
273 		return isSomeString!T;
274 	}
275 }
276 
277 interface ComponentWrapper
278 {
279 	void bind(WorkspaceD workspaced, WorkspaceD.Instance instance);
280 	Future!JSONValue run(string method, JSONValue[] args);
281 	void shutdown();
282 }
283 
284 interface ComponentFactory
285 {
286 	ComponentWrapper create(WorkspaceD workspaced, WorkspaceD.Instance instance, out Exception error);
287 	ComponentInfo info() @property;
288 }
289 
290 struct ComponentFactoryInstance
291 {
292 	ComponentFactory factory;
293 	bool autoRegister;
294 	alias factory this;
295 }
296 
297 struct ComponentWrapperInstance
298 {
299 	ComponentWrapper wrapper;
300 	ComponentInfo info;
301 }
302 
303 struct Configuration
304 {
305 	/// JSON containing base configuration formatted as {[component]:{key:value pairs}}
306 	JSONValue base;
307 
308 	bool get(string component, string key, out JSONValue val)
309 	{
310 		if (base.type != JSON_TYPE.OBJECT)
311 		{
312 			JSONValue[string] tmp;
313 			base = JSONValue(tmp);
314 		}
315 		auto com = component in base.object;
316 		if (!com)
317 			return false;
318 		auto v = key in *com;
319 		if (!v)
320 			return false;
321 		val = *v;
322 		return true;
323 	}
324 
325 	T get(T)(string component, string key, T defaultValue = T.init)
326 	{
327 		JSONValue ret;
328 		if (!get(component, key, ret))
329 			return defaultValue;
330 		return ret.fromJSON!T;
331 	}
332 
333 	bool set(T)(string component, string key, T value)
334 	{
335 		if (base.type != JSON_TYPE.OBJECT)
336 		{
337 			JSONValue[string] tmp;
338 			base = JSONValue(tmp);
339 		}
340 		auto com = component in base.object;
341 		if (!com)
342 		{
343 			JSONValue[string] val;
344 			val[key] = value.toJSON;
345 			base.object[component] = JSONValue(val);
346 		}
347 		else
348 		{
349 			com.object[key] = value.toJSON;
350 		}
351 		return true;
352 	}
353 
354 	/// Same as init but might make nicer code.
355 	static immutable Configuration none = Configuration.init;
356 
357 	/// Loads unset keys from global, keeps existing keys
358 	void loadBase(Configuration global)
359 	{
360 		if (global.base.type != JSON_TYPE.OBJECT)
361 			return;
362 
363 		if (base.type != JSON_TYPE.OBJECT)
364 			base = global.base.dupJson;
365 		else
366 		{
367 			foreach (component, config; global.base.object)
368 			{
369 				auto existing = component in base.object;
370 				if (!existing || config.type != JSON_TYPE.OBJECT)
371 					base.object[component] = config.dupJson;
372 				else
373 				{
374 					foreach (key, value; config.object)
375 					{
376 						auto existingValue = key in *existing;
377 						if (!existingValue)
378 							(*existing)[key] = value.dupJson;
379 					}
380 				}
381 			}
382 		}
383 	}
384 }
385 
386 private JSONValue dupJson(JSONValue v)
387 {
388 	switch (v.type)
389 	{
390 	case JSON_TYPE.OBJECT:
391 		return JSONValue(v.object.dup);
392 	case JSON_TYPE.ARRAY:
393 		return JSONValue(v.array.dup);
394 	default:
395 		return v;
396 	}
397 }
398 
399 /// WorkspaceD instance holding plugins.
400 class WorkspaceD
401 {
402 	static class Instance
403 	{
404 		string cwd;
405 		ComponentWrapperInstance[] instanceComponents;
406 		Configuration config;
407 
408 		string[] importPaths() const @property
409 		{
410 			return importPathProvider ? importPathProvider() : [];
411 		}
412 
413 		string[] stringImportPaths() const @property
414 		{
415 			return stringImportPathProvider ? stringImportPathProvider() : [];
416 		}
417 
418 		string[] importFiles() const @property
419 		{
420 			return importFilesProvider ? importFilesProvider() : [];
421 		}
422 
423 		void shutdown()
424 		{
425 			foreach (ref com; instanceComponents)
426 				com.wrapper.shutdown();
427 			instanceComponents = null;
428 		}
429 
430 		ImportPathProvider importPathProvider;
431 		ImportPathProvider stringImportPathProvider;
432 		ImportPathProvider importFilesProvider;
433 
434 		Future!JSONValue run(WorkspaceD workspaced, string component, string method, JSONValue[] args)
435 		{
436 			foreach (ref com; instanceComponents)
437 				if (com.info.name == component)
438 					return com.wrapper.run(method, args);
439 			throw new Exception("Component '" ~ component ~ "' not found");
440 		}
441 
442 		T get(T)()
443 		{
444 			auto name = getUDAs!(T, ComponentInfo)[0].name;
445 			foreach (com; instanceComponents)
446 				if (com.info.name == name)
447 					return cast(T) com.wrapper;
448 			throw new Exception(
449 					"Attempted to get unknown instance component " ~ T.stringof ~ " in instance cwd:" ~ cwd);
450 		}
451 
452 		bool has(T)()
453 		{
454 			auto name = getUDAs!(T, ComponentInfo)[0].name;
455 			foreach (com; instanceComponents)
456 				if (com.info.name == name)
457 					return true;
458 			return false;
459 		}
460 
461 		/// Loads a registered component which didn't have auto register on just for this instance.
462 		/// Returns: false instead of using the onBindFail callback on failure.
463 		/// Throws: Exception if component was not regsitered in workspaced.
464 		bool attach(T)(WorkspaceD workspaced)
465 		{
466 			string name = getUDAs!(T, ComponentInfo)[0].name;
467 			foreach (factory; workspaced.components)
468 			{
469 				if (factory.info.name == name)
470 				{
471 					auto inst = factory.create(workspaced, this);
472 					if (inst)
473 					{
474 						instanceComponents ~= ComponentWrapperInstance(inst, info);
475 						return true;
476 					}
477 					else
478 						return false;
479 				}
480 			}
481 			throw new Exception("Component not found");
482 		}
483 	}
484 
485 	BroadcastCallback onBroadcast;
486 	ComponentBindFailCallback onBindFail;
487 
488 	Instance[] instances;
489 	/// Base global configuration for new instances, does not modify existing ones.
490 	Configuration globalConfiguration;
491 	ComponentWrapperInstance[] globalComponents;
492 	ComponentFactoryInstance[] components;
493 	StringCache stringCache;
494 
495 	this()
496 	{
497 		stringCache = StringCache(StringCache.defaultBucketCount * 4);
498 	}
499 
500 	void shutdown()
501 	{
502 		foreach (ref instance; instances)
503 			instance.shutdown();
504 		instances = null;
505 		foreach (ref com; globalComponents)
506 			com.wrapper.shutdown();
507 		globalComponents = null;
508 		components = null;
509 	}
510 
511 	void broadcast(WorkspaceD.Instance instance, JSONValue value)
512 	{
513 		if (onBroadcast)
514 			onBroadcast(this, instance, value);
515 	}
516 
517 	Instance getInstance(string cwd) nothrow
518 	{
519 		cwd = buildNormalizedPath(cwd);
520 		foreach (instance; instances)
521 			if (instance.cwd == cwd)
522 				return instance;
523 		return null;
524 	}
525 
526 	T get(T)()
527 	{
528 		auto name = getUDAs!(T, ComponentInfo)[0].name;
529 		foreach (com; globalComponents)
530 			if (com.info.name == name)
531 				return cast(T) com.wrapper;
532 		throw new Exception("Attempted to get unknown global component " ~ T.stringof);
533 	}
534 
535 	bool has(T)()
536 	{
537 		auto name = getUDAs!(T, ComponentInfo)[0].name;
538 		foreach (com; globalComponents)
539 			if (com.info.name == name)
540 				return true;
541 		return false;
542 	}
543 
544 	T get(T)(string cwd)
545 	{
546 		if (!cwd.length)
547 			return this.get!T;
548 		auto inst = getInstance(cwd);
549 		if (inst is null)
550 			throw new Exception("cwd '" ~ cwd ~ "' not found");
551 		return inst.get!T;
552 	}
553 
554 	bool has(T)(string cwd)
555 	{
556 		auto inst = getInstance(cwd);
557 		if (inst is null)
558 			return false;
559 		return inst.has!T;
560 	}
561 
562 	Future!JSONValue run(string cwd, string component, string method, JSONValue[] args)
563 	{
564 		auto instance = getInstance(cwd);
565 		if (instance is null)
566 			throw new Exception("cwd '" ~ cwd ~ "' not found");
567 		return instance.run(this, component, method, args);
568 	}
569 
570 	Future!JSONValue run(string component, string method, JSONValue[] args)
571 	{
572 		foreach (ref com; globalComponents)
573 			if (com.info.name == component)
574 				return com.wrapper.run(method, args);
575 		throw new Exception("Global component '" ~ component ~ "' not found");
576 	}
577 
578 	ComponentFactory register(T)(bool autoRegister = true)
579 	{
580 		ComponentFactory factory;
581 		static foreach (attr; __traits(getAttributes, T))
582 			static if (is(attr == class) && is(attr : ComponentFactory))
583 				factory = new attr;
584 		if (factory is null)
585 			factory = new DefaultComponentFactory!T;
586 		components ~= ComponentFactoryInstance(factory, autoRegister);
587 		auto info = factory.info;
588 		Exception error;
589 		auto glob = factory.create(this, null, error);
590 		if (glob)
591 			globalComponents ~= ComponentWrapperInstance(glob, info);
592 		if (autoRegister)
593 			foreach (ref instance; instances)
594 			{
595 				auto inst = factory.create(this, instance, error);
596 				if (inst)
597 					instance.instanceComponents ~= ComponentWrapperInstance(inst, info);
598 				else if (onBindFail)
599 					onBindFail(instance, factory, error);
600 			}
601 		static if (__traits(compiles, T.registered(this)))
602 			T.registered(this);
603 		else static if (__traits(compiles, T.registered()))
604 			T.registered();
605 		return factory;
606 	}
607 
608 	/// Creates a new workspace with the given cwd with optional config overrides and preload components for non-autoRegister components.
609 	/// Throws: Exception if normalized cwd already exists as instance.
610 	Instance addInstance(string cwd,
611 			Configuration configOverrides = Configuration.none, string[] preloadComponents = [])
612 	{
613 		cwd = buildNormalizedPath(cwd);
614 		if (instances.canFind!(a => a.cwd == cwd))
615 			throw new Exception("Instance with cwd '" ~ cwd ~ "' already exists!");
616 		auto inst = new Instance();
617 		inst.cwd = cwd;
618 		configOverrides.loadBase(globalConfiguration);
619 		inst.config = configOverrides;
620 		instances ~= inst;
621 		foreach (name; preloadComponents)
622 		{
623 			foreach (factory; components)
624 			{
625 				if (!factory.autoRegister && factory.info.name == name)
626 				{
627 					Exception error;
628 					auto wrap = factory.create(this, inst, error);
629 					if (wrap)
630 						inst.instanceComponents ~= ComponentWrapperInstance(wrap, factory.info);
631 					else if (onBindFail)
632 						onBindFail(inst, factory, error);
633 					break;
634 				}
635 			}
636 		}
637 		foreach (factory; components)
638 		{
639 			if (factory.autoRegister)
640 			{
641 				Exception error;
642 				auto wrap = factory.create(this, inst, error);
643 				if (wrap)
644 					inst.instanceComponents ~= ComponentWrapperInstance(wrap, factory.info);
645 				else if (onBindFail)
646 					onBindFail(inst, factory, error);
647 			}
648 		}
649 		return inst;
650 	}
651 
652 	bool removeInstance(string cwd)
653 	{
654 		cwd = buildNormalizedPath(cwd);
655 		foreach (i, instance; instances)
656 			if (instance.cwd == cwd)
657 			{
658 				foreach (com; instance.instanceComponents)
659 					destroy(com.wrapper);
660 				destroy(instance);
661 				instances = instances.remove(i);
662 				return true;
663 			}
664 		return false;
665 	}
666 
667 	deprecated("Use overload taking an out Exception error or attachSilent instead") bool attach(
668 			Instance instance, string component)
669 	{
670 		return attachSilent(instance, component);
671 	}
672 
673 	bool attachSilent(Instance instance, string component)
674 	{
675 		Exception error;
676 		return attach(instance, component, error);
677 	}
678 
679 	bool attach(Instance instance, string component, out Exception error)
680 	{
681 		foreach (factory; components)
682 		{
683 			if (factory.info.name == component)
684 			{
685 				auto wrap = factory.create(this, instance, error);
686 				if (wrap)
687 				{
688 					instance.instanceComponents ~= ComponentWrapperInstance(wrap, factory.info);
689 					return true;
690 				}
691 				else
692 					return false;
693 			}
694 		}
695 		return false;
696 	}
697 }
698 
699 class DefaultComponentFactory(T : ComponentWrapper) : ComponentFactory
700 {
701 	ComponentWrapper create(WorkspaceD workspaced, WorkspaceD.Instance instance, out Exception error)
702 	{
703 		auto wrapper = new T();
704 		try
705 		{
706 			wrapper.bind(workspaced, instance);
707 			return wrapper;
708 		}
709 		catch (Exception e)
710 		{
711 			error = e;
712 			return null;
713 		}
714 	}
715 
716 	ComponentInfo info() @property
717 	{
718 		alias udas = getUDAs!(T, ComponentInfo);
719 		static assert(udas.length == 1, "Can't construct default component factory for "
720 				~ T.stringof ~ ", expected exactly 1 ComponentInfo instance attached to the type");
721 		return udas[0];
722 	}
723 }
724 
725 /// Describes what to insert/replace/delete to do something
726 struct CodeReplacement
727 {
728 	/// Range what to replace. If both indices are the same its inserting.
729 	size_t[2] range;
730 	/// Content to replace it with. Empty means remove.
731 	string content;
732 
733 	/// Applies this edit to a string.
734 	string apply(string code)
735 	{
736 		size_t min = range[0];
737 		size_t max = range[1];
738 		if (min > max)
739 		{
740 			min = range[1];
741 			max = range[0];
742 		}
743 		if (min >= code.length)
744 			return code ~ content;
745 		if (max >= code.length)
746 			return code[0 .. min] ~ content;
747 		return code[0 .. min] ~ content ~ code[max .. $];
748 	}
749 }
750 
751 /// Code replacements mapped to a file
752 struct FileChanges
753 {
754 	/// File path to change.
755 	string file;
756 	/// Replacements to apply.
757 	CodeReplacement[] replacements;
758 }
759 
760 package bool getConfigPath(string file, ref string retPath)
761 {
762 	foreach (dir; standardPaths(StandardPath.config, "workspace-d"))
763 	{
764 		auto path = buildPath(dir, file);
765 		if (path.exists)
766 		{
767 			retPath = path;
768 			return true;
769 		}
770 	}
771 	return false;
772 }
773 
774 enum verRegex = ctRegex!`(\d+)\.(\d+)\.(\d+)`;
775 bool checkVersion(string ver, int[3] target)
776 {
777 	auto match = ver.matchFirst(verRegex);
778 	if (!match)
779 		return false;
780 	int major = match[1].to!int;
781 	int minor = match[2].to!int;
782 	int patch = match[3].to!int;
783 	if (major > target[0])
784 		return true;
785 	if (major == target[0] && minor > target[1])
786 		return true;
787 	if (major == target[0] && minor == target[1] && patch >= target[2])
788 		return true;
789 	return false;
790 }
791 
792 package string getVersionAndFixPath(ref string execPath)
793 {
794 	import std.process;
795 
796 	try
797 	{
798 		return execute([execPath, "--version"]).output;
799 	}
800 	catch (ProcessException e)
801 	{
802 		auto newPath = buildPath(thisExePath.dirName, execPath.baseName);
803 		if (exists(newPath))
804 		{
805 			execPath = newPath;
806 			return execute([execPath, "--version"]).output;
807 		}
808 		throw e;
809 	}
810 }
811 
812 class Future(T)
813 {
814 	static if (!is(T == void))
815 		T value;
816 	Throwable exception;
817 	bool has;
818 	void delegate() _onDone;
819 
820 	/// Sets the onDone callback if no value has been set yet or calls immediately if the value has already been set or was set during setting the callback.
821 	/// Crashes with an assert error if attempting to override an existing callback (i.e. calling this function on the same object twice).
822 	void onDone(void delegate() callback) @property
823 	{
824 		assert(!_onDone);
825 		if (has)
826 			callback();
827 		else
828 		{
829 			bool called;
830 			_onDone = { called = true; callback(); };
831 			if (has && !called)
832 				callback();
833 		}
834 	}
835 
836 	static if (is(T == void))
837 		static Future!void finished()
838 		{
839 			auto ret = new Future!void;
840 			ret.has = true;
841 			return ret;
842 		}
843 	else
844 		static Future!T fromResult(T value)
845 		{
846 			auto ret = new Future!T;
847 			ret.value = value;
848 			ret.has = true;
849 			return ret;
850 		}
851 
852 	static Future!T async(T delegate() cb)
853 	{
854 		import core.thread : Thread;
855 
856 		auto ret = new Future!T;
857 		new Thread({
858 			try
859 			{
860 				static if (is(T == void))
861 				{
862 					cb();
863 					ret.finish();
864 				}
865 				else
866 					ret.finish(cb());
867 			}
868 			catch (Throwable t)
869 			{
870 				ret.error(t);
871 			}
872 		}).start();
873 		return ret;
874 	}
875 
876 	static Future!T fromError(T)(Throwable error)
877 	{
878 		auto ret = new Future!T;
879 		ret.error = error;
880 		ret.has = true;
881 		return ret;
882 	}
883 
884 	static if (is(T == void))
885 		void finish()
886 		{
887 			assert(!has);
888 			has = true;
889 			if (_onDone)
890 				_onDone();
891 		}
892 	else
893 		void finish(T value)
894 		{
895 			assert(!has);
896 			this.value = value;
897 			has = true;
898 			if (_onDone)
899 				_onDone();
900 		}
901 
902 	void error(Throwable t)
903 	{
904 		assert(!has);
905 		exception = t;
906 		has = true;
907 		if (_onDone)
908 			_onDone();
909 	}
910 
911 	/// Waits for the result of this future using Thread.sleep
912 	T getBlocking(alias sleepDur = 1.msecs)()
913 	{
914 		import core.thread : Thread;
915 
916 		while (!has)
917 			Thread.sleep(sleepDur);
918 		if (exception)
919 			throw exception;
920 		static if (!is(T == void))
921 			return value;
922 	}
923 
924 	/// Waits for the result of this future using Fiber.yield
925 	T getYield()
926 	{
927 		import core.thread : Fiber;
928 
929 		while (!has)
930 			Fiber.yield();
931 		if (exception)
932 			throw exception;
933 		static if (!is(T == void))
934 			return value;
935 	}
936 }
937 
938 version (unittest)
939 {
940 	struct TestingWorkspace
941 	{
942 		string directory;
943 
944 		@disable this(this);
945 
946 		this(string path)
947 		{
948 			if (path.exists)
949 				throw new Exception("Path already exists");
950 			directory = path;
951 			mkdir(path);
952 		}
953 
954 		~this()
955 		{
956 			rmdirRecurse(directory);
957 		}
958 
959 		string getPath(string path)
960 		{
961 			return buildPath(directory, path);
962 		}
963 
964 		void createDir(string dir)
965 		{
966 			mkdirRecurse(getPath(dir));
967 		}
968 
969 		void writeFile(string path, string content)
970 		{
971 			write(getPath(path), content);
972 		}
973 	}
974 
975 	TestingWorkspace makeTemporaryTestingWorkspace()
976 	{
977 		import std.random;
978 
979 		return TestingWorkspace(buildPath(tempDir, "workspace-d-test-" ~ uniform(0,
980 				int.max).to!string(36)));
981 	}
982 }