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