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