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 }