1 /****************************************************************************** 2 3 Manages a set of parameters where each parameter is a string key/value pair. 4 5 Wraps an associative array serving as map of parameter key and value 6 strings. 7 The parameter keys are set on instantiation; that is, a key list is passed 8 to the constructor. The keys cannot be changed, added or removed later by 9 ParamSet. However, a subclass can add keys. 10 All methods that accept a key handle the key case insensitively (except the 11 constructor). When keys are output, the original keys are used. 12 Note that keys and values are meant to slice string buffers in a subclass or 13 external to this class. 14 15 Build note: Requires linking against libglib-2.0: add 16 17 -L-lglib-2.0 18 19 to the DMD build parameters. 20 21 Copyright: 22 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 23 All rights reserved. 24 25 License: 26 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 27 Alternatively, this file may be distributed under the terms of the Tango 28 3-Clause BSD License (see LICENSE_BSD.txt for details). 29 30 ******************************************************************************/ 31 32 module ocean.net.util.ParamSet; 33 34 35 import ocean.meta.types.Qualifiers; 36 37 import ocean.core.TypeConvert; 38 39 import ocean.core.Verify; 40 41 import ocean.text.util.SplitIterator: ISplitIterator; 42 import ocean.text.convert.Formatter; 43 44 import core.stdc.ctype: tolower; 45 46 version (unittest) import ocean.core.Test; 47 48 /****************************************************************************** 49 50 Compares each of the the first n characters in s1 and s2 in a 51 case-insensitive manner. 52 53 @see http://www.gtk.org/api/2.6/glib/glib-String-Utility-Functions.html#g-ascii-strncasecmp 54 55 Params: 56 s1 = string to compare each of the first n characters against those in s2 57 s2 = string to compare each of the first n characters against those in s1 58 n = number of characters to compare 59 60 Returns: 61 an integer less than, equal to, or greater than zero if the first n 62 characters of s1 is found, respectively, to be less than, to match, or 63 to be greater than the first n characters of s2 64 65 ******************************************************************************/ 66 67 extern (C) private int g_ascii_strncasecmp ( const(char)* s1, const(char)* s2, size_t n ); 68 69 /******************************************************************************/ 70 71 class ParamSet 72 { 73 struct Element 74 { 75 cstring key, val; 76 } 77 78 /************************************************************************** 79 80 Set to true to skip key/value pairs with a null value on 'foreach' 81 iteration. 82 83 **************************************************************************/ 84 85 public bool skip_null_values_on_iteration = false; 86 87 /************************************************************************** 88 89 Minimum required buffer length for decimal formatting of an uint value 90 91 **************************************************************************/ 92 93 public static immutable ulong_dec_length = ulong.max.stringof.length; 94 95 /************************************************************************** 96 97 Key/value map of the parameter set 98 99 Keys are the parameter keys in lower case, values are structs containing 100 the original key and the parameter value. The value stored in the struct 101 is set to null initially and by reset(). 102 103 **************************************************************************/ 104 105 private Element[istring] paramset; 106 107 /************************************************************************** 108 109 Reused buffer for case conversion 110 111 **************************************************************************/ 112 113 private mstring tolower_buf; 114 115 /************************************************************************** 116 117 Obtains the parameter value corresponding to key. key must be one of 118 the parameter keys passed on instantiation or added by a subclass. 119 120 Params: 121 key = parameter key (case insensitive) 122 123 Returns: 124 parameter value; null indicates that no value is currently set for 125 this key 126 127 Throws: 128 Behaves like regular associative array indexing. 129 130 **************************************************************************/ 131 132 cstring opIndex ( cstring key ) 133 { 134 key = this.tolower(key); 135 auto value = key in this.paramset; 136 verify( 137 value !is null, 138 format("Key '{}' is not found in ParamSet", key) 139 ); 140 141 return value.val; 142 } 143 144 /************************************************************************** 145 146 Obtains the parameter value corresponding to key. 147 148 Params: 149 key = parameter key (case insensitive) 150 151 Returns: 152 pointer to the corresponding parameter value or null if the key is 153 unknown. A pointer to null indicates that no value is currently set 154 for this key. 155 156 **************************************************************************/ 157 158 cstring* opIn_r ( cstring key ) 159 { 160 Element* element = this.get_(key); 161 162 return element? &element.val : null; 163 } 164 165 /************************************************************************** 166 167 Obtains the parameter value corresponding to key, bundled with the 168 original key. 169 170 Params: 171 key = parameter key (case insensitive) 172 173 Returns: 174 Struct containing original key and parameter value or null for key 175 and value if the key was not found. A non-null key with a null value 176 indicates that no value is currently set for this key. 177 178 **************************************************************************/ 179 180 Element getElement ( cstring key ) 181 out (element) 182 { 183 assert (element.key || !element.val); 184 } 185 do 186 { 187 Element* element = this.get_(key); 188 189 return element? *element : Element.init; 190 } 191 192 /************************************************************************** 193 194 Obtains the parameter value corresponding to key which is expected to be 195 an unsigned decimal integer number and not empty. key must be one of the 196 parameter keys passed on instantiation or added by a subclass. 197 198 Params: 199 key = parameter key (case insensitive) 200 n = result destination; will be changed only if a value exists 201 for key 202 is_set = will be changed to true if a value exists for key (even if 203 it is empty or not an unsigned decimal integer number) 204 205 Returns: 206 true on success or false if either no value exists for key or the 207 value is empty or not an unsigned decimal integer number or. 208 209 Throws: 210 Behaves like regular associative array indexing using key as key. 211 212 **************************************************************************/ 213 214 bool getUnsigned ( T : ulong ) ( cstring key, ref T n, out bool is_set ) 215 { 216 cstring val = this[key]; 217 218 is_set = val !is null; 219 220 return is_set? !this.readUnsigned(val, n).length && val.length : false; 221 } 222 223 /************************************************************************** 224 225 ditto 226 227 **************************************************************************/ 228 229 bool getUnsigned ( T : ulong ) ( cstring key, ref T n ) 230 { 231 cstring val = this[key]; 232 233 return val.length? !this.readUnsigned(val, n).length : false; 234 } 235 236 237 /************************************************************************** 238 239 Sets the parameter value for key. Key must be one of the parameter keys 240 passed on instantiation or added by a subclass. 241 242 Params: 243 val = parameter value (will be sliced) 244 key = parameter key (case insensitive) 245 246 Returns: 247 val 248 249 Throws: 250 Asserts that key is one of the parameter keys passed on 251 instantiation or added by a subclass. 252 253 **************************************************************************/ 254 255 cstring opIndexAssign ( cstring val, cstring key ) 256 { 257 Element* element = this.get_(key); 258 259 verify(element !is null, "cannot assign to unknown key \"" 260 ~ idup(key) ~ "\""); 261 262 return element.val = val; 263 } 264 265 /************************************************************************** 266 267 Sets the parameter value for key if key is one of the parameter keys 268 passed on instantiation or added by a subclass. 269 270 Params: 271 key = parameter key (case insensitive) 272 val = parameter value (will be sliced) 273 274 Returns: 275 true if key is one of parameter keys passed on instantiation or 276 added by a subclass or false otherwise. In case of false nothing has 277 changed. 278 279 **************************************************************************/ 280 281 bool set ( cstring key, cstring val ) 282 { 283 return this.access(key, (cstring, ref cstring dst){dst = val;}); 284 } 285 286 /************************************************************************** 287 288 ditto 289 290 Params: 291 key = parameter key (case insensitive) 292 val = parameter value 293 dec = number to string conversion buffer, a slice will be associated 294 as string value for key 295 296 Returns: 297 true if key is one of parameter keys passed on instantiation or 298 false otherwise. In case of false nothing has changed. 299 300 **************************************************************************/ 301 302 bool set ( cstring key, size_t val, mstring dec ) 303 { 304 return this.access(key, (cstring, ref cstring dst) 305 { 306 dst = this.writeUnsigned(dec, val); 307 }); 308 } 309 310 /************************************************************************** 311 312 Invokes dg with the original key and a reference to the parameter value 313 for key if key is one of parameter keys passed on instantiation or added 314 by a subclass. 315 316 Params: 317 key = parameter key (case insensitive) 318 dg = callback delegate 319 320 Returns: 321 true if key is one of the parameter keys passed on instantiation or 322 added by a subclass or false otherwise. In case of false dg was not 323 invoked. 324 325 **************************************************************************/ 326 327 bool access ( cstring key, scope void delegate ( cstring key, ref cstring val ) dg ) 328 { 329 Element* element = this.get_(key); 330 331 if (element) 332 { 333 dg(element.key, element.val); 334 } 335 336 return element !is null; 337 } 338 339 /************************************************************************** 340 341 Compares the parameter value corresponding to key with val in a 342 case-insensitive manner. 343 344 Params: 345 key = parameter key (case insensitive) 346 val = parameter value (case insensitive) 347 348 Returns: 349 true if a parameter for key exists and its value case-insensitively 350 equals val. 351 352 **************************************************************************/ 353 354 bool matches ( cstring key, cstring val ) 355 { 356 Element* element = this.get_(key); 357 358 return element? 359 (element.val.length == val.length) && 360 !this.strncasecmp(element.val, val) : 361 false; 362 } 363 364 /************************************************************************** 365 366 'foreach' iteration over parameter key/value pairs 367 368 **************************************************************************/ 369 370 public int opApply ( scope int delegate ( ref cstring key, ref cstring val ) dg ) 371 { 372 int result = 0; 373 374 foreach (ref element; this.paramset) 375 { 376 this.iterate(element, dg, result); 377 378 if (result) break; 379 } 380 381 return result; 382 } 383 384 /************************************************************************** 385 386 Resets all parameter values to null. 387 388 **************************************************************************/ 389 390 public void reset ( ) 391 { 392 foreach (ref element; this.paramset) 393 { 394 element.val = null; 395 } 396 } 397 398 /************************************************************************** 399 400 Compares a to b, treating ASCII characters case-insensitively. If a and 401 b have a different length, the first common characters are compared. If 402 these are equal, the longer string compares greater. 403 404 To see if the content of a and b is the same, use 405 406 --- 407 (a.length == b.length) && !strncasecmp (a, b) 408 --- 409 . 410 411 Treats null strings like empty strings. 412 413 Params: 414 a = string to compare against b 415 b = string to compare against a 416 417 Returns: 418 a value greater than 0 if a compares greater than b, less than 0 419 if a compares less than b, or 0 if a and b are of equal length and 420 all characters are equal. 421 422 **************************************************************************/ 423 424 public static int strncasecmp ( cstring a, cstring b ) 425 { 426 if ( a.length && b.length ) 427 { 428 if ( a.length == b.length ) 429 { 430 return g_ascii_strncasecmp(a.ptr, b.ptr, a.length); 431 } 432 else 433 { 434 bool a_is_shorter = a.length < b.length; 435 int c = g_ascii_strncasecmp(a.ptr, b.ptr, 436 a_is_shorter? a.length : b.length); 437 return c? c : a_is_shorter? -1 : 1; 438 } 439 } 440 else 441 { 442 return (a.length < b.length)? -1 : 443 (a.length > b.length)? 1 : 444 0; 445 } 446 } 447 448 unittest 449 { 450 test(strncasecmp("a", "b") < 0); 451 test(strncasecmp("b", "a") > 0); 452 test(strncasecmp("hello", "hello") == 0); 453 test(strncasecmp("hello", "Hello") == 0); 454 test(strncasecmp("hello", "HELLO") == 0); 455 test(strncasecmp("hello", "hello there") < 0); 456 test(strncasecmp("hello there", "hello") > 0); 457 test(strncasecmp("", "hell0") < 0); 458 test(strncasecmp("hello", "") > 0); 459 test(strncasecmp("", "") == 0); 460 } 461 462 /************************************************************************** 463 464 Adds an entry for key. 465 466 Params: 467 keys = parameter key to add (will be duplicated) 468 469 **************************************************************************/ 470 471 protected void addKeys ( in cstring[] keys ... ) 472 { 473 foreach (key; keys) 474 { 475 this.addKey(key); 476 } 477 } 478 479 /************************************************************************** 480 481 Adds an entry for key. 482 483 Params: 484 key = parameter key to add (will be duplicated) 485 486 **************************************************************************/ 487 488 protected cstring addKey ( cstring key ) 489 { 490 mstring lower_key = this.tolower(key); 491 492 if (!(lower_key in this.paramset)) 493 { 494 this.paramset[idup(lower_key)] = Element(key); 495 } 496 497 return lower_key; 498 } 499 500 /************************************************************************** 501 502 Looks up key in a case-insensitive manner. 503 504 Params: 505 key = parameter key 506 507 Returns: 508 - Pointer to a a struct which contains the original key and the 509 parameter value, where a null value indicates that no value is 510 currently set for this key, or 511 - null if the key was not found. 512 513 **************************************************************************/ 514 515 protected Element* get_ ( cstring key ) 516 out (element) 517 { 518 if (element) 519 assert (element.key || !element.val); 520 } 521 do 522 { 523 return this.tolower(key) in this.paramset; 524 } 525 526 /************************************************************************** 527 528 Converts key to lower case, writing to a separate buffer so that key is 529 left untouched. 530 531 Params: 532 key = key to convert to lower case 533 534 Returns: 535 result (references an internal buffer) 536 537 **************************************************************************/ 538 539 protected mstring tolower ( cstring key ) 540 { 541 if (this.tolower_buf.length < key.length) 542 { 543 this.tolower_buf.length = key.length; 544 } 545 546 foreach (i, c; key) 547 { 548 this.tolower_buf[i] = castFrom!(int).to!(char)(.tolower(c)); 549 } 550 551 return this.tolower_buf[0 .. key.length]; 552 } 553 554 /************************************************************************** 555 556 Rehashes the associative array. 557 558 **************************************************************************/ 559 560 protected void rehash ( ) 561 { 562 this.paramset.rehash; 563 } 564 565 /************************************************************************** 566 567 opApply() helper, invokes dg with element.key & val. 568 569 Params: 570 element = element currently iterating over 571 dg = opApply() iteration delegate 572 result = set to dg() return value if dg is invoked, remains 573 unchanged otherwise. 574 575 **************************************************************************/ 576 577 final protected void iterate ( ref Element element, 578 scope int delegate ( ref cstring key, ref cstring val ) dg, 579 ref int result ) 580 { 581 with (element) if (val || !this.skip_null_values_on_iteration) 582 { 583 result = dg(key, val); 584 } 585 } 586 587 /************************************************************************** 588 589 Converts n to decimal representation, writing to dst. dst must be long 590 enough to hold the result. The result will be written to the end of dst, 591 returning a slice to the valid content in dst. 592 593 Params: 594 dst = destination string 595 n = number to convert to decimal representation 596 597 Returns: 598 result (dst) 599 600 **************************************************************************/ 601 602 protected static mstring writeUnsigned ( mstring dst, ulong n ) 603 out (dec) 604 { 605 assert (!n); 606 assert (&dec[$ - 1] is &dst[$ - 1]); 607 } 608 do 609 { 610 foreach_reverse (i, ref c; dst) 611 { 612 ulong quot = n / 10; 613 614 c = castFrom!(ulong).to!(char)(n - (quot * 10) + '0'); 615 n = quot; 616 617 if (!n) 618 { 619 return dst[i .. $]; 620 } 621 } 622 623 assert(false); 624 } 625 626 unittest 627 { 628 char[ulong_dec_length] dec; 629 630 test (writeUnsigned(dec, 4711) == "4711"); 631 test (writeUnsigned(dec, 0) == "0"); 632 633 test (writeUnsigned(dec, uint.max) == "4294967295"); 634 test (writeUnsigned(dec, ulong.max) == "18446744073709551615"); 635 636 test (strncasecmp("", "a") < 0); 637 638 } 639 640 /************************************************************************** 641 642 Converts str, which is expected to contain a decimal number, to the 643 number it represents. Tailing and leading whitespace is allowed and will 644 be trimmed. If src contains non-decimal digit characters after trimming, 645 conversion will be stopped at the first non-decimal digit character. 646 647 Example: 648 649 --- 650 651 uint n; 652 653 cstring remaining = readUnsigned(" 123abc45 ", n); 654 655 // n is now 123 656 // remaining is now "abc45" 657 658 --- 659 660 Params: 661 src = source string 662 x = result output 663 664 Returns: 665 slice of src starting with the first character that is not a decimal 666 digit or an empty string if src contains only decimal digits 667 668 **************************************************************************/ 669 670 protected static cstring readUnsigned ( T : ulong ) ( cstring src, out T x ) 671 in 672 { 673 static assert (T.init == 0, "initial value of type \"" ~ T.stringof ~ "\" is " ~ T.init.stringof ~ " (need 0)"); 674 static assert (cast (T) (T.max + 1) < T.max); // ensure overflow checking works 675 } 676 do 677 { 678 cstring trimmed = ISplitIterator.trim(src); 679 680 foreach (i, c; trimmed) 681 { 682 if ('0' <= c && c <= '9') 683 { 684 T y = x * 10 + (c - '0'); 685 686 if (y >= x) // overflow checking 687 { 688 x = y; 689 continue; 690 } 691 } 692 693 return trimmed[i .. $]; 694 } 695 696 return src? src[$ .. $] : null; 697 } 698 }