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 }