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