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