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