1 /*******************************************************************************
2 
3     Provides convenient functions to fill the values of a given aggregate.
4 
5     Provides functions that use a given source to fill the member variables
6     of a provided aggregate or newly created instance of a given class.
7 
8     The provided class can use certain wrappers to add conditions or
9     informations to the variable in question. The value of a wrapped variable
10     can be accessed using the opCall syntax "variable()"
11 
12     Overview of available wrappers:
13 
14     * Required  — This variable has to be set in the configuration file
15                   Example:  Required!(char[]) nodes_config;
16     * MinMax    — This numeric variable has to be within the specified range
17                   Example: MinMax!(long, -10, 10) range;
18     * Min       — This numeric variable has to be >= the specified value
19                   Example: Min!(int, -10) min_range;
20     * Max       — This numeric variable has to be <= the specified value
21                   Example: Max!(int, 20) max_range;
22     * LimitCmp  — This variable must be one of the given values. To compare the
23                   config value with the given values, the given function will be
24                   used
25                   Example:  LimitCmp!(char[], "red", defComp!(char[]),
26                                       "red", "green", "blue", "yellow") color;
27     * LimitInit — This variable must be one of the given values, it will default
28                   to the given value.
29                   Example: LimitInit!(char[], "red", "red", "green") color;
30     * Limit     — This variable must be one of the given values
31                   Example: Limit!(char[], "up", "down", "left", "right") dir;
32     * SetInfo   — the 'set' member can be used to query whether this
33                   variable was set from the configuration file or not
34                   Example: SetInfo!(bool) enable; // enable.set
35 
36     Use debug=Config to get a printout of all the configuration options
37 
38     Copyright:
39         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
40         All rights reserved.
41 
42     License:
43         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
44         Alternatively, this file may be distributed under the terms of the Tango
45         3-Clause BSD License (see LICENSE_BSD.txt for details).
46 
47 *******************************************************************************/
48 
49 module ocean.util.config.ConfigFiller;
50 
51 
52 
53 import ocean.meta.types.Qualifiers;
54 
55 import ocean.core.Verify;
56 
57 public import ocean.util.config.ConfigParser: ConfigException;
58 
59 import ocean.core.ExceptionDefinitions, ocean.core.Enforce;
60 
61 import ocean.util.config.ConfigParser;
62 
63 import ocean.util.Convert;
64 
65 import ocean.meta.traits.Basic /* : isArrayType, isCharType, isIntegerType, isRealType */ ;
66 import ocean.meta.traits.Arrays /* : isUTF8StringType */ ;
67 import ocean.meta.types.Arrays /* : ElementTypeOf */ ;
68 
69 import ocean.io.Stdout;
70 
71 import ocean.text.convert.Formatter;
72 
73 version (unittest) import ocean.core.Test;
74 
75 /*******************************************************************************
76 
77     Whether loose parsing is enabled or not.
78     Loose parsing means, that variables that have no effect are allowed.
79 
80     States
81         false = variables that have no effect cause an exception
82         true  = variables that have no effect cause a stderr warning message
83 
84 *******************************************************************************/
85 
86 private bool loose_parsing = false;
87 
88 /*******************************************************************************
89 
90     Evaluates to the original type with which a Wrapper Struct was initialised
91 
92     If T is not a struct, T itself is returned
93 
94     Params:
95         T = struct or type to find the basetype for
96 
97 *******************************************************************************/
98 
99 template BaseType ( T )
100 {
101     static if ( is(typeof(T.value)) )
102     {
103         alias BaseType!(typeof(T.value)) BaseType;
104     }
105     else
106     {
107         alias T BaseType;
108     }
109 }
110 
111 /*******************************************************************************
112 
113     Returns the value of the given struct/value.
114 
115     If value is not a struct, the value itself is returned
116 
117     Params:
118         v = instance of a struct the value itself
119 
120 *******************************************************************************/
121 
122 BaseType!(T) Value ( T ) ( T v )
123 {
124     static if ( is(T == BaseType!(typeof(v))) )
125     {
126         return v;
127     }
128     else
129     {
130         return Value(v.value);
131     }
132 }
133 
134 /*******************************************************************************
135 
136     Contains methods used in all WrapperStructs to access and set the value
137     variable
138 
139     Params:
140         T = type of the value
141 
142 *******************************************************************************/
143 
144 template WrapperStructCore ( T, T init = T.init )
145 {
146     /***************************************************************************
147 
148         The value of the configuration setting
149 
150     ***************************************************************************/
151 
152     private T value = init;
153 
154 
155     /***************************************************************************
156 
157         Returns the value that is wrapped
158 
159     ***************************************************************************/
160 
161     public BaseType!(T) opCall ( )
162     {
163         return Value(this.value);
164     }
165 
166     /***************************************************************************
167 
168         Returns the value that is wrapped
169 
170     ***************************************************************************/
171 
172     public BaseType!(T) opCast ( )
173     {
174         return Value(this.value);
175     }
176 
177     /***************************************************************************
178 
179         Sets the wrapped value to val
180 
181         Params:
182             val = new value
183 
184         Returns:
185             val
186 
187     ***************************************************************************/
188 
189     public BaseType!(T) opAssign ( BaseType!(T) val )
190     {
191         return value = val;
192     }
193 
194     /***************************************************************************
195 
196         Calls check_() with the same parameters. If check doesn't throw an
197         exception it checks whether the wrapped value is also a struct and if so
198         its check function is called.
199 
200         Params:
201             bool  = whether the variable existed in the configuration file
202             group = group this variable should appear
203             name  = name of the variable
204 
205     ***************************************************************************/
206 
207     private void check ( bool found, cstring group, cstring name )
208     {
209         static if ( !is (BaseType!(T) == T) )
210         {
211             scope(success) this.value.check(found, group, name);
212         }
213 
214         this.check_(found, group, name);
215     }
216 }
217 
218 /*******************************************************************************
219 
220     Configuration settings that are mandatory can be marked as such by
221     wrapping them with this template.
222     If the variable is not set, then an exception is thrown.
223 
224     The value can be accessed with the opCall method
225 
226     Params:
227         T = the original type of the variable
228 
229 *******************************************************************************/
230 
231 struct Required ( T )
232 {
233     mixin WrapperStructCore!(T);
234 
235     /***************************************************************************
236 
237         Checks whether the checked value was found, throws if not
238 
239         Params:
240             found = whether the variable was found in the configuration
241             group = group the variable appeares in
242             name  = name of the variable
243 
244         Throws:
245             ConfigException
246 
247     ***************************************************************************/
248 
249     private void check_ ( bool found, cstring group, cstring name )
250     {
251         enforce!(ConfigException)(
252             found,
253             format("Mandatory variable {}.{} not set.", group, name));
254     }
255 }
256 
257 /*******************************************************************************
258 
259     Configuration settings that are required to be within a certain numeric
260     range can be marked as such by wrapping them with this template.
261 
262     If the value is outside the provided range, an exception is thrown.
263 
264     The value can be accessed with the opCall method
265 
266     Params:
267         T    = the original type of the variable (can be another struct)
268         min  = smallest allowed value
269         max  = biggest allowed value
270         init = default value when it is not given in the configuration file
271 
272 *******************************************************************************/
273 
274 struct MinMax ( T, T min, T max, T init = T.init )
275 {
276     mixin WrapperStructCore!(T, init);
277 
278     /***************************************************************************
279 
280         Checks whether the configuration value is bigger than the smallest
281         allowed value and smaller than the biggest allowed value.
282         If not, an exception is thrown
283 
284         Params:
285             bool  = whether the variable existed in the configuration file
286             group = group this variable should appear
287             name  = name of the variable
288 
289         Throws:
290             ConfigException
291 
292     ***************************************************************************/
293 
294     private void check_ ( bool found, cstring group, cstring name )
295     {
296         enforce!(ConfigException)(
297             Value(this.value) >= min,
298             format("Configuration key {}.{} is smaller than allowed minimum of {}",
299                    group, name, min));
300         enforce!(ConfigException)(
301             Value(this.value) <= max,
302             format("Configuration key {}.{} is bigger than allowed maximum of {}",
303                    group, name, max));
304     }
305 }
306 
307 /*******************************************************************************
308 
309     Configuration settings that are required to be within a certain numeric
310     range can be marked as such by wrapping them with this template.
311 
312     If the value is outside the provided range, an exception is thrown.
313 
314     The value can be accessed with the opCall method
315 
316     Params:
317         T    = the original type of the variable (can be another struct)
318         min  = smallest allowed value
319         init = default value when it is not given in the configuration file
320 
321 *******************************************************************************/
322 
323 struct Min ( T, T min, T init = T.init )
324 {
325     mixin WrapperStructCore!(T, init);
326 
327     /***************************************************************************
328 
329         Checks whether the configuration value is bigger than the smallest
330         allowed value. If not, an exception is thrown
331 
332         Params:
333             bool  = whether the variable existed in the configuration file
334             group = group this variable should appear
335             name  = name of the variable
336 
337         Throws:
338             ConfigException
339 
340     ***************************************************************************/
341 
342     private void check_ ( bool found, cstring group, cstring name )
343     {
344         enforce!(ConfigException)(
345             Value(this.value) >= min,
346             format("Configuration key {}.{} is smaller than allowed minimum of {}",
347                    group, name, min));
348     }
349 }
350 
351 
352 /*******************************************************************************
353 
354     Configuration settings that are required to be within a certain numeric
355     range can be marked as such by wrapping them with this template.
356 
357     If the value is outside the provided range, an exception is thrown.
358 
359     The value can be accessed with the opCall method
360 
361     Params:
362         T    = the original type of the variable (can be another struct)
363         max  = biggest allowed value
364         init = default value when it is not given in the configuration file
365 
366 *******************************************************************************/
367 
368 struct Max ( T, T max, T init = T.init )
369 {
370     mixin WrapperStructCore!(T, init);
371 
372     /***************************************************************************
373 
374         Checks whether the configuration value is smaller than the biggest
375         allowed value. If not, an exception is thrown
376 
377         Params:
378             bool  = whether the variable existed in the configuration file
379             group = group this variable should appear
380             name  = name of the variable
381 
382         Throws:
383             ConfigException
384 
385     ***************************************************************************/
386 
387     private void check_ ( bool found, cstring group, cstring name )
388     {
389         enforce!(ConfigException)(
390             Value(this.value) <= max,
391             format("Configuration key {}.{} is bigger than allowed maximum of {}",
392                    group, name, max));
393     }
394 }
395 
396 
397 /*******************************************************************************
398 
399     Default compare function, used with the LimitCmp struct/template
400 
401     Params:
402         a = first value to compare
403         b = second value to compare with
404 
405     Returns:
406         whether a == b
407 
408 *******************************************************************************/
409 
410 bool defComp ( T ) ( T a, T b )
411 {
412     return a == b;
413 }
414 
415 /*******************************************************************************
416 
417     Configuration settings that are limited to a certain set of values can be
418     marked as such by wrapping them with this template.
419 
420     If the value is not in the provided set, an exception is thrown.
421 
422     The value can be accessed with the opCall method
423 
424     Params:
425         T    = the original type of the variable (can be another struct)
426         init = default value when it is not given in the configuration file
427         comp = compare function to be used to compare two values from the set
428         Set  = tuple of values that are valid
429 
430 *******************************************************************************/
431 
432 struct LimitCmp ( T, T init = T.init, alias comp = defComp!(T), Set... )
433 {
434     mixin WrapperStructCore!(T, init);
435 
436     /***************************************************************************
437 
438         Checks whether the configuration value is within the set of allowed
439         values. If not, an exception is thrown
440 
441         Params:
442             bool  = whether the variable existed in the configuration file
443             group = group this variable should appear
444             name  = name of the variable
445 
446          Throws:
447             ConfigException
448 
449     ***************************************************************************/
450 
451     private void check_ ( bool found, cstring group, cstring name )
452     {
453         if ( found == false ) return;
454 
455         foreach ( el ; Set )
456         {
457             static assert (
458                 is ( typeof(el) : T ),
459                 "Tuple contains incompatible types! ("
460                     ~ typeof(el).stringof ~ " to " ~ T.stringof ~ " )"
461             );
462 
463             if ( comp(Value(this.value), el) )
464                 return;
465         }
466 
467         istring allowed_vals;
468 
469         foreach ( el ; Set )
470         {
471             allowed_vals ~= ", " ~ to!(istring)(el);
472         }
473 
474         throw new ConfigException(
475             format("Value '{}' of configuration key {}.{} is not within the "
476                    ~ "set of allowed values ({})",
477                    Value(this.value), group, name, allowed_vals[2 .. $]));
478     }
479 }
480 
481 
482 unittest
483 {
484     test(is(typeof({ LimitCmp!(int, 1, defComp!(int), 0, 1) val; })));
485     test(is(typeof({ LimitCmp!(istring, "", defComp!(istring), "red"[], "green"[]) val; })));
486 }
487 
488 /*******************************************************************************
489 
490     Simplified version of LimitCmp that uses default comparison
491 
492     Params:
493         T = type of the value
494         init = default initial value if config value wasn't set
495         Set = set of allowed values
496 
497 *******************************************************************************/
498 
499 template LimitInit ( T, T init = T.init, Set... )
500 {
501     alias LimitCmp!(T, init, defComp!(T), Set) LimitInit;
502 }
503 
504 unittest
505 {
506     test(is(typeof({LimitInit!(int, 1, 0, 1) val;})));
507     test(is(typeof({LimitInit!(istring, "green"[], "red"[], "green"[]) val;})));
508 }
509 
510 
511 /*******************************************************************************
512 
513     Simplified version of LimitCmp that uses default comparison and default
514     initializer
515 
516     Params:
517         T = type of the value
518         Set = set of allowed values
519 
520 *******************************************************************************/
521 
522 template Limit ( T, Set... )
523 {
524     alias LimitInit!(T, T.init, Set) Limit;
525 }
526 
527 
528 /*******************************************************************************
529 
530     Adds the information of whether the filler actually set the value
531     or whether it was left untouched.
532 
533     Params:
534         T = the original type
535 
536 *******************************************************************************/
537 
538 struct SetInfo ( T )
539 {
540     mixin WrapperStructCore!(T);
541 
542     /***************************************************************************
543 
544         Query method for the value with optional default initializer
545 
546         Params:
547             def = the value that should be used when it was not found in the
548                   configuration
549 
550     ***************************************************************************/
551 
552     public BaseType!(T) opCall ( BaseType!(T) def = BaseType!(T).init )
553     {
554         if ( set )
555         {
556             return Value(this.value);
557         }
558 
559         return def;
560     }
561 
562     /***************************************************************************
563 
564         Whether this value has been set
565 
566     ***************************************************************************/
567 
568     public bool set;
569 
570     /***************************************************************************
571 
572         Sets the set attribute according to whether the variable appeared in
573         the configuration or not
574 
575         Params:
576             bool  = whether the variable existed in the configuration file
577             group = group this variable should appear
578             name  = name of the variable
579 
580     ***************************************************************************/
581 
582     private void check_ ( bool found, cstring group, cstring name )
583     {
584         this.set = found;
585     }
586 }
587 
588 
589 /*******************************************************************************
590 
591     Template that evaluates to true when T is a supported type
592 
593     Params:
594         T = type to check for
595 
596 *******************************************************************************/
597 
598 public template IsSupported ( T )
599 {
600     static if ( is(T : bool) )
601         static immutable IsSupported = true;
602     else static if ( isIntegerType!(T) || isRealType!(T) )
603         static immutable IsSupported = true;
604     else static if ( is(ElementTypeOf!(T) U) )
605     {
606         static if ( isCharType!(U) ) // If it is a string
607             static immutable IsSupported = true;
608         else static if ( isUTF8StringType!(U) ) // If it is string of strings
609             static immutable IsSupported = true;
610         else static if ( isIntegerType!(U) || isRealType!(U) )
611             static immutable IsSupported = true;
612         else
613             static immutable IsSupported = false;
614     }
615     else
616         static immutable IsSupported = false;
617 }
618 
619 
620 /*******************************************************************************
621 
622     Set whether loose parsing is enabled or not.
623     Loose parsing means, that variables that have no effect are allowed.
624 
625     Initial value is false.
626 
627     Params:
628         state =
629             default: true
630             false: variables that have no effect cause an exception
631             true:  variables that have no effect cause a stderr warning message
632 
633 *******************************************************************************/
634 
635 public bool enable_loose_parsing ( bool state = true )
636 {
637     return loose_parsing = state;
638 }
639 
640 
641 /*******************************************************************************
642 
643     Creates an instance of T, and fills it with according values from the
644     configuration file. The name of each variable will used to get it
645     from the given section in the configuration file.
646 
647     Variables can be marked as required with the Required template.
648     If it is important to know whether the setting has been set, the
649     SetInfo struct can be used.
650 
651     Params:
652         group     = the group/section of the variable
653         config    = instance of the source to use
654 
655     Returns:
656         a new instance filled with values from the configuration file
657 
658     See_Also:
659         Required, SetInfo
660 
661 *******************************************************************************/
662 
663 public T fill ( T, Source = ConfigParser )
664               ( cstring group, Source config )
665 {
666     verify(config !is null, "ConfigFiller.fill: Cannot use null config");
667 
668     T reference;
669     return fill(group, reference, config);
670 }
671 
672 
673 /*******************************************************************************
674 
675     Fill the given instance of T with according values from the
676     configuration file. The name of each variable will used to get it
677     from the given section in the configuration file.
678 
679     If reference is null, an instance will be created.
680 
681     Variables can be marked as required with the Required template.
682     If it is important to know whether the setting has been set, the
683     SetInfo struct can be used.
684 
685     Params:
686         group     = the group/section of the variable
687         reference = the instance to fill. If null it will be created
688         config    = instance of the source to use
689 
690     Returns:
691         an instance filled with values from the configuration file
692 
693     See_Also:
694         Required, SetInfo
695 
696 *******************************************************************************/
697 
698 public T fill ( T, Source = ConfigParser )
699               ( cstring group, ref T reference, Source config )
700 {
701     verify(config !is null, "ConfigFiller.fill: Cannot use null config");
702 
703     static if (is (T: Object))
704     {
705         if ( reference is null )
706         {
707             reference = new T;
708         }
709     }
710 
711     foreach ( var; config.iterateCategory(group) )
712     {
713         if ( !hasField(reference, var) )
714         {
715             auto msg = cast(istring) ("Invalid configuration key "
716                 ~ group ~ "." ~ var);
717             enforce!(ConfigException)(loose_parsing, msg);
718             Stderr.formatln("#### WARNING: {}", msg);
719         }
720     }
721 
722     readFields!(T)(group, reference, config);
723 
724     return reference;
725 }
726 
727 /*******************************************************************************
728 
729     Checks whether T or any of its super classes contain
730     a variable called field
731 
732     Params:
733         reference = reference of the object that will be checked
734         field     = name of the field to check for
735 
736     Returns:
737         true when T or any parent class has a member named the same as the
738         value of field,
739         else false
740 
741 *******************************************************************************/
742 
743 private bool hasField ( T ) ( T reference, cstring field )
744 {
745     foreach ( si, unused; reference.tupleof )
746     {
747         auto key = reference.tupleof[si].stringof["reference.".length .. $];
748 
749         if ( key == field ) return true;
750     }
751 
752     bool was_found = true;
753 
754     // Recurse into super any classes
755     static if ( is(T S == super ) )
756     {
757         was_found = false;
758 
759         foreach ( G; S ) static if ( !is(G == Object) )
760         {
761             if ( hasField!(G)(cast(G) reference, field))
762             {
763                 was_found = true;
764                 break;
765             }
766         }
767     }
768 
769     return was_found;
770 }
771 
772 /*******************************************************************************
773 
774     Config Iterator. Iterates over variables of a category
775 
776     Params:
777         T = type of the class to iterate upon
778         Source = type of the source of values of the class' members - must
779             provide foreach iteration over its elements
780             (defaults to ConfigParser)
781 
782 *******************************************************************************/
783 
784 struct ConfigIterator ( T, Source = ConfigParser )
785 {
786     /***************************************************************************
787 
788         The full parsed configuration. This contains all sections of the
789         configuration, but only those that begin with the root string are
790         iterated upon.
791 
792     ***************************************************************************/
793 
794     Source config;
795 
796     /***************************************************************************
797 
798         The root string that is used to filter sections of the configuration
799         over which to iterate.
800         For instance, in a config file containing sections 'LOG.a', 'LOG.b',
801         'LOG.a.a1' etc., the root string would be "LOG".
802 
803     ***************************************************************************/
804 
805     istring root;
806 
807     /***************************************************************************
808 
809         Class invariant.
810 
811     ***************************************************************************/
812 
813     invariant()
814     {
815         assert(this.config !is null,
816             "ConfigFiller.ConfigIterator: Cannot have null config");
817     }
818 
819     /***************************************************************************
820 
821         Variable Iterator. Iterates over variables of a category, with the
822         foreach delegate being called with the name of the category (not
823         including the root string prefix) and an instance containing the
824         properties within that category.
825 
826     ***************************************************************************/
827 
828     public int opApply ( scope int delegate ( ref istring name, ref T x ) dg )
829     {
830         int result = 0;
831 
832         foreach ( key; this.config )
833         {
834             static if (is (T == struct))
835             {
836                 T instance;
837             }
838             else
839             {
840                 scope T instance = new T;
841             }
842 
843             if ( key.length > this.root.length
844                  && key[0 .. this.root.length] == this.root
845                  && key[this.root.length] == '.' )
846             {
847                 .fill(key, instance, this.config);
848 
849                 auto name = key[this.root.length + 1 .. $];
850                 result = dg(name, instance);
851 
852                 if (result) break;
853             }
854         }
855 
856         return result;
857     }
858 
859     /***************************************************************************
860 
861         Variable Iterator. Iterates over variables of a category, with the
862         foreach delegate being called with only the name of the category (not
863         including the root string prefix).
864 
865         This iterator may be used in cases where iteration over categories
866         prefixed by the root string can be done, with the decision of whether to
867         call 'fill()' or not being made on a case-by-case basis.
868 
869     ***************************************************************************/
870 
871     public int opApply ( scope int delegate ( ref istring name ) dg )
872     {
873         int result = 0;
874 
875         foreach ( key; this.config )
876         {
877             if ( key.length > this.root.length
878                  && key[0 .. this.root.length] == this.root
879                  && key[this.root.length] == '.' )
880             {
881                 auto name = key[this.root.length + 1 .. $];
882                 result = dg(name);
883 
884                 if (result) break;
885             }
886         }
887 
888         return result;
889     }
890 
891     /***************************************************************************
892 
893         Fills the properties of the given category into an instance representing
894         that category.
895 
896         Params:
897             name = category whose properties are to be filled (this name will be
898                 prefixed with the root string and a period to form an actual
899                 section name of the parsed configuration)
900             instance = instance into which to fill the properties
901 
902     ***************************************************************************/
903 
904     public void fill ( cstring name, ref T instance )
905     {
906         auto key = this.root ~ "." ~ name;
907 
908         .fill(key, instance, this.config);
909     }
910 }
911 
912 /*******************************************************************************
913 
914     Creates an iterator that iterates over groups that start with
915     a common string, filling an instance of the passed class type from
916     the variables of each matching group and calling the delegate.
917 
918     Params:
919         T = type of the class to fill
920         Source = source to use
921         root = start of the group name
922         config = instance of the source to use
923 
924     Returns:
925         iterator that iterates over all groups matching the pattern
926 
927 *******************************************************************************/
928 
929 public ConfigIterator!(T) iterate ( T, Source = ConfigParser )
930                                  ( istring root, Source config )
931 {
932     verify(config !is null, "ConfigFiller.iterate: Cannot use null config");
933 
934     return ConfigIterator!(T, Source)(config, root);
935 }
936 
937 
938 /*******************************************************************************
939 
940     Fills the fields of the `reference` from config file's group.
941 
942     Params:
943         T  = type of the class to fill
944         Source = source to use
945         group = group to read fields from
946         reference = reference to the object to be filled
947         config = instance of the source to use
948 
949 *******************************************************************************/
950 
951 private void readFieldsImpl ( T, Source )
952                           ( cstring group, ref T reference, Source config )
953 {
954     verify( config !is null, "ConfigFiller.readFields: Cannot use null config");
955 
956     foreach ( si, field; reference.tupleof )
957     {
958         alias BaseType!(typeof(field)) Type;
959 
960         static assert ( IsSupported!(Type),
961                         "ConfigFiller.readFields: Type "
962                         ~ Type.stringof ~ " is not supported" );
963 
964         auto key = reference.tupleof[si].stringof["reference.".length .. $];
965 
966         if ( config.exists(group, key) )
967         {
968             static if (is(Type U : U[]) && !isUTF8StringType!(Type))
969             {
970                 reference.tupleof[si] = config.getListStrict!(U)(group, key);
971             }
972             else
973             {
974                 reference.tupleof[si] = config.getStrict!(Type)(group, key);
975             }
976 
977             debug (Config) Stdout.formatln("Config Debug: {}.{} = {}", group,
978                              reference.tupleof[si]
979                             .stringof["reference.".length  .. $],
980                             Value(reference.tupleof[si]));
981 
982             static if ( !is (Type == typeof(field)) )
983             {
984                 reference.tupleof[si].check(true, group, key);
985             }
986         }
987         else
988         {
989             debug (Config) Stdout.formatln("Config Debug: {}.{} = {} (builtin)", group,
990                              reference.tupleof[si]
991                             .stringof["reference.".length  .. $],
992                             Value(reference.tupleof[si]));
993 
994             static if ( !is (Type == typeof(field)) )
995             {
996                 reference.tupleof[si].check(false, group, key);
997             }
998         }
999     }
1000 
1001     // Recurse into super any classes
1002     static if ( is(T S == super ) )
1003     {
1004         foreach ( G; S ) static if ( !is(G == Object) )
1005         {
1006             readFields!(G)(group, cast(G) reference, config);
1007         }
1008     }
1009 }
1010 
1011 /*******************************************************************************
1012 
1013     Fills the fields of the `reference` from config file's group.
1014 
1015     Params:
1016         T  = type of the class to fill
1017         Source = source to use
1018         group = group to read fields from
1019         reference = reference to the object to be filled
1020         config = instance of the source to use
1021 
1022 *******************************************************************************/
1023 
1024 package void readFields ( T : Object, Source )
1025                           ( cstring group, T reference, Source config )
1026 {
1027     // Workaround to work on both l- and r-values
1028     T tmp = reference;
1029     readFieldsImpl(group, tmp, config);
1030 }
1031 
1032 /*******************************************************************************
1033 
1034     Fills the fields of the `reference` from config file's group.
1035 
1036     Params:
1037         T  = type of the aggregate to fill
1038         Source = source to use
1039         group = group to read fields from
1040         reference = reference to the object to be filled
1041         config = instance of the source to use
1042 
1043 *******************************************************************************/
1044 
1045 package void readFields ( T, Source )
1046                           ( cstring group, ref T reference, Source config )
1047 {
1048     readFieldsImpl(group, reference, config);
1049 }
1050 
1051 version (unittest)
1052 {
1053     class SolarSystemEntity
1054     {
1055         uint radius;
1056         uint circumference;
1057     }
1058 
1059     struct SolarSystemEntityStruct
1060     {
1061         uint radius;
1062         uint circumference;
1063     }
1064 
1065     auto config_str =
1066 `
1067 [SUN.earth]
1068 radius = 6371
1069 circumference = 40075
1070 
1071 [SUN-andromeda]
1072 lunch = dessert_place
1073 
1074 [SUN_wannabe_solar_system_entity]
1075 radius = 4525
1076 circumference = 35293
1077 
1078 [SUN.earth.moon]
1079 radius = 1737
1080 circumference = 10921
1081 
1082 [SUNBLACKHOLE]
1083 shoe_size = 42
1084 `;
1085 }
1086 
1087 unittest
1088 {
1089     auto config_parser = new ConfigParser();
1090 
1091     config_parser.parseString(config_str);
1092 
1093     auto iter = iterate!(SolarSystemEntity)("SUN", config_parser);
1094 
1095     SolarSystemEntity entity_details;
1096 
1097     foreach ( entity; iter )
1098     {
1099         test((entity == "earth") || (entity == "earth.moon"),
1100             "'" ~ entity ~ "' is neither 'earth' nor 'earth.moon'");
1101 
1102         iter.fill(entity, entity_details);
1103 
1104         if (entity == "earth")
1105         {
1106             test!("==")(entity_details.radius, 6371);
1107             test!("==")(entity_details.circumference, 40075);
1108         }
1109         else // if (entity == "earth.moon")
1110         {
1111             test!("==")(entity_details.radius, 1737);
1112             test!("==")(entity_details.circumference, 10921);
1113         }
1114     }
1115 }
1116 
1117 unittest
1118 {
1119     auto config_parser = new ConfigParser();
1120 
1121     config_parser.parseString(config_str);
1122 
1123     auto iter = iterate!(SolarSystemEntityStruct)("SUN", config_parser);
1124 
1125     SolarSystemEntityStruct entity_details;
1126 
1127     foreach ( entity; iter )
1128     {
1129         test((entity == "earth") || (entity == "earth.moon"),
1130             "'" ~ entity ~ "' is neither 'earth' nor 'earth.moon'");
1131 
1132         iter.fill(entity, entity_details);
1133 
1134         if (entity == "earth")
1135         {
1136             test!("==")(entity_details.radius, 6371);
1137             test!("==")(entity_details.circumference, 40075);
1138         }
1139         else // if (entity == "earth.moon")
1140         {
1141             test!("==")(entity_details.radius, 1737);
1142             test!("==")(entity_details.circumference, 10921);
1143         }
1144     }
1145 }
1146 unittest
1147 {
1148     static immutable config_text =
1149 `
1150 [Section]
1151 str = I'm a string
1152 integer = -300
1153 pi = 3.14
1154 `;
1155 
1156     auto config_parser = new ConfigParser();
1157     config_parser.parseString(config_text);
1158 
1159     class SingleValues
1160     {
1161         istring str;
1162         int integer;
1163         float pi;
1164         uint default_value = 99;
1165     }
1166 
1167     struct SingleValuesStruct
1168     {
1169         istring str;
1170         int integer;
1171         float pi;
1172         uint default_value = 99;
1173     }
1174 
1175     auto single_values = new SingleValues();
1176 
1177     readFields("Section", single_values, config_parser);
1178     test!("==")(single_values.str, "I'm a string");
1179     test!("==")(single_values.integer, -300);
1180     test!("==")(single_values.pi, cast(float)3.14);
1181     test!("==")(single_values.default_value, 99);
1182 
1183     SingleValuesStruct single_values_struct;
1184 
1185     readFields("Section", single_values_struct, config_parser);
1186     test!("==")(single_values_struct.str, "I'm a string");
1187     test!("==")(single_values_struct.integer, -300);
1188     test!("==")(single_values_struct.pi, cast(float)3.14);
1189     test!("==")(single_values_struct.default_value, 99);
1190 
1191     auto single_values_fill = fill!(SingleValuesStruct)("Section", config_parser);
1192     test!("==")(single_values_fill.str, "I'm a string");
1193     test!("==")(single_values_fill.integer, -300);
1194     test!("==")(single_values_fill.pi, cast(float)3.14);
1195     test!("==")(single_values_fill.default_value, 99);
1196 }
1197 
1198 unittest
1199 {
1200     static immutable config_text =
1201 `
1202 [Section]
1203 str = I'm a mutable string
1204 `;
1205 
1206     auto config_parser = new ConfigParser();
1207     config_parser.parseString(config_text);
1208 
1209     class MutString
1210     {
1211         mstring str;
1212     }
1213 
1214     auto mut_string = new MutString();
1215 
1216     readFields("Section", mut_string, config_parser);
1217     test!("==")(mut_string.str, "I'm a mutable string");
1218 }
1219 
1220 unittest
1221 {
1222     static immutable config_text =
1223 `
1224 [SectionArray]
1225 string_arr = Hello
1226          World
1227 int_arr = 30
1228       40
1229       -60
1230       1111111111
1231       0x10
1232 ulong_arr = 0
1233         50
1234         18446744073709551615
1235         0xa123bcd
1236 float_arr = 10.2
1237         -25.3
1238         90
1239         0.000000001
1240 `;
1241 
1242     auto config_parser = new ConfigParser();
1243     config_parser.parseString(config_text);
1244 
1245     class ArrayValues
1246     {
1247         istring[] string_arr;
1248         int[] int_arr;
1249         ulong[] ulong_arr;
1250         float[] float_arr;
1251     }
1252 
1253     auto array_values = new ArrayValues();
1254     readFields("SectionArray", array_values, config_parser);
1255     test!("==")(array_values.string_arr, ["Hello", "World"]);
1256     test!("==")(array_values.int_arr, [30, 40, -60, 1111111111, 0x10]);
1257     ulong[] ulong_array = [0, 50, ulong.max, 0xa123bcd];
1258     test!("==")(array_values.ulong_arr, ulong_array);
1259     float[] float_array = [10.2, -25.3, 90, 0.000000001];
1260     test!("==")(array_values.float_arr, float_array);
1261 
1262     // Make sure it works on lvalues as well
1263     Object o = new ArrayValues();
1264     readFields("SectionArray", cast(ArrayValues)o, config_parser);
1265 
1266     array_values = cast(ArrayValues)o;
1267     test!("==")(array_values.string_arr, ["Hello", "World"]);
1268     test!("==")(array_values.int_arr, [30, 40, -60, 1111111111, 0x10]);
1269     test!("==")(array_values.ulong_arr, ulong_array);
1270     test!("==")(array_values.float_arr, float_array);
1271 
1272     struct ArrayValuesStruct
1273     {
1274         istring[] string_arr;
1275         int[] int_arr;
1276         ulong[] ulong_arr;
1277         float[] float_arr;
1278     }
1279 
1280     ArrayValuesStruct array_struct;
1281     readFields("SectionArray", array_struct, config_parser);
1282     test!("==")(array_struct.string_arr, ["Hello", "World"]);
1283     test!("==")(array_struct.int_arr, [30, 40, -60, 1111111111, 0x10]);
1284     test!("==")(array_struct.ulong_arr, ulong_array);
1285     test!("==")(array_struct.float_arr, float_array);
1286 }
1287 
1288 version (unittest)
1289 {
1290     import ocean.io.Stdout;
1291     import ConfigFiller = ocean.util.config.ConfigFiller;
1292     import ocean.util.config.ConfigParser;
1293 
1294 }
1295 
1296 ///
1297 unittest
1298 {
1299     /*
1300         Config file for the example below:
1301 
1302         [Example.FirstGroup]
1303         number = 1
1304         required_string = SET
1305         was_this_set = "there, I set it!"
1306         limited = 20
1307 
1308         [Example.SecondGroup]
1309         number = 2
1310         required_string = SET_AGAIN
1311 
1312         [Example.ThirdGroup]
1313         number = 3
1314         required_string = SET
1315         was_this_set = "arrr"
1316         limited = 40
1317     */
1318 
1319     static struct ConfigParameters
1320     {
1321         int number;
1322         ConfigFiller.Required!(char[]) required_string;
1323         ConfigFiller.SetInfo!(char[]) was_this_set;
1324         ConfigFiller.Required!(ConfigFiller.MinMax!(size_t, 1, 30)) limited;
1325         ConfigFiller.Limit!(cstring, "one", "two", "three") limited_set;
1326         ConfigFiller.LimitInit!(cstring, "one", "one", "two", "three") limited_set_with_default;
1327     }
1328 
1329     void parseConfig ( char[][] argv )
1330     {
1331         scope config = new ConfigParser();
1332         config.parseFile(argv[1].dup);
1333 
1334         auto iter = ConfigFiller.iterate!(ConfigParameters)("Example", config);
1335         foreach ( name, conf; iter ) try
1336         {
1337             // Outputs FirstGroup/SecondGroup/ThirdGroup
1338             Stdout.formatln("Group: {}", name);
1339             Stdout.formatln("Number: {}", conf.number);
1340             Stdout.formatln("Required: {}", conf.required_string());
1341             if ( conf.was_this_set.set )
1342             {
1343                 Stdout.formatln("It was set! And the value is {}",
1344                     conf.was_this_set());
1345             }
1346             // If limited was not set, an exception will be thrown
1347             // If limited was set but is outside of the specified
1348             // range [1 .. 30], an exception will be thrown as well
1349             Stdout.formatln("Limited: {}", conf.limited());
1350             // If limited_set is not a value in the given set ("one", "two",
1351             // "three"), an exception will be thrown
1352             Stdout.formatln("Limited_set: {}", conf.limited_set());
1353             // If limited_set is not a value in the given set ("one", "two",
1354             // "three"), an exception will be thrown, if it is not set, it
1355             // defaults to "one"
1356             Stdout.formatln("Limited_set_with_default: {}",
1357                              conf.limited_set_with_default());
1358         }
1359         catch ( Exception e )
1360         {
1361             Stdout.formatln("Required parameter wasn't set: {}", e.message());
1362         }
1363     }
1364 }