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.transition;
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] =
971                     config.getListStrict!(SliceIfD1StaticArray!(U))(group, key);
972             }
973             else
974             {
975                 reference.tupleof[si] =
976                     config.getStrict!(SliceIfD1StaticArray!(Type))(group, key);
977             }
978 
979             debug (Config) Stdout.formatln("Config Debug: {}.{} = {}", group,
980                              reference.tupleof[si]
981                             .stringof["reference.".length  .. $],
982                             Value(reference.tupleof[si]));
983 
984             static if ( !is (Type == typeof(field)) )
985             {
986                 reference.tupleof[si].check(true, group, key);
987             }
988         }
989         else
990         {
991             debug (Config) Stdout.formatln("Config Debug: {}.{} = {} (builtin)", group,
992                              reference.tupleof[si]
993                             .stringof["reference.".length  .. $],
994                             Value(reference.tupleof[si]));
995 
996             static if ( !is (Type == typeof(field)) )
997             {
998                 reference.tupleof[si].check(false, group, key);
999             }
1000         }
1001     }
1002 
1003     // Recurse into super any classes
1004     static if ( is(T S == super ) )
1005     {
1006         foreach ( G; S ) static if ( !is(G == Object) )
1007         {
1008             readFields!(G)(group, cast(G) reference, config);
1009         }
1010     }
1011 }
1012 
1013 /*******************************************************************************
1014 
1015     Fills the fields of the `reference` from config file's group.
1016 
1017     Params:
1018         T  = type of the class to fill
1019         Source = source to use
1020         group = group to read fields from
1021         reference = reference to the object to be filled
1022         config = instance of the source to use
1023 
1024 *******************************************************************************/
1025 
1026 package void readFields ( T : Object, Source )
1027                           ( cstring group, T reference, Source config )
1028 {
1029     // Workaround to work on both l- and r-values
1030     T tmp = reference;
1031     readFieldsImpl(group, tmp, config);
1032 }
1033 
1034 /*******************************************************************************
1035 
1036     Fills the fields of the `reference` from config file's group.
1037 
1038     Params:
1039         T  = type of the aggregate to fill
1040         Source = source to use
1041         group = group to read fields from
1042         reference = reference to the object to be filled
1043         config = instance of the source to use
1044 
1045 *******************************************************************************/
1046 
1047 package void readFields ( T, Source )
1048                           ( cstring group, ref T reference, Source config )
1049 {
1050     readFieldsImpl(group, reference, config);
1051 }
1052 
1053 version ( UnitTest )
1054 {
1055     class SolarSystemEntity
1056     {
1057         uint radius;
1058         uint circumference;
1059     }
1060 
1061     struct SolarSystemEntityStruct
1062     {
1063         uint radius;
1064         uint circumference;
1065     }
1066 
1067     auto config_str =
1068 `
1069 [SUN.earth]
1070 radius = 6371
1071 circumference = 40075
1072 
1073 [SUN-andromeda]
1074 lunch = dessert_place
1075 
1076 [SUN_wannabe_solar_system_entity]
1077 radius = 4525
1078 circumference = 35293
1079 
1080 [SUN.earth.moon]
1081 radius = 1737
1082 circumference = 10921
1083 
1084 [SUNBLACKHOLE]
1085 shoe_size = 42
1086 `;
1087 }
1088 
1089 unittest
1090 {
1091     auto config_parser = new ConfigParser();
1092 
1093     config_parser.parseString(config_str);
1094 
1095     auto iter = iterate!(SolarSystemEntity)("SUN", config_parser);
1096 
1097     SolarSystemEntity entity_details;
1098 
1099     foreach ( entity; iter )
1100     {
1101         test((entity == "earth") || (entity == "earth.moon"),
1102             "'" ~ entity ~ "' is neither 'earth' nor 'earth.moon'");
1103 
1104         iter.fill(entity, entity_details);
1105 
1106         if (entity == "earth")
1107         {
1108             test!("==")(entity_details.radius, 6371);
1109             test!("==")(entity_details.circumference, 40075);
1110         }
1111         else // if (entity == "earth.moon")
1112         {
1113             test!("==")(entity_details.radius, 1737);
1114             test!("==")(entity_details.circumference, 10921);
1115         }
1116     }
1117 }
1118 
1119 unittest
1120 {
1121     auto config_parser = new ConfigParser();
1122 
1123     config_parser.parseString(config_str);
1124 
1125     auto iter = iterate!(SolarSystemEntityStruct)("SUN", config_parser);
1126 
1127     SolarSystemEntityStruct entity_details;
1128 
1129     foreach ( entity; iter )
1130     {
1131         test((entity == "earth") || (entity == "earth.moon"),
1132             "'" ~ entity ~ "' is neither 'earth' nor 'earth.moon'");
1133 
1134         iter.fill(entity, entity_details);
1135 
1136         if (entity == "earth")
1137         {
1138             test!("==")(entity_details.radius, 6371);
1139             test!("==")(entity_details.circumference, 40075);
1140         }
1141         else // if (entity == "earth.moon")
1142         {
1143             test!("==")(entity_details.radius, 1737);
1144             test!("==")(entity_details.circumference, 10921);
1145         }
1146     }
1147 }
1148 unittest
1149 {
1150     static immutable config_text =
1151 `
1152 [Section]
1153 str = I'm a string
1154 integer = -300
1155 pi = 3.14
1156 `;
1157 
1158     auto config_parser = new ConfigParser();
1159     config_parser.parseString(config_text);
1160 
1161     class SingleValues
1162     {
1163         istring str;
1164         int integer;
1165         float pi;
1166         uint default_value = 99;
1167     }
1168 
1169     struct SingleValuesStruct
1170     {
1171         istring str;
1172         int integer;
1173         float pi;
1174         uint default_value = 99;
1175     }
1176 
1177     auto single_values = new SingleValues();
1178 
1179     readFields("Section", single_values, config_parser);
1180     test!("==")(single_values.str, "I'm a string");
1181     test!("==")(single_values.integer, -300);
1182     test!("==")(single_values.pi, cast(float)3.14);
1183     test!("==")(single_values.default_value, 99);
1184 
1185     SingleValuesStruct single_values_struct;
1186 
1187     readFields("Section", single_values_struct, config_parser);
1188     test!("==")(single_values_struct.str, "I'm a string");
1189     test!("==")(single_values_struct.integer, -300);
1190     test!("==")(single_values_struct.pi, cast(float)3.14);
1191     test!("==")(single_values_struct.default_value, 99);
1192 
1193     auto single_values_fill = fill!(SingleValuesStruct)("Section", config_parser);
1194     test!("==")(single_values_fill.str, "I'm a string");
1195     test!("==")(single_values_fill.integer, -300);
1196     test!("==")(single_values_fill.pi, cast(float)3.14);
1197     test!("==")(single_values_fill.default_value, 99);
1198 }
1199 
1200 unittest
1201 {
1202     static immutable config_text =
1203 `
1204 [Section]
1205 str = I'm a mutable string
1206 `;
1207 
1208     auto config_parser = new ConfigParser();
1209     config_parser.parseString(config_text);
1210 
1211     class MutString
1212     {
1213         mstring str;
1214     }
1215 
1216     auto mut_string = new MutString();
1217 
1218     readFields("Section", mut_string, config_parser);
1219     test!("==")(mut_string.str, "I'm a mutable string");
1220 }
1221 
1222 unittest
1223 {
1224     static immutable config_text =
1225 `
1226 [SectionArray]
1227 string_arr = Hello
1228          World
1229 int_arr = 30
1230       40
1231       -60
1232       1111111111
1233       0x10
1234 ulong_arr = 0
1235         50
1236         18446744073709551615
1237         0xa123bcd
1238 float_arr = 10.2
1239         -25.3
1240         90
1241         0.000000001
1242 `;
1243 
1244     auto config_parser = new ConfigParser();
1245     config_parser.parseString(config_text);
1246 
1247     class ArrayValues
1248     {
1249         istring[] string_arr;
1250         int[] int_arr;
1251         ulong[] ulong_arr;
1252         float[] float_arr;
1253     }
1254 
1255     auto array_values = new ArrayValues();
1256     readFields("SectionArray", array_values, config_parser);
1257     test!("==")(array_values.string_arr, ["Hello", "World"]);
1258     test!("==")(array_values.int_arr, [30, 40, -60, 1111111111, 0x10]);
1259     ulong[] ulong_array = [0, 50, ulong.max, 0xa123bcd];
1260     test!("==")(array_values.ulong_arr, ulong_array);
1261     float[] float_array = [10.2, -25.3, 90, 0.000000001];
1262     test!("==")(array_values.float_arr, float_array);
1263 
1264     // Make sure it works on lvalues as well
1265     Object o = new ArrayValues();
1266     readFields("SectionArray", cast(ArrayValues)o, config_parser);
1267 
1268     array_values = cast(ArrayValues)o;
1269     test!("==")(array_values.string_arr, ["Hello", "World"]);
1270     test!("==")(array_values.int_arr, [30, 40, -60, 1111111111, 0x10]);
1271     test!("==")(array_values.ulong_arr, ulong_array);
1272     test!("==")(array_values.float_arr, float_array);
1273 
1274     struct ArrayValuesStruct
1275     {
1276         istring[] string_arr;
1277         int[] int_arr;
1278         ulong[] ulong_arr;
1279         float[] float_arr;
1280     }
1281 
1282     ArrayValuesStruct array_struct;
1283     readFields("SectionArray", array_struct, config_parser);
1284     test!("==")(array_struct.string_arr, ["Hello", "World"]);
1285     test!("==")(array_struct.int_arr, [30, 40, -60, 1111111111, 0x10]);
1286     test!("==")(array_struct.ulong_arr, ulong_array);
1287     test!("==")(array_struct.float_arr, float_array);
1288 }
1289 
1290 version (UnitTest)
1291 {
1292     import ocean.io.Stdout;
1293     import ConfigFiller = ocean.util.config.ConfigFiller;
1294     import ocean.util.config.ConfigParser;
1295 
1296 }
1297 
1298 ///
1299 unittest
1300 {
1301     /*
1302         Config file for the example below:
1303 
1304         [Example.FirstGroup]
1305         number = 1
1306         required_string = SET
1307         was_this_set = "there, I set it!"
1308         limited = 20
1309 
1310         [Example.SecondGroup]
1311         number = 2
1312         required_string = SET_AGAIN
1313 
1314         [Example.ThirdGroup]
1315         number = 3
1316         required_string = SET
1317         was_this_set = "arrr"
1318         limited = 40
1319     */
1320 
1321     static struct ConfigParameters
1322     {
1323         int number;
1324         ConfigFiller.Required!(char[]) required_string;
1325         ConfigFiller.SetInfo!(char[]) was_this_set;
1326         ConfigFiller.Required!(ConfigFiller.MinMax!(size_t, 1, 30)) limited;
1327         ConfigFiller.Limit!(cstring, "one", "two", "three") limited_set;
1328         ConfigFiller.LimitInit!(cstring, "one", "one", "two", "three") limited_set_with_default;
1329     }
1330 
1331     void parseConfig ( char[][] argv )
1332     {
1333         scope config = new ConfigParser();
1334         config.parseFile(argv[1].dup);
1335 
1336         auto iter = ConfigFiller.iterate!(ConfigParameters)("Example", config);
1337         foreach ( name, conf; iter ) try
1338         {
1339             // Outputs FirstGroup/SecondGroup/ThirdGroup
1340             Stdout.formatln("Group: {}", name);
1341             Stdout.formatln("Number: {}", conf.number);
1342             Stdout.formatln("Required: {}", conf.required_string());
1343             if ( conf.was_this_set.set )
1344             {
1345                 Stdout.formatln("It was set! And the value is {}",
1346                     conf.was_this_set());
1347             }
1348             // If limited was not set, an exception will be thrown
1349             // If limited was set but is outside of the specified
1350             // range [1 .. 30], an exception will be thrown as well
1351             Stdout.formatln("Limited: {}", conf.limited());
1352             // If limited_set is not a value in the given set ("one", "two",
1353             // "three"), an exception will be thrown
1354             Stdout.formatln("Limited_set: {}", conf.limited_set());
1355             // If limited_set is not a value in the given set ("one", "two",
1356             // "three"), an exception will be thrown, if it is not set, it
1357             // defaults to "one"
1358             Stdout.formatln("Limited_set_with_default: {}",
1359                              conf.limited_set_with_default());
1360         }
1361         catch ( Exception e )
1362         {
1363             Stdout.formatln("Required parameter wasn't set: {}", e.message());
1364         }
1365     }
1366 }