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