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