1 /** 2 Aedi, a dependency injection library. 3 4 Aedi is a dependency injection library. It does provide a set of containers that do 5 IoC, and an interface to configure application components (structs, objects, etc.) 6 7 Aim: 8 9 The aim of library is to provide a dependency injection solution that is 10 feature rich, easy to use, easy to learn, and easy to extend up to your needs. 11 12 At certian point of time the manufacturer decided that their car factory should provide cars with different engines, 13 electric/diesel/gasoline based on user preference. Therefore the application itself needed to be reconfigured to 14 satisfy user needs. 15 16 The workflow needed to implement in application in order to allow 3 different configurations is 17 shown below and consists of following steps: 18 $(OL 19 $(LI read profile argument from command line or environment ) 20 $(LI switch on it, and register diesel/gasoline/electric engine by Engine interface depending on selected profile ) 21 ) 22 23 Aedi does provide basic containers singleton and prototype, which can be combined into some- 24 thing more complex. Though, on top of their behavior additional one can be built, such as a container 25 that can be enabled/disabled, or a container that adds observer pattern. Out of the box framework does provide 26 following decorating containers: 27 28 $(UL 29 $(LI aliasing - provides aliasing functions) 30 $(LI gc rigistered - registers instantiated components with gc, when other that gc allocator are used) 31 $(LI subscribable - provides observable pattern on container events) 32 $(LI typed - serves components based on their implemented interfaces) 33 $(LI proxy - serves component proxies instead of original ones) 34 $(LI deffered - provides deffered construction of components) 35 $(LI describing - allows to register description for components that can be used later for help information) 36 ) 37 38 Following snippet of code shows all decorating containers in use except of proxy one. 39 ------------------ 40 auto decorated() { 41 auto gasoline = singleton.typed.switchable.enabled(false); 42 auto electric = singleton.typed.switchable.enabled(false); 43 auto diesel = singleton.typed.switchable.enabled(false); 44 45 auto cont = aggregate( 46 singleton, "singleton", 47 prototype, "prototype", 48 values, "parameters", 49 gasoline, "gasoline", 50 electric, "electric", 51 diesel, "diesel" 52 ).aliasing.gcRegistered.deffered.describing("Car factory", "Car factory, please see available contents of our factory"); 53 54 return cont 55 .subscribable 56 .subscribe( 57 ContainerInstantiationEventType.pre, 58 { 59 cont.locate!Switchable(cont.locate!string("profile")).enabled = true; 60 } 61 ); 62 } 63 ------------------ 64 65 The profile based container is assembled from 3 typed and switchable containers joined together into a subscribable 66 composite container with gc component registration, construction of deffered components and describing capabilities. 67 When the application is booted up, the code from $(D_INLINECODE main(string[] args)) loads into container 68 "profile" argument. Afterwards components are registered into container, and for each profile, 69 the right engine is registered in respective gasoline, electric, diesel containers. Once this 70 is finished, the container is instantiated using $(D_INLINECODE intantiate) method. During instantiation phase, 71 subscribable composite container fires an pre-instantiation event on which, a delegate is attached, that 72 checks for "profile" argument, and enables the container identified by value in profile container. 73 Afterwards construction of components proceeds. When car is constructed typed container jumps in and 74 provides an implenentation of $(D_INLINECODE Engine) for car depending on enabled container. When 75 construction arrives at a Tire, a circular dependency is detected, and construction of a component is 76 deffered at later stage in order to break dependency chain. Once component is constructed, it is registered 77 with global gc instance in order to avoid destruction of gc managed components while they are still referenced 78 by non-gc-managed components. Once instantiation is finished, car is fetched from container and displayed in console. 79 80 Each of decorating container shown in example above will be explained in detail below. 81 82 Aliasing: 83 84 The aliasing container decorator adds ability to add aliases to existing components in container. Therefore 85 a "foo" component can have multiple aliases "moo", "boo" etc. Each alias will resolve to same component in container. 86 Such feature is useful when an existing component in container for compatibility reasons should have another identity as 87 well. To use aliasing container simply wrap any container in $(D_INLINECODE aliasing) method which will decorate existing one. 88 UFCS syntax is desired for fluent like style. 89 90 ----------------- 91 auto cont = aggregate( 92 singleton, "singleton", 93 prototype, "prototype", 94 values, "parameters", 95 gasoline, "gasoline", 96 electric, "electric", 97 diesel, "diesel" 98 ).aliasing; 99 ----------------- 100 101 GcRegistered: 102 103 GC registered decorating container registers any created component with global gc available in D. Since any component can have customized 104 memory allocation strategy through std.experimental.allocator, were one of strategy is to use garbage collector, and each component 105 can have dependencies to other components, in some cases, gc on collection cycle could deallocate a component (which technically isn't 106 referenced by anyone) even if it is still referenced by other component of which gc is not aware of. GC registered container aims to register 107 all public components provided by underlying container into GC in order to avoid the case from above. To use it, just suffix any container with $(D_INLINECODE gcRegistered). 108 109 ----------------- 110 auto cont = aggregate( 111 singleton, "singleton", 112 prototype, "prototype", 113 values, "parameters", 114 gasoline, "gasoline", 115 electric, "electric", 116 diesel, "diesel" 117 ).aliasing.gcRegistered; 118 ----------------- 119 120 Subscribable: 121 122 Subscribable container offers a list of events to which a listener can subscribe and perform operations on. Currently only two events are provided: 123 $(OL 124 $(LI instantiation pre event - fired before instantiation of container) 125 $(LI instantiation post event - fired after instantiation of container) 126 ) 127 An example of such listeners can be like in listing below, which will enable a container before instantiation based on profile argument 128 ----------------- 129 cont.subscribable 130 .subscribe( 131 ContainerInstantiationEventType.pre, 132 { 133 cont.locate!Switchable(cont.locate!string("profile")).enabled = true; 134 } 135 ); 136 ----------------- 137 138 Typed: 139 140 Typed container decorates underlying container with ability to provide a component based on interface it implements. 141 Code using typed container attempts to get a component by an interface such as $(D_INLINECODE Engine) in this example, 142 typed container will check if underlying container has a component identified by provided interface. If yes it will give 143 the component from decorated container. If not, it will search for all registered components that implement the required interface 144 and use first found component to serve it. Notice that in this example, no $(D_INLINECODE Engine) was registered in container by 145 $(D_INLINECODE Engine) interface. Each instance $(D_INLINECODE GasolineEngine), $(D_INLINECODE ElectricEngine), and $(D_INLINECODE DieselEngine) 146 are registered by their type only. No attempt to alias one of them to $(D_INLINECODE Engine) is done, yet when Car is instantiated, a component 147 implementing $(D_INLINECODE Engine) interface is supplied. This magic is done by typed container, which supplied first available component implementing 148 $(D_INLINECODE Engine) interface. 149 150 Proxy: 151 152 A proxy container proxies all components supplied by decorated container. It will supply proxy objects instead of real components, which in turn 153 will on each public call of a proxy will redirect it to component in redirected container, and return a value from it. The proxy container 154 alone is not much useful. It is intended to be used along with containers that are dependendet on some global state to provide components. 155 An example of such containers could be containers that provide different instances of same component per thread or fiber, or in case of 156 web application, a container that gives new instances of same component for each new available request web application receives. 157 158 Deffered: 159 160 Deffered container, executes delayed components construction and configuration. Construction and configuration of a component can occur in cases 161 when there is a circular dependency between a set of components. In such case to resolve it, injection of one dependency can be delayed. 162 Delaying of construction or configuration is left to component factory, while execution of delayed action is enforced upon Deffered container. 163 To enable circular dependency resolution, decorate container with $(D_INLINECODE deffered) decorator, and register components using $(D_INLINECODE withConfigurationDefferring) 164 configuration context (simply append it after $(D_INLINECODE container.configure) method). This exampe shows a typical example of circular 165 dependency. A car has a dependency on four tires, while each tire has a dependency on a car instance, resulting in a circular dependency. 166 Removing $(D_INLINECODE deffered) or $(D_INLINECODE withConfigurationDefferring) will result in circular dependency exception thrown by container. 167 168 Describing: 169 170 Describing container, adds ability to store description for the container itself, and components that are managed in decorated container. Main purpose 171 of this type of container is to be used in storing description that will be used for help information (usually displayed in command line). 172 -------------------------- 173 auto cont = aggregate( 174 singleton, "singleton", 175 prototype, "prototype", 176 values, "parameters", 177 gasoline, "gasoline", 178 electric, "electric", 179 diesel, "diesel" 180 ).aliasing.gcRegistered.deffered.describing("Car factory", "Car factory, please see available contents of our factory"); 181 -------------------------- 182 183 Registering a description can happen in two ways: 184 $(OL 185 $(LI By using $(D_INLINECODE .describe) that adds a description on registered component)) 186 $(LI adding description directly to identity describer) 187 ) 188 189 Both ways are shown in example below: 190 -------------------------- 191 with (cont.configureValueContainer("parameters")) { 192 193 register(verbose, "verbose").describe("verbose errors", "whether to show or not appearing errors."); 194 195 if (profile !is null) { 196 register(profile, "profile"); 197 } 198 199 with (cont.locate!(IdentityDescriber!())) { 200 register("profile", "Car enginge type", "Type of engine to select while building a car (gasoline|diesel|electric|ecological)."); 201 } 202 } 203 -------------------------- 204 205 The first line is using first option for registering descriptions, where $(D_INLINECODE .describe) method will query locator for $(D_INLINECODE IdentityDescriber!()) 206 component which hosts those descriptions and then will register the description in it if found. If not a $(D_INLINECODE NotFoundException) will be thrown. 207 208 Notice that $(D_INLINECODE profile) is wrapped in a if conditional. This is done intenionally in the example to simulate a missing profile setting 209 when none is passed through command line, however the same situation could happen with $(D_INLINECODE switchable) container where even if component 210 is registered it could not be available at run time. 211 212 Last $(D_INLINECODE with) statement is the second variant of configuration, where instead of using $(D_INLINECODE .describe) entire registration of description 213 is done manually. It is better to use first method for registering descriptions, while the latter only in cases when first is not possible, just like in the 214 example where missing property is simulated. This will work with $(D_INLINECODE switchable) container since we do register those components, yet they won't 215 be available at runtime. 216 217 Second registration version used $(D_INLINECODE IdentityDescriber!()) component, which itself is a component that describing container delegates the task of 218 describing components. Besides $(D_INLINECODE IdentityDescriber!()) used as main describer, container has also a describer for itself or decorated container, 219 and a fallback describer that is used in case main one can't describe a component. All three of them are of $(D_INLINECODE Describer!()) interface, and it is 220 possible to pass them in $(D_INLINECODE describing) container as arguments. By default following implementations are available: 221 $(OL 222 $(LI $(D_INLINECODE IdentityDescriber!()) - a describer that describes data based upon identity of component) 223 $(LI $(D_INLINECODE TypeDescriber!()) - a describer that uses a template and formatter for identity and component itself to generate a description) 224 $(LI $(D_INLINECODE StaticDescriber!()) - a describer that will provide same description no matter what is passed) 225 $(LI $(D_INLINECODE NullDescriber!()) - a describer that does not describe anyhting) 226 ) 227 228 Rendering of stored description can happen like in example below: 229 -------------------------- 230 if (help.helpWanted) { 231 defaultGetoptPrinter( 232 cont.locate!(Describer!()).describe(null, cont).description, 233 cont.locate!(DescriptionsProvider!string) 234 .provide 235 .map!(description => Option(null, description.identity, text(description.title, " - ", description.description))) 236 .array 237 ); 238 return; 239 } 240 241 try { 242 243 cont.instantiate(); 244 245 cont.locate!Car.drive(profile); 246 } catch (AediException e) { 247 foreach (throwable; e.exceptions.filterByInterface!NotFoundException) { 248 defaultGetoptPrinter( 249 text("Missing \"", e.identity, "\" for proper functioning, please see detailed info of missing piece below. For more detailed options run with --help"), 250 cont.locate!(Describer!()).describe(e.identity, null) 251 .only 252 .filter!(description => !description.isNull) 253 .map!(description => Option(null, description.identity, text(description.title, " - ", description.description), true)) 254 .array 255 ); 256 257 break; 258 } 259 260 if (cont.locate!bool("verbose")) { 261 throw e; 262 } 263 } 264 -------------------------- 265 266 In case if help is required, or in other words all descriptions registered at certain point, a $(D_INLINECODE DescriptionsProvider!string) should be 267 located fetched from container and asked to provide all available descriptions. Coincidentally $(D_INLINECODE IdentityDescriber!()) implements also 268 $(D_INLINECODE DescriptionsProvider!string) and therefore it will be fetched from container once asked by provider interface it implements. 269 270 Since $(D_INLINECODE describing) container is mostly geared towards being queried for description based on identity of a component and component itself, 271 it is also possible to provide targeted info per problematic aspect encountered during application running, such as missing component in container, profile for 272 example. Try - catch statement in example uses this functionality to print message about missing component, and its description. 273 274 Try running this example, pass as argument $(D_INLINECODE --profile) with value of 275 $(D_INLINECODE gasoline), $(D_INLINECODE electric), $(D_INLINECODE diesel). 276 Experiment with it, to understand decorating containers. 277 278 License: 279 Boost Software License - Version 1.0 - August 17th, 2003 280 281 Permission is hereby granted, free of charge, to any person or organization 282 obtaining a copy of the software and accompanying documentation covered by 283 this license (the "Software") to use, reproduce, display, distribute, 284 execute, and transmit the Software, and to prepare derivative works of the 285 Software, and to permit third-parties to whom the Software is furnished to 286 do so, all subject to the following: 287 288 The copyright notices in the Software and this entire statement, including 289 the above license grant, this restriction and the following disclaimer, 290 must be included in all copies of the Software, in whole or in part, and 291 all derivative works of the Software, unless such copies or derivative 292 works are solely in the form of machine-executable object code generated by 293 a source language processor. 294 295 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 296 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 297 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 298 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 299 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 300 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 301 DEALINGS IN THE SOFTWARE. 302 303 Authors: 304 aermicioi 305 **/ 306 307 module decorating_containers; 308 309 import aermicioi.aedi; 310 import aermicioi.aedi.util.range : exceptions, filterByInterface; 311 import std.stdio; 312 import std.algorithm; 313 import std.range; 314 import std.array; 315 import std.conv : text; 316 import std.experimental.allocator.mallocator; 317 import std.experimental.allocator; 318 319 /** 320 A struct that should be managed by container. 321 **/ 322 struct Color { 323 ubyte r; 324 ubyte g; 325 ubyte b; 326 } 327 328 /** 329 Size of a car. 330 **/ 331 struct Size { 332 333 ulong width; 334 ulong height; 335 ulong length; 336 } 337 338 /** 339 Interface for engines. 340 341 An engine should implement it, in order to be installable in a car. 342 **/ 343 interface Engine { 344 345 public { 346 347 void turnOn(); 348 void run(); 349 void turnOff(); 350 } 351 } 352 353 /** 354 A concrete implementation of Engine that uses gasoline for propelling. 355 **/ 356 class GasolineEngine : Engine { 357 358 public { 359 360 void turnOn() { 361 writeln("pururukVrooomVrrr"); 362 363 } 364 365 void run() { 366 writeln("vrooom"); 367 } 368 369 void turnOff() { 370 writeln("vrrrPrrft"); 371 } 372 } 373 } 374 375 /** 376 A concrete implementation of Engine that uses diesel for propelling. 377 **/ 378 class DieselEngine : Engine { 379 380 public { 381 382 void turnOn() { 383 writeln("pururukVruumVrrr"); 384 385 } 386 387 void run() { 388 writeln("vruum"); 389 } 390 391 void turnOff() { 392 writeln("vrrrPft"); 393 } 394 } 395 } 396 397 /** 398 A concrete implementation of Engine that uses electricity for propelling. 399 **/ 400 class ElectricEngine : Engine { 401 public { 402 403 void turnOn() { 404 writeln("pzt"); 405 406 } 407 408 void run() { 409 writeln("vvvvvvvvv"); 410 } 411 412 void turnOff() { 413 writeln("tzp"); 414 } 415 } 416 } 417 418 /** 419 Tire, what it can represent else? 420 **/ 421 class Tire { 422 private { 423 int size_; 424 float pressure_; 425 string vendor_; 426 Car car_; 427 } 428 429 public @property { 430 @autowired 431 Tire car(Car car) { 432 this.car_ = car; 433 return this; 434 } 435 436 Car car() { 437 return this.car_; 438 } 439 440 Tire size(int size) @safe nothrow { 441 this.size_ = size; 442 443 return this; 444 } 445 446 int size() @safe nothrow { 447 return this.size_; 448 } 449 450 Tire pressure(float pressure) @safe nothrow { 451 this.pressure_ = pressure; 452 453 return this; 454 } 455 456 float pressure() @safe nothrow { 457 return this.pressure_; 458 } 459 460 Tire vendor(string vendor) @safe nothrow { 461 this.vendor_ = vendor; 462 463 return this; 464 } 465 466 string vendor() @safe nothrow { 467 return this.vendor_; 468 } 469 } 470 471 public override string toString() { 472 import std.algorithm; 473 import std.range; 474 import std.conv; 475 import std.utf; 476 477 return only("Tire(", this.size.to!string, " inch, ", this.pressure.to!string, " atm, ", this.vendor, ")") 478 .joiner 479 .byChar 480 .array; 481 } 482 } 483 484 /** 485 A class representing a car. 486 **/ 487 class Car { 488 489 private { 490 Color color_; // Car color 491 Size size_; // Car size 492 Engine engine_; // Car engine 493 494 Tire frontLeft_; 495 Tire frontRight_; 496 Tire backLeft_; 497 Tire backRight_; 498 } 499 500 public { 501 502 /** 503 Constructor of car. 504 505 Constructs a car with a set of sizes. A car cannot of course have 506 undefined sizes, so we should provide it during construction. 507 508 Params: 509 size = size of a car. 510 **/ 511 this(Size size, Engine engine) { 512 this.size_ = size; 513 this.engine = engine; 514 } 515 516 @property { 517 518 /** 519 Set color of car 520 521 Set color of car. A car can live with undefined color (physics allow it). 522 523 Params: 524 color = color of a car. 525 526 Returns: 527 Car 528 **/ 529 Car color(Color color) @safe nothrow { 530 this.color_ = color; 531 532 return this; 533 } 534 535 Color color() @safe nothrow { 536 return this.color_; 537 } 538 539 Size size() @safe nothrow { 540 return this.size_; 541 } 542 543 /** 544 Set engine used in car. 545 546 Params: 547 engine = engine used in car to propel it. 548 549 Returns: 550 Car 551 **/ 552 Car engine(Engine engine) @safe nothrow { 553 this.engine_ = engine; 554 555 return this; 556 } 557 558 Engine engine() @safe nothrow { 559 return this.engine_; 560 } 561 562 Car frontLeft(Tire frontLeft) @safe nothrow { 563 this.frontLeft_ = frontLeft; 564 565 return this; 566 } 567 568 Tire frontLeft() @safe nothrow { 569 return this.frontLeft_; 570 } 571 572 Car frontRight(Tire frontRight) @safe nothrow { 573 this.frontRight_ = frontRight; 574 575 return this; 576 } 577 578 Tire frontRight() @safe nothrow { 579 return this.frontRight_; 580 } 581 582 Car backLeft(Tire backLeft) @safe nothrow { 583 this.backLeft_ = backLeft; 584 585 return this; 586 } 587 588 Tire backLeft() @safe nothrow { 589 return this.backLeft_; 590 } 591 592 Car backRight(Tire backRight) @safe nothrow { 593 this.backRight_ = backRight; 594 595 return this; 596 } 597 598 Tire backRight() @safe nothrow { 599 return this.backRight_; 600 } 601 602 } 603 604 void start() { 605 engine.turnOn(); 606 } 607 608 void run() { 609 engine.run(); 610 } 611 612 void stop() { 613 engine.turnOff(); 614 } 615 } 616 } 617 618 void drive(Car car, string name) { 619 writeln("Uuh, what a nice ", name," car, with following specs:"); 620 writeln("Size:\t", car.size); 621 writeln("Color:\t", car.color); 622 writeln("Engine:\t", car.engine); 623 writeln("Tire front left:\t", car.frontLeft, "\t located at memory ", cast(void*) car.frontLeft()); 624 writeln("Tire front right:\t", car.frontRight, "\t located at memory ", cast(void*) car.frontRight()); 625 writeln("Tire back left: \t", car.backLeft, "\t located at memory ", cast(void*) car.backLeft()); 626 writeln("Tire back right:\t", car.backRight, "\t located at memory ", cast(void*) car.backRight()); 627 } 628 629 auto decorated() { 630 auto gasoline = singleton.typed.switchable.enabled(false); 631 auto electric = singleton.typed.switchable.enabled(false); 632 auto diesel = singleton.typed.switchable.enabled(false); 633 634 auto cont = aggregate( 635 singleton, "singleton", 636 prototype, "prototype", 637 values, "parameters", 638 gasoline, "gasoline", 639 electric, "electric", 640 diesel, "diesel" 641 ).aliasing.gcRegistered.deferred.describing("Car factory", "Car factory, please see available contents of our factory"); 642 643 return cont 644 .subscribable 645 .subscribe( 646 ContainerInstantiationEventType.pre, 647 { 648 cont.locate!Switchable(cont.locate!string("profile")).enabled = true; 649 } 650 ); 651 } 652 653 void main(string[] args) { 654 655 auto cont = decorated(); 656 scope(exit) container.terminate(); 657 658 import std.getopt; 659 string profile; 660 bool verbose; 661 662 auto help = getopt(args, 663 "p|profile", &profile, 664 "v|verbose", &verbose 665 ); 666 667 with (cont.configureValueContainer("parameters")) { 668 669 register(verbose, "verbose").describe("verbose errors", "whether to show or not appearing errors."); 670 671 if (profile !is null) { 672 register(profile, "profile"); 673 } 674 675 with (cont.locate!(IdentityDescriber!())) { 676 register("profile", "Car enginge type", "Type of engine to select while building a car (gasoline|diesel|electric|ecological)."); 677 } 678 } 679 680 with (cont.configure("singleton", Mallocator.instance.allocatorObject)) { 681 register!Color; 682 683 register!Size 684 .set!"width"(200UL) 685 .set!"height"(150UL) 686 .set!"length"(300UL) 687 .describe("Car size", "Rough estimations of car size that will be produced"); 688 689 register!Car 690 .autowire 691 .autowire!"color" 692 .set!"frontLeft"(lref!Tire) 693 .set!"frontRight"(lref!Tire) 694 .set!"backLeft"(lref!Tire) 695 .set!"backRight"(lref!Tire) 696 .describe("The car", "The car our factory constructed"); 697 } 698 699 with (cont.configure("prototype")) { 700 register!Tire 701 .autowire!"car" 702 .set!"size"(17) 703 .set!"pressure"(3.0) 704 .set!"vendor"("Divine tire") 705 .describe("Divine tire", "A template of a divine tire used in our car."); 706 } 707 708 cont.configure("diesel").register!DieselEngine; 709 cont.configure("gasoline").register!GasolineEngine; 710 cont.configure("electric").register!ElectricEngine; 711 712 cont.link("electric", "ecological"); 713 714 if (help.helpWanted) { 715 defaultGetoptPrinter( 716 cont.locate!(Describer!()).describe(null, cont).description, 717 cont.locate!(DescriptionsProvider!string) 718 .provide 719 .map!(description => Option(null, description.identity, text(description.title, " - ", description.description))) 720 .array 721 ); 722 return; 723 } 724 725 try { 726 727 cont.instantiate(); 728 729 cont.locate!Car.drive(profile); 730 } catch (AediException e) { 731 foreach (throwable; e.exceptions.filterByInterface!NotFoundException) { 732 defaultGetoptPrinter( 733 text("Missing \"", e.identity, "\" for proper functioning, please see detailed info of missing piece below. For more detailed options run with --help"), 734 cont.locate!(Describer!()).describe(e.identity, null) 735 .only 736 .filter!(description => !description.isNull) 737 .map!(description => Option(null, description.identity, text(description.title, " - ", description.description), true)) 738 .array 739 ); 740 741 break; 742 } 743 744 if (cont.locate!bool("verbose")) { 745 throw e; 746 } 747 } 748 }