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 }