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 moduledecorating_containers;
308 309 importaermicioi.aedi;
310 importaermicioi.aedi.util.range : exceptions, filterByInterface;
311 importstd.stdio;
312 importstd.algorithm;
313 importstd.range;
314 importstd.array;
315 importstd.conv : text;
316 importstd.experimental.allocator.mallocator;
317 importstd.experimental.allocator;
318 319 /**
320 A struct that should be managed by container.
321 **/322 structColor {
323 ubyter;
324 ubyteg;
325 ubyteb;
326 }
327 328 /**
329 Size of a car.
330 **/331 structSize {
332 333 ulongwidth;
334 ulongheight;
335 ulonglength;
336 }
337 338 /**
339 Interface for engines.
340 341 An engine should implement it, in order to be installable in a car.
342 **/343 interfaceEngine {
344 345 public {
346 347 voidturnOn();
348 voidrun();
349 voidturnOff();
350 }
351 }
352 353 /**
354 A concrete implementation of Engine that uses gasoline for propelling.
355 **/356 classGasolineEngine : Engine {
357 358 public {
359 360 voidturnOn() {
361 writeln("pururukVrooomVrrr");
362 363 }
364 365 voidrun() {
366 writeln("vrooom");
367 }
368 369 voidturnOff() {
370 writeln("vrrrPrrft");
371 }
372 }
373 }
374 375 /**
376 A concrete implementation of Engine that uses diesel for propelling.
377 **/378 classDieselEngine : Engine {
379 380 public {
381 382 voidturnOn() {
383 writeln("pururukVruumVrrr");
384 385 }
386 387 voidrun() {
388 writeln("vruum");
389 }
390 391 voidturnOff() {
392 writeln("vrrrPft");
393 }
394 }
395 }
396 397 /**
398 A concrete implementation of Engine that uses electricity for propelling.
399 **/400 classElectricEngine : Engine {
401 public {
402 403 voidturnOn() {
404 writeln("pzt");
405 406 }
407 408 voidrun() {
409 writeln("vvvvvvvvv");
410 }
411 412 voidturnOff() {
413 writeln("tzp");
414 }
415 }
416 }
417 418 /**
419 Tire, what it can represent else?
420 **/421 classTire {
422 private {
423 intsize_;
424 floatpressure_;
425 stringvendor_;
426 Carcar_;
427 }
428 429 public @property {
430 @autowired431 Tirecar(Carcar) {
432 this.car_ = car;
433 returnthis;
434 }
435 436 Carcar() {
437 returnthis.car_;
438 }
439 440 Tiresize(intsize) @safenothrow {
441 this.size_ = size;
442 443 returnthis;
444 }
445 446 intsize() @safenothrow {
447 returnthis.size_;
448 }
449 450 Tirepressure(floatpressure) @safenothrow {
451 this.pressure_ = pressure;
452 453 returnthis;
454 }
455 456 floatpressure() @safenothrow {
457 returnthis.pressure_;
458 }
459 460 Tirevendor(stringvendor) @safenothrow {
461 this.vendor_ = vendor;
462 463 returnthis;
464 }
465 466 stringvendor() @safenothrow {
467 returnthis.vendor_;
468 }
469 }
470 471 publicoverridestringtoString() {
472 importstd.algorithm;
473 importstd.range;
474 importstd.conv;
475 importstd.utf;
476 477 returnonly("Tire(", this.size.to!string, " inch, ", this.pressure.to!string, " atm, ", this.vendor, ")")
478 .joiner479 .byChar480 .array;
481 }
482 }
483 484 /**
485 A class representing a car.
486 **/487 classCar {
488 489 private {
490 Colorcolor_; // Car color491 Sizesize_; // Car size492 Engineengine_; // Car engine493 494 TirefrontLeft_;
495 TirefrontRight_;
496 TirebackLeft_;
497 TirebackRight_;
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(Sizesize, Engineengine) {
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 Carcolor(Colorcolor) @safenothrow {
530 this.color_ = color;
531 532 returnthis;
533 }
534 535 Colorcolor() @safenothrow {
536 returnthis.color_;
537 }
538 539 Sizesize() @safenothrow {
540 returnthis.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 Carengine(Engineengine) @safenothrow {
553 this.engine_ = engine;
554 555 returnthis;
556 }
557 558 Engineengine() @safenothrow {
559 returnthis.engine_;
560 }
561 562 CarfrontLeft(TirefrontLeft) @safenothrow {
563 this.frontLeft_ = frontLeft;
564 565 returnthis;
566 }
567 568 TirefrontLeft() @safenothrow {
569 returnthis.frontLeft_;
570 }
571 572 CarfrontRight(TirefrontRight) @safenothrow {
573 this.frontRight_ = frontRight;
574 575 returnthis;
576 }
577 578 TirefrontRight() @safenothrow {
579 returnthis.frontRight_;
580 }
581 582 CarbackLeft(TirebackLeft) @safenothrow {
583 this.backLeft_ = backLeft;
584 585 returnthis;
586 }
587 588 TirebackLeft() @safenothrow {
589 returnthis.backLeft_;
590 }
591 592 CarbackRight(TirebackRight) @safenothrow {
593 this.backRight_ = backRight;
594 595 returnthis;
596 }
597 598 TirebackRight() @safenothrow {
599 returnthis.backRight_;
600 }
601 602 }
603 604 voidstart() {
605 engine.turnOn();
606 }
607 608 voidrun() {
609 engine.run();
610 }
611 612 voidstop() {
613 engine.turnOff();
614 }
615 }
616 }
617 618 voiddrive(Carcar, stringname) {
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 autodecorated() {
630 autogasoline = singleton.typed.switchable.enabled(false);
631 autoelectric = singleton.typed.switchable.enabled(false);
632 autodiesel = singleton.typed.switchable.enabled(false);
633 634 autocont = 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 returncont644 .subscribable645 .subscribe(
646 ContainerInstantiationEventType.pre,
647 {
648 cont.locate!Switchable(cont.locate!string("profile")).enabled = true;
649 }
650 );
651 }
652 653 voidmain(string[] args) {
654 655 autocont = decorated();
656 scope(exit) container.terminate();
657 658 importstd.getopt;
659 stringprofile;
660 boolverbose;
661 662 autohelp = getopt(args,
663 "p|profile", &profile,
664 "v|verbose", &verbose665 );
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 !isnull) {
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!Size684 .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!Car690 .autowire691 .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!Tire701 .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 .provide719 .map!(description => Option(null, description.identity, text(description.title, " - ", description.description)))
720 .array721 );
722 return;
723 }
724 725 try {
726 727 cont.instantiate();
728 729 cont.locate!Car.drive(profile);
730 } catch (AediExceptione) {
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 .only736 .filter!(description => !description.isNull)
737 .map!(description => Option(null, description.identity, text(description.title, " - ", description.description), true))
738 .array739 );
740 741 break;
742 }
743 744 if (cont.locate!bool("verbose")) {
745 throwe;
746 }
747 }
748 }