1 // Written in the D programming language. 2 3 /** 4 5 This module contains implementation of settings container. 6 7 Similar to JSON, can be written/read to/from JSON. 8 9 Difference from usual JSON implementations: map (object) is ordered - will be written in the same order as read (or created). 10 11 Has a lot of methods for convenient storing/accessing of settings. 12 13 14 Synopsis: 15 16 ---- 17 import dlangui.core.settings; 18 19 Setting s = new Setting(); 20 21 ---- 22 23 Copyright: Vadim Lopatin, 2014 24 License: Boost License 1.0 25 Authors: Vadim Lopatin, coolreader.org@gmail.com 26 */ 27 module dlangui.core.settings; 28 29 import dlangui.core.logger; 30 import dlangui.core.types : parseHexDigit; 31 public import dlangui.core.parseutils; 32 import std.range; 33 //import std.algorithm : clamp, equal; 34 import std.algorithm : equal; 35 import std.conv : to; 36 import std.utf : encode; 37 import std.math : pow; 38 import std.file; 39 import std.path; 40 import std.datetime : SysTime; 41 42 /// setting types - same as in std.json 43 enum SettingType { 44 STRING, 45 INTEGER, 46 UINTEGER, 47 FLOAT, 48 OBJECT, 49 ARRAY, 50 TRUE, 51 FALSE, 52 NULL 53 } 54 55 /// settings file format 56 enum SettingsFileFormat { 57 JSON, 58 SDL, 59 } 60 61 /// Settings object whith file information 62 class SettingsFile { 63 protected Setting _setting; 64 protected string _filename; 65 protected SysTime _lastModificationTime; 66 protected bool _loaded; 67 protected SettingsFileFormat _format = SettingsFileFormat.JSON; 68 69 @property Setting setting() { return _setting; } 70 @property Setting copySettings() { 71 return _setting.clone(); 72 } 73 /// replace setting object 74 void replaceSetting(Setting s) { 75 _setting = s; 76 } 77 @property void applySettings(Setting settings) { 78 // TODO copy only changed settings 79 _setting = settings; 80 //_setting.apply(settings); 81 } 82 alias setting this; 83 84 /// create settings file object; if filename is provided, attempts to load settings from file 85 this(string filename = null) { 86 _setting = new Setting(); 87 _filename = filename; 88 if (_filename) { 89 string dir = dirName(_filename); 90 if (load()) { 91 // loaded ok 92 } else { 93 } 94 } 95 } 96 97 static int limitInt(long value, int minvalue, int maxvalue) { 98 if (value < minvalue) 99 return minvalue; 100 if (value > maxvalue) 101 return maxvalue; 102 return cast(int)value; 103 // remove clamp to support older compilers 104 //return clamp(cast(int)value, minvalue, maxvalue); 105 } 106 107 static string limitString(string value, const string[] values) 108 in { assert(values.length > 0); } 109 body { 110 foreach(v; values) 111 if (v.equal(value)) 112 return value; 113 return values[0]; 114 } 115 116 117 @property bool loaded() { 118 return _loaded; 119 } 120 121 /// filename 122 @property string filename() { return _filename; } 123 /// filename 124 @property void filename(string fn) { _filename = fn; } 125 126 protected bool updateModificationTime() { 127 if (_filename is null) 128 return false; 129 try { 130 if (!_filename.exists || !_filename.isFile) 131 return false; 132 SysTime accTime; 133 getTimes(_filename, accTime, _lastModificationTime); 134 return true; 135 } catch (Exception e) { 136 return false; 137 } 138 } 139 140 /// load settings from file 141 bool load(string filename = null) { 142 if (filename !is null) 143 _filename = filename; 144 assert(_filename !is null); 145 if (updateModificationTime()) { 146 bool res = _setting.load(_filename); 147 if (res) 148 _loaded = true; 149 afterLoad(); 150 return res; 151 } 152 return false; 153 } 154 155 /// save settings to file 156 bool save(string filename = null, bool pretty = true) { 157 if (filename !is null) 158 _filename = filename; 159 assert(_filename); 160 string dir = dirName(_filename); 161 if (!dir.exists) { 162 try { 163 mkdirRecurse(dir); 164 } catch (Exception e) { 165 return false; 166 } 167 } else if (!dir.isDir) { 168 Log.d("", dir, " is file"); 169 return false; 170 } 171 bool res = _setting.save(_filename, pretty); 172 res = updateModificationTime() || res; 173 afterSave(); 174 return res; 175 } 176 177 /// override to add default values if missing 178 void updateDefaults() { 179 } 180 181 /// override to do something after loading - e.g. set defaults 182 void afterLoad() { 183 } 184 185 /// override to do something after saving 186 void afterSave() { 187 } 188 189 bool merge(string json) { 190 try { 191 Setting setting = new Setting(); 192 setting.parseJSON(json); 193 _setting.apply(setting); 194 } catch (Exception e) { 195 Log.e("SettingsFile.merge - failed to parse json", e); 196 return false; 197 } 198 return true; 199 } 200 } 201 202 /// setting object 203 final class Setting { 204 union Store { 205 string str; 206 long integer; 207 ulong uinteger; 208 double floating; 209 SettingArray array; 210 SettingMap * map; 211 } 212 private Setting _parent; 213 private Store _store; 214 private bool _changed; 215 private SettingType _type = SettingType.NULL; 216 217 this() { 218 // NULL setting 219 } 220 this(long v) { 221 integer = v; 222 } 223 this(ulong v) { 224 uinteger = v; 225 } 226 this(string v) { 227 str = v; 228 } 229 this(double v) { 230 floating = v; 231 } 232 this(bool v) { 233 boolean = v; 234 } 235 this(Setting[] v) { 236 clear(SettingType.ARRAY); 237 _store.array.list = v; 238 } 239 240 this(string[] v) { 241 clear(SettingType.ARRAY); 242 this.strArray = v; 243 } 244 245 this(string[string] v) { 246 clear(SettingType.ARRAY); 247 this.strMap = v; 248 } 249 250 /// returns true if setting has been changed 251 @property bool changed() { 252 return _changed; 253 } 254 255 /// sets change flag 256 @property void changed(bool changed) { 257 _changed = changed; 258 } 259 260 /// array 261 private static struct SettingArray { 262 Setting[] list; 263 @property bool empty() inout { return list.length == 0; } 264 Setting set(int index, Setting value, Setting parent = null) { 265 if (index < 0) 266 index = cast(int)(list.length); 267 if (index >= list.length) { 268 int oldlen = cast(int)list.length; 269 list.length = index + 1; 270 foreach(i; oldlen .. index) 271 list[i] = new Setting(); // insert NULL items in holes 272 } 273 list[index] = value; 274 value.parent = parent; 275 return value; 276 } 277 /// get item by index, returns null if index out of bounds 278 Setting get(int index) { 279 if (index < 0 || index >= list.length) 280 return null; 281 return list[index]; 282 } 283 /// remove by index, returns removed value 284 Setting remove(int index) { 285 Setting res = get(index); 286 if (!res) 287 return null; 288 foreach(i; index .. list.length - 1) 289 list[i] = list[i + 1]; 290 list[$ - 1] = null; 291 list.length--; 292 return res; 293 } 294 @property int length() { 295 return cast(int)list.length; 296 } 297 /// deep copy 298 void copyFrom(ref SettingArray v) { 299 list.length = v.list.length; 300 foreach(i; 0 .. v.list.length) { 301 list[i] = v.list[i].clone(); 302 } 303 } 304 } 305 306 /// ordered map 307 private static struct SettingMap { 308 Setting[] list; 309 int[string] map; 310 @property bool empty() inout { return list.length == 0; } 311 /// get item by index, returns null if index out of bounds 312 Setting get(int index) { 313 if (index < 0 || index >= list.length) 314 return null; 315 return list[index]; 316 } 317 /// get item by key, returns null if key is not found 318 Setting get(string key) { 319 auto p = (key in map); 320 if (!p) 321 return null; 322 return list[*p]; 323 } 324 Setting set(string key, Setting value, Setting parent) { 325 value.parent = parent; 326 auto p = (key in map); 327 if (p) { 328 // key is found 329 list[*p] = value; 330 } else { 331 // new value 332 list ~= value; 333 map[key] = cast(int)list.length - 1; 334 } 335 return value; 336 } 337 338 /// remove by index, returns removed value 339 Setting remove(int index) { 340 Setting res = get(index); 341 if (!res) 342 return null; 343 foreach(i; index .. list.length - 1) 344 list[i] = list[i + 1]; 345 list[$ - 1] = null; 346 list.length--; 347 string key; 348 foreach(k, ref v; map) { 349 if (v == index) { 350 key = k; 351 } else if (v > index) { 352 v--; 353 } 354 } 355 if (key) 356 map.remove(key); 357 return res; 358 } 359 /// returns key for index 360 string keyByIndex(int index) { 361 foreach(k, ref v; map) { 362 if (v == index) { 363 return k; 364 } 365 } 366 return null; 367 } 368 /// remove by key, returns removed value 369 Setting remove(string key) { 370 auto p = (key in map); 371 if (!p) 372 return null; 373 return remove(*p); 374 } 375 @property int length() { 376 return cast(int)list.length; 377 } 378 /// deep copy 379 void copyFrom(SettingMap * v) { 380 list.length = v.list.length; 381 foreach(i; 0 .. v.list.length) { 382 list[i] = v.list[i].clone(); 383 } 384 destroy(map); 385 foreach(key, value; v.map) 386 map[key] = value; 387 } 388 } 389 390 391 /// get parent 392 @property inout(Setting) parent() inout { return _parent; } 393 /// set parent 394 @property Setting parent(Setting v) { 395 _parent = v; 396 return v; 397 } 398 399 /// returns SettingType of setting 400 @property SettingType type() const { return _type; } 401 402 @property bool isString() { return _type == SettingType.STRING; } 403 @property bool isInteger() { return _type == SettingType.INTEGER; } 404 @property bool isUinteger() { return _type == SettingType.UINTEGER; } 405 @property bool isFloating() { return _type == SettingType.FLOAT; } 406 @property bool isObject() { return _type == SettingType.OBJECT; } 407 @property bool isArray() { return _type == SettingType.ARRAY; } 408 @property bool isBoolean() { return _type == SettingType.TRUE || _type == SettingType.FALSE; } 409 @property bool isNull() { return _type == SettingType.NULL; } 410 411 /// clear value and set new type 412 void clear(SettingType newType) { 413 if (newType != _type) { 414 clear(); 415 _type = newType; 416 } 417 clear(); 418 } 419 /// clear value 420 void clear() { 421 final switch(_type) with(SettingType) { 422 case STRING: 423 _store.str = null; 424 break; 425 case ARRAY: 426 _store.array = _store.array.init; 427 break; 428 case OBJECT: 429 _store.map = _store.map.init; 430 break; 431 case INTEGER: 432 _store.integer = _store.integer.init; 433 break; 434 case UINTEGER: 435 _store.uinteger = _store.uinteger.init; 436 break; 437 case FLOAT: 438 _store.floating = _store.floating.init; 439 break; 440 case TRUE: 441 case FALSE: 442 case NULL: 443 break; 444 } 445 } 446 447 void apply(Setting settings) { 448 if (settings.isObject) { 449 foreach(key, value; settings.map) { 450 this[key] = value; 451 } 452 } 453 } 454 455 /// deep copy of settings 456 Setting clone() { 457 Setting res = new Setting(); 458 res.clear(_type); 459 final switch(_type) with(SettingType) { 460 case STRING: 461 res._store.str = _store.str; 462 break; 463 case ARRAY: 464 res._store.array.copyFrom(_store.array); 465 break; 466 case OBJECT: 467 if (_store.map) { 468 res._store.map = new SettingMap(); 469 res._store.map.copyFrom(_store.map); 470 } 471 break; 472 case INTEGER: 473 res._store.integer = _store.integer; 474 break; 475 case UINTEGER: 476 res._store.uinteger = _store.uinteger; 477 break; 478 case FLOAT: 479 res._store.floating = _store.floating; 480 break; 481 case TRUE: 482 case FALSE: 483 case NULL: 484 break; 485 } 486 res._changed = false; 487 return res; 488 } 489 490 491 /// read as string value 492 @property inout(string) str() inout { 493 final switch(_type) with(SettingType) { 494 case STRING: 495 return _store.str; 496 case INTEGER: 497 return to!string(_store.integer); 498 case UINTEGER: 499 return to!string(_store.uinteger); 500 case FLOAT: 501 return to!string(_store.floating); 502 case TRUE: 503 return "true"; 504 case FALSE: 505 return "false"; 506 case NULL: 507 case ARRAY: 508 case OBJECT: 509 return null; 510 } 511 } 512 /// read as string value 513 inout(string) strDef(string defValue) inout { 514 final switch(_type) with(SettingType) { 515 case STRING: 516 return _store.str; 517 case INTEGER: 518 return to!string(_store.integer); 519 case UINTEGER: 520 return to!string(_store.uinteger); 521 case FLOAT: 522 return to!string(_store.floating); 523 case TRUE: 524 return "true"; 525 case FALSE: 526 return "false"; 527 case NULL: 528 case ARRAY: 529 case OBJECT: 530 return defValue; 531 } 532 } 533 /// set string value for object 534 @property string str(string v) { 535 if (_type != SettingType.STRING) 536 clear(SettingType.STRING); 537 _store.str = v; 538 return v; 539 } 540 541 /// returns items as string array 542 @property string[] strArray() { 543 final switch(_type) with(SettingType) { 544 case STRING: 545 return [_store.str]; 546 case INTEGER: 547 return [to!string(_store.integer)]; 548 case UINTEGER: 549 return [to!string(_store.uinteger)]; 550 case FLOAT: 551 return [to!string(_store.floating)]; 552 case TRUE: 553 return ["true"]; 554 case FALSE: 555 return ["false"]; 556 case NULL: 557 return null; 558 case ARRAY: 559 case OBJECT: 560 string[] res; 561 foreach(i; 0 .. length) 562 res ~= this[i].str; 563 return res; 564 } 565 } 566 /// sets string array 567 @property string[] strArray(string[] list) { 568 clear(SettingType.ARRAY); 569 foreach(s; list) { 570 this[length] = new Setting(s); 571 } 572 return list; 573 } 574 575 /// returns items as int array 576 @property int[] intArray() { 577 final switch(_type) with(SettingType) { 578 case STRING: 579 case INTEGER: 580 case UINTEGER: 581 case FLOAT: 582 case TRUE: 583 case FALSE: 584 return [cast(int)integer]; 585 case NULL: 586 return null; 587 case ARRAY: 588 case OBJECT: 589 int[] res; 590 foreach(i; 0 .. length) 591 res ~= cast(int)this[i].integer; 592 return res; 593 } 594 } 595 /// sets int array 596 @property int[] intArray(int[] list) { 597 clear(SettingType.ARRAY); 598 foreach(s; list) { 599 this[length] = new Setting(cast(long)s); 600 } 601 return list; 602 } 603 604 /// returns items as Setting array 605 @property Setting[] array() { 606 final switch(_type) with(SettingType) { 607 case STRING: 608 case INTEGER: 609 case UINTEGER: 610 case FLOAT: 611 case TRUE: 612 case FALSE: 613 return [this]; 614 case NULL: 615 return null; 616 case ARRAY: 617 case OBJECT: 618 Setting[] res; 619 foreach(i; 0 .. length) 620 res ~= this[i]; 621 return res; 622 } 623 } 624 /// sets Setting array 625 @property Setting[] array(Setting[] list) { 626 clear(SettingType.ARRAY); 627 foreach(s; list) { 628 this[length] = s; 629 } 630 return list; 631 } 632 633 /// returns items as string[string] map 634 @property string[string] strMap() { 635 final switch(_type) with(SettingType) { 636 case STRING: 637 case INTEGER: 638 case UINTEGER: 639 case FLOAT: 640 case TRUE: 641 case FALSE: 642 case NULL: 643 case ARRAY: 644 return null; 645 case OBJECT: 646 string[string] res; 647 if (_store.map) { 648 foreach(key, value; _store.map.map) { 649 Setting v = _store.map.get(value); 650 res[key] = v ? v.str : null; 651 } 652 } 653 return res; 654 } 655 } 656 /// sets string[string] map 657 @property string[string] strMap(string[string] list) { 658 clear(SettingType.OBJECT); 659 foreach(key, value; list) { 660 this[key] = new Setting(value); 661 } 662 return list; 663 } 664 665 /// returns items as int[string] map 666 @property int[string] intMap() { 667 final switch(_type) with(SettingType) { 668 case STRING: 669 case INTEGER: 670 case UINTEGER: 671 case FLOAT: 672 case TRUE: 673 case FALSE: 674 case NULL: 675 case ARRAY: 676 return null; 677 case OBJECT: 678 int[string] res; 679 foreach(key, value; _store.map.map) 680 res[key] = cast(int)this[value].integer; 681 return res; 682 } 683 } 684 /// sets int[string] map 685 @property int[string] intMap(int[string] list) { 686 clear(SettingType.OBJECT); 687 foreach(key, value; list) { 688 this[key] = new Setting(cast(long)value); 689 } 690 return list; 691 } 692 693 /// returns items as Setting[string] map 694 @property Setting[string] map() { 695 final switch(_type) with(SettingType) { 696 case STRING: 697 case INTEGER: 698 case UINTEGER: 699 case FLOAT: 700 case TRUE: 701 case FALSE: 702 case NULL: 703 case ARRAY: 704 return null; 705 case OBJECT: 706 Setting[string] res; 707 foreach(key, value; _store.map.map) 708 res[key] = this[value]; 709 return res; 710 } 711 } 712 /// sets Setting[string] map 713 @property Setting[string] map(Setting[string] list) { 714 clear(SettingType.OBJECT); 715 foreach(key, value; list) { 716 this[key] = value; 717 } 718 return list; 719 } 720 721 /// to iterate using foreach 722 int opApply(int delegate(ref Setting)dg) { 723 int result = 0; 724 if (_type == SettingType.ARRAY) { 725 for(int i = 0; i < _store.array.list.length; i++) { 726 result = dg(_store.array.list[i]); 727 if (result) 728 break; 729 } 730 } else if (_type == SettingType.OBJECT) { 731 for(int i = 0; i < _store.map.list.length; i++) { 732 result = dg(_store.map.list[i]); 733 if (result) 734 break; 735 } 736 } 737 return result; 738 } 739 740 /// to iterate over OBJECT using foreach(key, value; map) 741 int opApply(int delegate(ref string, ref Setting)dg) { 742 int result = 0; 743 if (_type == SettingType.OBJECT) { 744 for(int i = 0; i < _store.map.list.length; i++) { 745 string key = _store.map.keyByIndex(i); 746 result = dg(key, _store.map.list[i]); 747 if (result) 748 break; 749 } 750 } 751 return result; 752 } 753 754 /// to iterate using foreach_reverse 755 int opApplyReverse(int delegate(ref Setting)dg) { 756 int result = 0; 757 if (_type == SettingType.ARRAY) { 758 for(int i = cast(int)_store.array.list.length - 1; i >= 0; i--) { 759 result = dg(_store.array.list[i]); 760 if (result) 761 break; 762 } 763 } else if (_type == SettingType.OBJECT) { 764 for(int i = cast(int)_store.map.list.length - 1; i >= 0; i--) { 765 result = dg(_store.map.list[i]); 766 if (result) 767 break; 768 } 769 } 770 return result; 771 } 772 773 /// read as long value 774 @property inout(long) integer() inout { 775 final switch(_type) with(SettingType) { 776 case STRING: 777 return parseLong(_store.str); 778 case INTEGER: 779 return _store.integer; 780 case UINTEGER: 781 return cast(long)_store.uinteger; 782 case FLOAT: 783 return cast(long)_store.floating; 784 case TRUE: 785 return 1; 786 case FALSE: 787 case NULL: 788 case ARRAY: 789 case OBJECT: 790 return 0; 791 } 792 } 793 794 /// read as long value 795 inout(long) integerDef(long defValue) inout { 796 final switch(_type) with(SettingType) { 797 case STRING: 798 return parseLong(_store.str, defValue); 799 case INTEGER: 800 return _store.integer; 801 case UINTEGER: 802 return cast(long)_store.uinteger; 803 case FLOAT: 804 return cast(long)_store.floating; 805 case TRUE: 806 return 1; 807 case FALSE: 808 return 0; 809 case NULL: 810 case ARRAY: 811 case OBJECT: 812 return defValue; 813 } 814 } 815 /// set long value for object 816 @property long integer(long v) { 817 if (_type != SettingType.INTEGER) 818 clear(SettingType.INTEGER); 819 _store.integer = v; 820 return v; 821 } 822 823 /// read as ulong value 824 @property inout(long) uinteger() inout { 825 final switch(_type) with(SettingType) { 826 case STRING: 827 return parseULong(_store.str); 828 case INTEGER: 829 return cast(ulong)_store.integer; 830 case UINTEGER: 831 return _store.uinteger; 832 case FLOAT: 833 return cast(ulong)_store.floating; 834 case TRUE: 835 return 1; 836 case FALSE: 837 case NULL: 838 case ARRAY: 839 case OBJECT: 840 return 0; 841 } 842 } 843 /// read as ulong value 844 inout(long) uintegerDef(ulong defValue) inout { 845 final switch(_type) with(SettingType) { 846 case STRING: 847 return parseULong(_store.str, defValue); 848 case INTEGER: 849 return cast(ulong)_store.integer; 850 case UINTEGER: 851 return _store.uinteger; 852 case FLOAT: 853 return cast(ulong)_store.floating; 854 case TRUE: 855 return 1; 856 case FALSE: 857 return 0; 858 case NULL: 859 case ARRAY: 860 case OBJECT: 861 return defValue; 862 } 863 } 864 /// set ulong value for object 865 @property ulong uinteger(ulong v) { 866 if (_type != SettingType.UINTEGER) 867 clear(SettingType.UINTEGER); 868 _store.uinteger = v; 869 return v; 870 } 871 872 /// read as double value 873 @property inout(double) floating() inout { 874 final switch(_type) with(SettingType) { 875 case STRING: 876 return 0; //parseULong(_store.str); 877 case INTEGER: 878 return cast(double)_store.integer; 879 case UINTEGER: 880 return cast(double)_store.uinteger; 881 case FLOAT: 882 return _store.floating; 883 case TRUE: 884 return 1; 885 case FALSE: 886 case NULL: 887 case ARRAY: 888 case OBJECT: 889 return 0; 890 } 891 } 892 /// read as double value with default 893 inout(double) floatingDef(double defValue) inout { 894 final switch(_type) with(SettingType) { 895 case STRING: 896 return defValue; //parseULong(_store.str); 897 case INTEGER: 898 return cast(double)_store.integer; 899 case UINTEGER: 900 return cast(double)_store.uinteger; 901 case FLOAT: 902 return _store.floating; 903 case TRUE: 904 return 1; 905 case FALSE: 906 return 0; 907 case NULL: 908 case ARRAY: 909 case OBJECT: 910 return defValue; 911 } 912 } 913 /// set ulong value for object 914 @property double floating(double v) { 915 if (_type != SettingType.FLOAT) 916 clear(SettingType.FLOAT); 917 _store.floating = v; 918 return v; 919 } 920 921 /// parse string as boolean; supports 1, 0, y, n, yes, no, t, f, true, false; returns defValue if cannot be parsed 922 static bool parseBool(inout string v, bool defValue = false) { 923 int len = cast(int)v.length; 924 if (len == 0) 925 return defValue; 926 char ch = v[0]; 927 if (len == 1) { 928 if (ch == '1' || ch == 'y' || ch == 't') 929 return true; 930 if (ch == '1' || ch == 'y' || ch == 't') 931 return false; 932 return defValue; 933 } 934 if (v.equal("yes") || v.equal("true")) 935 return true; 936 if (v.equal("no") || v.equal("false")) 937 return false; 938 return defValue; 939 } 940 941 /// read as boolean value 942 @property inout(bool) boolean() inout { 943 final switch(_type) with(SettingType) { 944 case STRING: 945 return parseBool(_store.str); 946 case INTEGER: 947 return _store.integer != 0; 948 case UINTEGER: 949 return _store.uinteger != 0; 950 case FLOAT: 951 return _store.floating != 0; 952 case TRUE: 953 return true; 954 case FALSE: 955 case NULL: 956 return false; 957 case ARRAY: 958 return !_store.array.empty; 959 case OBJECT: 960 return _store.map && !_store.map.empty; 961 } 962 } 963 /// read as boolean value 964 inout(bool) booleanDef(bool defValue) inout { 965 final switch(_type) with(SettingType) { 966 case STRING: 967 return parseBool(_store.str, defValue); 968 case INTEGER: 969 return _store.integer != 0; 970 case UINTEGER: 971 return _store.uinteger != 0; 972 case FLOAT: 973 return _store.floating != 0; 974 case TRUE: 975 return true; 976 case FALSE: 977 case NULL: 978 return false; 979 case ARRAY: 980 return defValue; 981 case OBJECT: 982 return defValue; 983 } 984 } 985 /// set bool value for object 986 @property bool boolean(bool v) { 987 if (_type == SettingType.TRUE) { 988 if (!v) _type = SettingType.FALSE; 989 } else if (_type == SettingType.FALSE) { 990 if (v) _type = SettingType.TRUE; 991 } else { 992 clear(v ? SettingType.TRUE : SettingType.FALSE); 993 } 994 return v; 995 } 996 997 /// get number of elements for array or map, returns 0 for other types 998 int length() inout { 999 if (_type == SettingType.ARRAY) { 1000 return cast(int)_store.array.list.length; 1001 } else if (_type == SettingType.OBJECT) { 1002 return _store.map ? cast(int)_store.map.list.length : 0; 1003 } else 1004 return 0; 1005 } 1006 1007 /// for array or object returns item by index, null if index is out of bounds or setting is neither array nor object 1008 Setting opIndex(int index) { 1009 if (_type == SettingType.ARRAY) { 1010 return _store.array.get(index); 1011 } else if (_type == SettingType.OBJECT) { 1012 if (!_store.map) 1013 return null; 1014 return _store.map.get(index); 1015 } else { 1016 return null; 1017 } 1018 } 1019 1020 /// for object returns item by key, null if not found or this setting is not an object 1021 Setting opIndex(string key) { 1022 if (_type == SettingType.OBJECT) { 1023 if (!_store.map) 1024 return null; 1025 return _store.map.get(key); 1026 } else { 1027 return null; 1028 } 1029 } 1030 1031 /// for array or object remove item by index, returns removed item or null if index is out of bounds or setting is neither array nor object 1032 Setting remove(int index) { 1033 if (_type == SettingType.ARRAY) { 1034 return _store.array.remove(index); 1035 } else if (_type == SettingType.OBJECT) { 1036 if (!_store.map) 1037 return null; 1038 return _store.map.remove(index); 1039 } else { 1040 return null; 1041 } 1042 } 1043 1044 /// for object remove item by key, returns removed item or null if is not found or setting is not an object 1045 Setting remove(string key) { 1046 if (_type == SettingType.OBJECT) { 1047 if (!_store.map) 1048 return null; 1049 return _store.map.remove(key); 1050 } else { 1051 return null; 1052 } 1053 } 1054 1055 // assign long value 1056 long opAssign(long value) { 1057 return (integer = value); 1058 } 1059 // assign ulong value 1060 ulong opAssign(ulong value) { 1061 return (uinteger = value); 1062 } 1063 // assign string value 1064 string opAssign(string value) { 1065 return (str = value); 1066 } 1067 // assign bool value 1068 bool opAssign(bool value) { 1069 return (boolean = value); 1070 } 1071 // assign double value 1072 double opAssign(double value) { 1073 return (floating = value); 1074 } 1075 // assign int[] value 1076 int[] opAssign(int[] value) { 1077 return (intArray = value); 1078 } 1079 // assign string[string] value 1080 string[string] opAssign(string[string] value) { 1081 return (strMap = value); 1082 } 1083 // assign string[] value 1084 string[] opAssign(string[] value) { 1085 return (strArray = value); 1086 } 1087 // assign int[string] value 1088 int[string] opAssign(int[string] value) { 1089 return (intMap = value); 1090 } 1091 // assign Setting[] value 1092 Setting[] opAssign(Setting[] value) { 1093 return (array = value); 1094 } 1095 // assign Setting[string] value 1096 Setting[string] opAssign(Setting[string] value) { 1097 return (map = value); 1098 } 1099 1100 // array methods 1101 /// sets value for array item by integer index 1102 T opIndexAssign(T)(T value, int index) { 1103 if (_type != SettingType.ARRAY) 1104 clear(SettingType.ARRAY); 1105 static if (is(T: Setting)) { 1106 _store.array.set(index, value, this); 1107 } else { 1108 Setting item = _store.array.get(index); 1109 if (item) { 1110 // existing item 1111 item = value; 1112 } else { 1113 // create new item 1114 _store.array.set(index, new Setting(value), this); 1115 } 1116 } 1117 return value; 1118 } 1119 /// sets value for array item by integer index if not already present 1120 T setDef(T)(T value, int index) { 1121 if (_type != SettingType.ARRAY) 1122 clear(SettingType.ARRAY); 1123 Setting item = _store.array.get(index); 1124 if (item) 1125 return value; 1126 static if (is(value == Setting)) { 1127 _store.array.set(index, value, this); 1128 } else { 1129 // create new item 1130 _store.array.set(index, new Setting(value), this); 1131 } 1132 return value; 1133 } 1134 1135 /// returns setting by path like "editors/sourceEditor/tabSize", creates object tree "editors/sourceEditor" and object of specified type if part of path does not exist. 1136 Setting settingByPath(string path, SettingType type, bool createIfNotExist = true) { 1137 if (_type != SettingType.OBJECT) 1138 clear(SettingType.OBJECT); 1139 string part1, part2; 1140 if (splitKey(path, part1, part2)) { 1141 auto s = this[part1]; 1142 if (!s) { 1143 s = new Setting(); 1144 s.clear(SettingType.OBJECT); 1145 this[part1] = s; 1146 } 1147 return s.settingByPath(part2, type); 1148 } else { 1149 auto s = this[path]; 1150 if (!s && createIfNotExist) { 1151 s = new Setting(); 1152 s.clear(type); 1153 this[path] = s; 1154 } 1155 return s; 1156 } 1157 } 1158 1159 /// get (or optionally create) object (map) by slash delimited path (e.g. key1/subkey2/subkey3) 1160 Setting objectByPath(string path, bool createIfNotExist = false) { 1161 if (type != SettingType.OBJECT) { 1162 if (!createIfNotExist) 1163 return null; 1164 // do we need to allow this conversion to object? 1165 clear(SettingType.OBJECT); 1166 } 1167 string part1, part2; 1168 if (splitKey(path, part1, part2)) { 1169 auto s = this[part1]; 1170 if (!s) { 1171 if (!createIfNotExist) 1172 return null; 1173 s = new Setting(); 1174 s.clear(SettingType.OBJECT); 1175 this[part1] = s; 1176 } 1177 return s.objectByPath(part2, createIfNotExist); 1178 } else { 1179 auto s = this[path]; 1180 if (!s) { 1181 if (!createIfNotExist) 1182 return null; 1183 s = new Setting(); 1184 s.clear(SettingType.OBJECT); 1185 this[path] = s; 1186 } 1187 return s; 1188 } 1189 } 1190 1191 private static bool splitKey(string key, ref string part1, ref string part2) { 1192 int dashPos = -1; 1193 for (int i = 0; i < key.length; i++) { 1194 if (key[i] == '/') { 1195 dashPos = i; 1196 break; 1197 } 1198 } 1199 if (dashPos >= 0) { 1200 // path 1201 part1 = key[0 .. dashPos]; 1202 part2 = key[dashPos + 1 .. $]; 1203 return true; 1204 } 1205 return false; 1206 } 1207 1208 // map methods 1209 /// sets value for object item by string key 1210 T opIndexAssign(T)(T value, string key) { 1211 if (_type != SettingType.OBJECT) 1212 clear(SettingType.OBJECT); 1213 if (!_store.map) 1214 _store.map = new SettingMap(); 1215 static if (is(T: Setting)) { 1216 _store.map.set(key, value, this); 1217 } else { 1218 Setting item = _store.map.get(key); 1219 if (item) { 1220 // existing item 1221 item = value; 1222 } else { 1223 // create new item 1224 _store.map.set(key, new Setting(value), this); 1225 } 1226 } 1227 return value; 1228 } 1229 /// sets value for object item by string key 1230 T setDef(T)(T value, string key) { 1231 if (_type != SettingType.OBJECT) 1232 clear(SettingType.OBJECT); 1233 if (!_store.map) 1234 _store.map = new SettingMap(); 1235 Setting item = _store.map.get(key); 1236 if (item) 1237 return value; 1238 static if (is(value == Setting)) { 1239 _store.map.set(key, value, this); 1240 } else { 1241 // create new item 1242 _store.map.set(key, new Setting(value), this); 1243 } 1244 return value; 1245 } 1246 1247 /// sets long item by index of array or map 1248 long setInteger(int index, long value) { 1249 return opIndexAssign(value, index); 1250 } 1251 /// sets ulong item by index of array or map 1252 ulong setUinteger(int index, ulong value) { 1253 return opIndexAssign(value, index); 1254 } 1255 /// sets bool item by index of array or map 1256 bool setBoolean(int index, bool value) { 1257 return opIndexAssign(value, index); 1258 } 1259 /// sets double item by index of array or map 1260 double setFloating(int index, double value) { 1261 return opIndexAssign(value, index); 1262 } 1263 /// sets str item by index of array or map 1264 string setString(int index, string value) { 1265 return opIndexAssign(value, index); 1266 } 1267 1268 /// sets long item by index of array or map only if it's фдкуфвн present 1269 long setIntegerDef(int index, long value) { 1270 return setDef(value, index); 1271 } 1272 /// sets ulong item by index of array or map only if it's фдкуфвн present 1273 ulong setUintegerDef(int index, ulong value) { 1274 return setDef(value, index); 1275 } 1276 /// sets bool item by index of array or map only if it's фдкуфвн present 1277 bool setBooleanDef(int index, bool value) { 1278 return setDef(value, index); 1279 } 1280 /// sets double item by index of array or map only if it's фдкуфвн present 1281 double setFloatingDef(int index, double value) { 1282 return setDef(value, index); 1283 } 1284 /// sets str item by index of array or map only if it's фдкуфвн present 1285 string setStringDef(int index, string value) { 1286 return setDef(value, index); 1287 } 1288 1289 1290 /// returns long item by index of array or map 1291 long getInteger(int index, long defValue = 0) { 1292 if (auto item = opIndex(index)) 1293 return item.integerDef(defValue); 1294 return defValue; 1295 } 1296 /// returns ulong item by index of array or map 1297 ulong getUinteger(int index, ulong defValue = 0) { 1298 if (auto item = opIndex(index)) 1299 return item.uintegerDef(defValue); 1300 return defValue; 1301 } 1302 /// returns bool item by index of array or map 1303 bool getBoolean(int index, bool defValue = false) { 1304 if (auto item = opIndex(index)) 1305 return item.booleanDef(defValue); 1306 return defValue; 1307 } 1308 /// returns double item by index of array or map 1309 double getFloating(int index, double defValue = 0) { 1310 if (auto item = opIndex(index)) 1311 return item.floatingDef(defValue); 1312 return defValue; 1313 } 1314 /// returns str item by index of array or map 1315 string getString(int index, string defValue = null) { 1316 if (auto item = opIndex(index)) 1317 return item.strDef(defValue); 1318 return defValue; 1319 } 1320 1321 1322 /// sets long item of map 1323 long setInteger(string key, long value) { 1324 return opIndexAssign(value, key); 1325 } 1326 /// sets ulong item of map 1327 ulong setUinteger(string key, ulong value) { 1328 return opIndexAssign(value, key); 1329 } 1330 /// sets bool item of map 1331 bool setBoolean(string key, bool value) { 1332 return opIndexAssign(value, key); 1333 } 1334 /// sets double item of map 1335 double setFloating(string key, double value) { 1336 return opIndexAssign(value, key); 1337 } 1338 /// sets str item of map 1339 string setString(string key, string value) { 1340 return opIndexAssign(value, key); 1341 } 1342 1343 /// sets long item of map if key is not yet present in map 1344 long setIntegerDef(string key, long value) { 1345 return setDef(value, key); 1346 } 1347 /// sets ulong item of map if key is not yet present in map 1348 ulong setUintegerDef(string key, ulong value) { 1349 return setDef(value, key); 1350 } 1351 /// sets bool item of map if key is not yet present in map 1352 bool setBooleanDef(string key, bool value) { 1353 return setDef(value, key); 1354 } 1355 /// sets double item of map if key is not yet present in map 1356 double setFloatingDef(string key, double value) { 1357 return setDef(value, key); 1358 } 1359 /// sets str item of map if key is not yet present in map 1360 string setStringDef(string key, string value) { 1361 return setDef(value, key); 1362 } 1363 1364 1365 1366 /// returns long item by key from map 1367 long getInteger(string key, long defValue = 0) { 1368 if (auto item = opIndex(key)) 1369 return item.integerDef(defValue); 1370 return defValue; 1371 } 1372 /// returns ulong item by key from map 1373 ulong getUinteger(string key, ulong defValue = 0) { 1374 if (auto item = opIndex(key)) 1375 return item.uintegerDef(defValue); 1376 return defValue; 1377 } 1378 /// returns bool item by key from map 1379 bool getBoolean(string key, bool defValue = false) { 1380 if (auto item = opIndex(key)) 1381 return item.booleanDef(defValue); 1382 return defValue; 1383 } 1384 /// returns double item by key from map 1385 double getFloating(string key, double defValue = 0) { 1386 if (auto item = opIndex(key)) 1387 return item.floatingDef(defValue); 1388 return defValue; 1389 } 1390 /// returns str item by key from map 1391 string getString(string key, string defValue = null) { 1392 if (auto item = opIndex(key)) 1393 return item.strDef(defValue); 1394 return defValue; 1395 } 1396 /// returns string array item by key from map, returns null if not found 1397 string[] getStringArray(string key) { 1398 if (auto item = opIndex(key)) 1399 return item.strArray(); 1400 return null; 1401 } 1402 1403 /// serialize to json 1404 string toJSON(bool pretty = false) { 1405 Buf buf; 1406 toJSON(buf, 0, pretty); 1407 return buf.get(); 1408 } 1409 private static struct Buf { 1410 char[] buffer; 1411 int pos; 1412 string get() { 1413 return buffer[0 .. pos].dup; 1414 } 1415 void reserve(size_t size) { 1416 if (pos + size >= buffer.length) 1417 buffer.length = buffer.length ? 4096 : (pos + size + 4096) * 2; 1418 } 1419 void append(char ch) { 1420 buffer[pos++] = ch; 1421 } 1422 void append(string s) { 1423 foreach(ch; s) 1424 buffer[pos++] = ch; 1425 } 1426 void appendEOL() { 1427 append('\n'); 1428 } 1429 1430 void appendTabs(int level) { 1431 reserve(level * 4 + 1024); 1432 foreach(i; 0 .. level) { 1433 buffer[pos++] = ' '; 1434 buffer[pos++] = ' '; 1435 buffer[pos++] = ' '; 1436 buffer[pos++] = ' '; 1437 } 1438 } 1439 1440 void appendHex(uint ch) { 1441 buffer[pos++] = '\\'; 1442 buffer[pos++] = 'u'; 1443 for (int i = 3; i >= 0; i--) { 1444 uint d = (ch >> (4 * i)) & 0x0F; 1445 buffer[pos++] = "0123456789abcdef"[d]; 1446 } 1447 } 1448 void appendJSONString(string s) { 1449 reserve(s.length * 3 + 8); 1450 if (s is null) { 1451 append("null"); 1452 } else { 1453 append('\"'); 1454 foreach(ch; s) { 1455 switch (ch) { 1456 case '\\': 1457 buffer[pos++] = '\\'; 1458 buffer[pos++] = '\\'; 1459 break; 1460 case '\"': 1461 buffer[pos++] = '\\'; 1462 buffer[pos++] = '\"'; 1463 break; 1464 case '\r': 1465 buffer[pos++] = '\\'; 1466 buffer[pos++] = 'r'; 1467 break; 1468 case '\n': 1469 buffer[pos++] = '\\'; 1470 buffer[pos++] = 'n'; 1471 break; 1472 case '\b': 1473 buffer[pos++] = '\\'; 1474 buffer[pos++] = 'b'; 1475 break; 1476 case '\t': 1477 buffer[pos++] = '\\'; 1478 buffer[pos++] = 't'; 1479 break; 1480 case '\f': 1481 buffer[pos++] = '\\'; 1482 buffer[pos++] = 'f'; 1483 break; 1484 default: 1485 if (ch < ' ') { 1486 appendHex(ch); 1487 } else { 1488 buffer[pos++] = ch; 1489 } 1490 break; 1491 } 1492 } 1493 append('\"'); 1494 } 1495 } 1496 } 1497 1498 void toJSON(ref Buf buf, int level, bool pretty) { 1499 buf.reserve(1024); 1500 final switch(_type) with(SettingType) { 1501 case STRING: 1502 buf.appendJSONString(_store.str); 1503 break; 1504 case INTEGER: 1505 buf.append(to!string(_store.integer)); 1506 break; 1507 case UINTEGER: 1508 buf.append(to!string(_store.uinteger)); 1509 break; 1510 case FLOAT: 1511 buf.append(to!string(_store.floating)); 1512 break; 1513 case TRUE: 1514 buf.append("true"); 1515 break; 1516 case FALSE: 1517 buf.append("false"); 1518 break; 1519 case NULL: 1520 buf.append("null"); 1521 break; 1522 case ARRAY: 1523 buf.append('['); 1524 if (pretty && _store.array.length > 0) 1525 buf.appendEOL(); 1526 foreach(i; 0 .. _store.array.length) { 1527 if (pretty) 1528 buf.appendTabs(level + 1); 1529 _store.array.get(i).toJSON(buf, level + 1, pretty); 1530 if (i >= _store.array.length - 1) 1531 break; 1532 buf.append(','); 1533 if (pretty) 1534 buf.appendEOL(); 1535 } 1536 if (pretty) { 1537 buf.appendEOL(); 1538 buf.appendTabs(level); 1539 } 1540 buf.append(']'); 1541 break; 1542 case OBJECT: 1543 buf.append('{'); 1544 if (_store.map && _store.map.length) { 1545 if (pretty) 1546 buf.appendEOL(); 1547 for (int i = 0; ; i++) { 1548 string key = _store.map.keyByIndex(i); 1549 if (pretty) 1550 buf.appendTabs(level + 1); 1551 buf.appendJSONString(key); 1552 buf.append(':'); 1553 if (pretty) 1554 buf.append(' '); 1555 _store.map.get(i).toJSON(buf, level + 1, pretty); 1556 if (i >= _store.map.length - 1) 1557 break; 1558 buf.append(','); 1559 if (pretty) 1560 buf.appendEOL(); 1561 } 1562 } 1563 if (pretty) { 1564 buf.appendEOL(); 1565 buf.appendTabs(level); 1566 } 1567 buf.append('}'); 1568 break; 1569 } 1570 } 1571 1572 /// save to file 1573 bool save(string filename, bool pretty = true) { 1574 try { 1575 write(filename, toJSON(pretty)); 1576 return true; 1577 } catch (Exception e) { 1578 Log.e("exception while saving settings file: ", e); 1579 return false; 1580 } 1581 } 1582 1583 private static struct JsonParser { 1584 string json; 1585 int pos; 1586 bool allowEol; // for SDL parsing where EOLs are meaningful 1587 void initialize(string s, bool allowEol) { 1588 json = s; 1589 pos = 0; 1590 this.allowEol = allowEol; 1591 } 1592 /// returns current char 1593 @property char peek() { 1594 return pos < json.length ? json[pos] : 0; 1595 } 1596 /// return fragment of text in current position 1597 @property string currentContext() { 1598 if (pos >= json.length) 1599 return "end of file"; 1600 string res = json[pos .. $]; 1601 if (res.length > 100) 1602 res.length = 100; 1603 return res; 1604 } 1605 /// skips current char, returns next one (or null if eof) 1606 @property char nextChar() { 1607 if (pos + 1 < json.length) { 1608 return json[++pos]; 1609 } else { 1610 if (pos < json.length) 1611 pos++; 1612 } 1613 return 0; 1614 } 1615 void error(string msg) { 1616 string context; 1617 // calculate error position line and column 1618 int line = 1; 1619 int col = 1; 1620 int lineStart = 0; 1621 foreach(int i; 0 .. pos) { 1622 char ch = json[i]; 1623 if (ch == '\r') { 1624 if (i < json.length - 1 && json[i + 1] == '\n') 1625 i++; 1626 line++; 1627 col = 1; 1628 lineStart = i + 1; 1629 } else if (ch == '\n') { 1630 if (i < json.length - 1 && json[i + 1] == '\r') 1631 i++; 1632 line++; 1633 col = 1; 1634 lineStart = i + 1; 1635 } 1636 } 1637 int contextStart = pos; 1638 int contextEnd = pos; 1639 for (; contextEnd < json.length; contextEnd++) { 1640 if (json[contextEnd] == '\r' || json[contextEnd] == '\n') 1641 break; 1642 } 1643 if (contextEnd - contextStart < 3) { 1644 for (int i = 0; i < 3 && contextStart > 0; contextStart--, i++) { 1645 if (json[contextStart - 1] == '\r' || json[contextStart - 1] == '\n') 1646 break; 1647 } 1648 } else if (contextEnd > contextStart + 10) 1649 contextEnd = contextStart + 10; 1650 if (contextEnd > contextStart && contextEnd < json.length) 1651 context = "near `" ~ json[contextStart .. contextEnd] ~ "` "; 1652 else if (pos >= json.length) 1653 context = "at end of file"; 1654 throw new Exception("JSON parsing error in (" ~ to!string(line) ~ ":" ~ to!string(col) ~ ") " ~ context ~ ": " ~ msg); 1655 } 1656 static bool isAlpha(char ch) { 1657 static import std.ascii; 1658 return std.ascii.isAlpha(ch) || ch == '_'; 1659 } 1660 static bool isAlNum(char ch) { 1661 static import std.ascii; 1662 return std.ascii.isAlphaNum(ch) || ch == '_'; 1663 } 1664 /// skip spaces and comments, return next available character 1665 @property char skipSpaces() { 1666 static import std.ascii; 1667 for(;pos < json.length;pos++) { 1668 char ch = json[pos]; 1669 char nextch = pos + 1 < json.length ? json[pos + 1] : 0; 1670 if (allowEol && ch == '\n') 1671 break; 1672 if (ch == '#' || (ch == '/' && nextch == '/') || (ch == '-' && nextch == '-')) { 1673 // skip one line comment // or # or -- 1674 pos++; 1675 for(;pos < json.length;pos++) { 1676 ch = json[pos]; 1677 if (ch == '\n') 1678 break; 1679 } 1680 if (allowEol && ch == '\n') 1681 break; 1682 continue; 1683 } else if (ch == '/' && nextch == '*') { 1684 // skip multiline /* */ comment 1685 pos += 2; 1686 for(;pos < json.length;pos++) { 1687 ch = json[pos]; 1688 nextch = pos + 1 < json.length ? json[pos + 1] : 0; 1689 if (ch == '*' && nextch == '/') { 1690 pos += 2; 1691 break; 1692 } 1693 } 1694 continue; 1695 } else if (ch == '\\' && nextch == '\n') { 1696 // continue to next line 1697 pos += 2; 1698 continue; 1699 } 1700 if (!std.ascii.isWhite(ch)) 1701 break; 1702 } 1703 return peek; 1704 } 1705 1706 string parseUnicodeChar() { 1707 if (pos >= json.length - 3) 1708 error("unexpected end of file while parsing unicode character entity inside string"); 1709 dchar ch = 0; 1710 foreach(i; 0 .. 4) { 1711 uint d = parseHexDigit(nextChar); 1712 if (d == uint.max) 1713 error("error while parsing unicode character entity inside string"); 1714 ch = (ch << 4) | d; 1715 } 1716 char[4] buf; 1717 size_t sz = encode(buf, ch); 1718 return buf[0..sz].dup; 1719 } 1720 1721 @property string parseString() { 1722 char[] res; 1723 char ch = peek; 1724 char quoteChar = ch; 1725 if (ch != '\"' && ch != '`') 1726 error("cannot parse string"); 1727 for (;;) { 1728 ch = nextChar; 1729 if (!ch) 1730 error("unexpected end of file while parsing string"); 1731 if (ch == quoteChar) { 1732 nextChar; 1733 return cast(string)res; 1734 } 1735 if (ch == '\\' && quoteChar != '`') { 1736 // escape sequence 1737 ch = nextChar; 1738 switch (ch) { 1739 case 'n': 1740 res ~= '\n'; 1741 break; 1742 case 'r': 1743 res ~= '\r'; 1744 break; 1745 case 'b': 1746 res ~= '\b'; 1747 break; 1748 case 'f': 1749 res ~= '\f'; 1750 break; 1751 case '\\': 1752 res ~= '\\'; 1753 break; 1754 case '/': 1755 res ~= '/'; 1756 break; 1757 case '\"': 1758 res ~= '\"'; 1759 break; 1760 case 'u': 1761 res ~= parseUnicodeChar(); 1762 break; 1763 default: 1764 error("unexpected escape sequence in string"); 1765 break; 1766 } 1767 } else { 1768 res ~= ch; 1769 } 1770 } 1771 } 1772 @property string parseIdent() { 1773 char ch = peek; 1774 if (ch == '\"' || ch == '`') { 1775 return parseString; 1776 } 1777 char[] res; 1778 if (isAlpha(ch)) { 1779 res ~= ch; 1780 for (;;) { 1781 ch = nextChar; 1782 if (isAlNum(ch)) { 1783 res ~= ch; 1784 } else { 1785 break; 1786 } 1787 } 1788 } else 1789 error("cannot parse ident"); 1790 return cast(string)res; 1791 } 1792 bool parseKeyword(string ident) { 1793 // returns true if parsed ok 1794 if (pos + ident.length > json.length) 1795 return false; 1796 foreach(i; 0 .. ident.length) { 1797 if (ident[i] != json[pos + i]) 1798 return false; 1799 } 1800 if (pos + ident.length < json.length) { 1801 char ch = json[pos + ident.length]; 1802 if (isAlNum(ch)) 1803 return false; 1804 } 1805 pos += ident.length; 1806 return true; 1807 } 1808 1809 // parse long, ulong or double 1810 void parseNumber(Setting res) { 1811 import std.ascii : isDigit; 1812 char ch = peek; 1813 int sign = 1; 1814 if (ch == '-') { 1815 sign = -1; 1816 ch = nextChar; 1817 } 1818 if (!isDigit(ch)) 1819 error("cannot parse number"); 1820 ulong n = 0; 1821 while (isDigit(ch)) { 1822 n = n * 10 + (ch - '0'); 1823 ch = nextChar; 1824 } 1825 if (ch == '.' || ch == 'e' || ch == 'E') { 1826 // floating 1827 ulong n2 = 0; 1828 ulong n2_div = 1; 1829 if (ch == '.') { 1830 ch = nextChar; 1831 while(isDigit(ch)) { 1832 n2 = n2 * 10 + (ch - '0'); 1833 n2_div *= 10; 1834 ch = nextChar; 1835 } 1836 if (isAlpha(ch) && ch != 'e' && ch != 'E') 1837 error("error while parsing number"); 1838 } 1839 int shift = 0; 1840 int shiftSign = 1; 1841 if (ch == 'e' || ch == 'E') { 1842 ch = nextChar; 1843 if (ch == '-') { 1844 shiftSign = -1; 1845 ch = nextChar; 1846 } 1847 if (!isDigit(ch)) 1848 error("error while parsing number"); 1849 while(isDigit(ch)) { 1850 shift = shift * 10 + (ch - '0'); 1851 ch = nextChar; 1852 } 1853 } 1854 if (isAlpha(ch)) 1855 error("error while parsing number"); 1856 double v = cast(double)n; 1857 if (n2) // part after period 1858 v += cast(double)n2 / n2_div; 1859 if (sign < 0) 1860 v = -v; 1861 if (shift) { // E part - pow10 1862 double p = pow(10.0, shift); 1863 if (shiftSign > 0) 1864 v *= p; 1865 else 1866 v /= p; 1867 } 1868 res.floating = v; 1869 } else { 1870 // integer 1871 if (isAlpha(ch)) 1872 error("cannot parse number"); 1873 if (sign < 0 || !(n & 0x8000000000000000L)) 1874 res.integer = cast(long)(n * sign); // signed 1875 else 1876 res.uinteger = n; // unsigned 1877 } 1878 } 1879 } 1880 1881 private void parseMap(ref JsonParser parser) { 1882 clear(SettingType.OBJECT); 1883 int startPos = parser.pos; 1884 //Log.v("parseMap at context ", parser.currentContext); 1885 char ch = parser.peek; 1886 parser.nextChar; // skip initial { 1887 if (ch != '{') { 1888 Log.e("expected { at ", parser.currentContext); 1889 } 1890 for(;;) { 1891 ch = parser.skipSpaces; 1892 if (ch == '}') { 1893 parser.nextChar; 1894 break; 1895 } 1896 string key = parser.parseIdent; 1897 ch = parser.skipSpaces; 1898 if (ch != ':') 1899 parser.error("no : char after object field name"); 1900 parser.nextChar; 1901 this[key] = (new Setting()).parseJSON(parser); 1902 //Log.v("context before skipSpaces: ", parser.currentContext); 1903 ch = parser.skipSpaces; 1904 //Log.v("context after skipSpaces: ", parser.currentContext); 1905 if (ch == ',') { 1906 parser.nextChar; 1907 parser.skipSpaces; 1908 } else if (ch != '}') { 1909 parser.error("unexpected character when waiting for , or } while parsing object; { position is "~ to!string(startPos)); 1910 } 1911 } 1912 } 1913 1914 private void parseArray(ref JsonParser parser) { 1915 clear(SettingType.ARRAY); 1916 parser.nextChar; // skip initial [ 1917 for(;;) { 1918 char ch = parser.skipSpaces; 1919 if (ch == ']') { 1920 parser.nextChar; 1921 break; 1922 } 1923 Setting value = new Setting(); 1924 value.parseJSON(parser); 1925 this[_store.array.length] = value; 1926 ch = parser.skipSpaces; 1927 if (ch == ',') { 1928 parser.nextChar; 1929 parser.skipSpaces; 1930 } else if (ch != ']') { 1931 parser.error("unexpected character when waiting for , or ] while parsing array"); 1932 } 1933 } 1934 } 1935 1936 private Setting parseJSON(ref JsonParser parser) { 1937 static import std.ascii; 1938 char ch = parser.skipSpaces; 1939 if (ch == '\"') { 1940 this = parser.parseString; 1941 } else if (ch == '[') { 1942 parseArray(parser); 1943 } else if (ch == '{') { 1944 parseMap(parser); 1945 } else if (parser.parseKeyword("null")) { 1946 // do nothing - we already have NULL value 1947 } else if (parser.parseKeyword("true")) { 1948 this = true; 1949 } else if (parser.parseKeyword("false")) { 1950 this = false; 1951 } else if (ch == '-' || std.ascii.isDigit(ch)) { 1952 parser.parseNumber(this); 1953 } else { 1954 parser.error("cannot parse JSON value"); 1955 } 1956 return this; 1957 } 1958 1959 void parseJSON(string s) { 1960 clear(SettingType.NULL); 1961 JsonParser parser; 1962 parser.initialize(convertEols(s), false); 1963 parseJSON(parser); 1964 } 1965 1966 /// SDL identifiers to be converted to JSON array (name should be changed, with 's' suffix) 1967 private static immutable (string[]) identsToConvertToArrays = [ 1968 "subPackage", // in JSON it's subPackages 1969 "configuration", // in JSON it's configurations 1970 "buildType", // in JSON it's buildTypes 1971 ]; 1972 1973 /// SDL identifiers to be converted to JSON object (name should be changed, with 's' suffix) 1974 private static immutable (string[]) identsToConvertToObjects = [ 1975 "dependency", // in JSON it's dependencies 1976 "subConfiguration", // in JSON it's subConfigurations 1977 ]; 1978 1979 /// SDL identifiers of JSON array w/o name conversion 1980 private static immutable (string[]) arrayIdents = [ 1981 "authors", 1982 "x:ddoxFilterArgs", 1983 "sourcePaths", 1984 "importPaths", 1985 "buildOptions", 1986 "libs", 1987 "sourceFiles", 1988 "buildRequirements", 1989 "excludedSourceFiles", 1990 "copyFiles", 1991 "versions", 1992 "debugVersions", 1993 "stringImportPaths", 1994 "preGenerateCommands", 1995 "postGenerateCommands", 1996 "preBuildCommands", 1997 "postBuildCommands", 1998 "dflags", 1999 "lflags", 2000 "platforms", 2001 ]; 2002 2003 protected bool isArrayItemNameIdent(string ident) { 2004 foreach(s; identsToConvertToArrays) { 2005 if (ident == s) 2006 return true; 2007 } 2008 return false; 2009 } 2010 2011 protected bool isObjectItemNameIdent(string ident) { 2012 foreach(s; identsToConvertToObjects) { 2013 if (ident == s) 2014 return true; 2015 } 2016 return false; 2017 } 2018 2019 protected bool isArrayIdent(string ident) { 2020 foreach(s; arrayIdents) { 2021 if (ident == s) 2022 return true; 2023 } 2024 return false; 2025 } 2026 2027 private void skipEol(ref JsonParser parser) { 2028 char ch = parser.skipSpaces; 2029 if (ch == 0) 2030 return; 2031 if (ch == '\n') { 2032 parser.nextChar; 2033 return; 2034 } 2035 parser.error("end of line expected"); 2036 } 2037 2038 private void parseSDLAttributes(ref JsonParser parser, bool ignorePlatformAttribute = true) { 2039 string attrName; 2040 Setting attrValue; 2041 for (;;) { 2042 char ch = parser.skipSpaces; 2043 if (ch == 0) 2044 return; 2045 if (ch == '\n') { 2046 parser.nextChar; 2047 return; 2048 } 2049 if (!JsonParser.isAlpha(ch)) 2050 parser.error("attr=value expected"); 2051 attrName = parser.parseIdent(); 2052 attrValue = new Setting(); 2053 ch = parser.skipSpaces; 2054 if (ch != '=') 2055 parser.error("= expected after " ~ attrName); 2056 ch = parser.nextChar; // skip '=' 2057 ch = parser.skipSpaces; 2058 if (ch == '\"' || ch == '`') { 2059 // string value 2060 string v = parser.parseString; 2061 attrValue = v; 2062 if (!ignorePlatformAttribute || attrName != "platform") 2063 this[attrName] = attrValue; 2064 continue; 2065 } 2066 if (JsonParser.isAlpha(ch)) { 2067 string v = parser.parseIdent; 2068 if (v == "true" || v == "on") { 2069 attrValue = true; 2070 this[attrName] = attrValue; 2071 continue; 2072 } 2073 if (v == "false" || v == "off") { 2074 attrValue = false; 2075 this[attrName] = attrValue; 2076 continue; 2077 } 2078 parser.error("unexpected attribue value " ~ v); 2079 } 2080 parser.error("only string and boolean values supported for SDL attributes now"); 2081 } 2082 } 2083 2084 // peek platform="value" from current line 2085 private string peekSDLPlatformAttribute(ref JsonParser parser) { 2086 string res = null; 2087 int oldpos = parser.pos; // save position 2088 for(;;) { 2089 char ch = parser.skipSpaces; 2090 if (ch == 0 || ch == '\n' || ch == '{' || ch == '}') 2091 break; 2092 if (parser.isAlpha(ch)) { 2093 string ident = parser.parseIdent; 2094 ch = parser.skipSpaces; 2095 if (ch != '=') 2096 continue; 2097 parser.nextChar; 2098 ch = parser.skipSpaces; 2099 string attrvalue; 2100 if (ch == '\"' || ch == '`') 2101 attrvalue = parser.parseString; 2102 else if (parser.isAlpha(ch)) 2103 attrvalue = parser.parseIdent; 2104 if (ident == "platform") { 2105 res = attrvalue; 2106 break; 2107 } 2108 } else if (ch == '\"' || ch == '`') { 2109 string str = parser.parseString; 2110 } else if (ch == '=') { 2111 parser.nextChar; 2112 continue; 2113 } else { 2114 break; 2115 } 2116 } 2117 parser.pos = oldpos; // restore position 2118 return res; 2119 } 2120 2121 private void skipPlatformAttribute(ref JsonParser parser) { 2122 char ch = parser.skipSpaces; 2123 int oldpos = parser.pos; 2124 if (parser.isAlpha(ch)) { 2125 string attrName = parser.parseIdent; 2126 if (attrName == "platform") { 2127 ch = parser.skipSpaces; 2128 if (ch == '=') { 2129 parser.nextChar; 2130 ch = parser.skipSpaces; 2131 string value = parser.parseString; 2132 return; // skipped platform attribute 2133 } 2134 } 2135 } 2136 // no changes 2137 parser.pos = oldpos; 2138 } 2139 2140 private Setting parseSDL(ref JsonParser parser, bool insideCurly = false) { 2141 //static import std.ascii; 2142 for (;;) { 2143 // looking for ident 2144 char ch = parser.skipSpaces; 2145 if (ch == 0) 2146 break; 2147 if (ch == '\n') { 2148 parser.nextChar; // skip 2149 continue; 2150 } 2151 if (ch == '}') { 2152 if (!insideCurly) 2153 parser.error("unexpected }"); 2154 parser.nextChar; // skip 2155 return this; 2156 } 2157 string ident = parser.parseIdent(); 2158 if (!ident.length) 2159 parser.error("identifier expected"); 2160 ch = parser.skipSpaces; 2161 string platform = peekSDLPlatformAttribute(parser); 2162 bool isArrayConvName = isArrayItemNameIdent(ident); 2163 bool isObjectConvName= isObjectItemNameIdent(ident); 2164 bool isArrayName = isArrayIdent(ident) || isArrayConvName; 2165 if (isArrayConvName || isObjectConvName) { 2166 import std.algorithm : endsWith; 2167 if (ident.endsWith("y")) 2168 ident = ident[0 .. $-1] ~ "ies"; // e.g. dependency->dependencies 2169 else if (!ident.endsWith("s")) 2170 ident = ident ~ "s"; // a.g. author->authors 2171 } 2172 if (platform.length) 2173 ident = ident ~ "-" ~ platform; 2174 Setting valueObj = this[ident]; // looking for existing object 2175 if (!valueObj) { // create if not exist 2176 valueObj = new Setting(); 2177 this[ident] = valueObj; 2178 } 2179 if (isArrayName) { 2180 if (!valueObj.isArray) { 2181 // convert to array 2182 valueObj.clear(SettingType.ARRAY); 2183 } 2184 } 2185 // now we have identifier 2186 if (ch == '\"' || ch == '`') { 2187 string value = parser.parseString; 2188 skipPlatformAttribute(parser); 2189 ch = parser.skipSpaces; 2190 if (ch == '{') { 2191 /* 2192 ident "name" { 2193 //... 2194 } 2195 */ 2196 parser.nextChar; // skip { 2197 Setting obj = isArrayName ? new Setting() : valueObj; 2198 obj["name"] = value; 2199 obj.parseSDL(parser, true); 2200 if (isArrayName) 2201 valueObj.array = valueObj.array ~ obj; 2202 continue; 2203 } 2204 if (JsonParser.isAlpha(ch)) { 2205 // ident=value pairs after "name" 2206 Setting obj = (isArrayName || isObjectConvName) ? new Setting() : valueObj; 2207 if (!isObjectConvName) 2208 obj["name"] = value; 2209 obj.parseSDLAttributes(parser); 2210 if (isArrayName) 2211 valueObj.array = valueObj.array ~ obj; 2212 else if (isObjectConvName) 2213 valueObj[value] = obj; 2214 continue; 2215 } 2216 if (isArrayName) { 2217 Setting[] values = valueObj.array; 2218 Setting svalue = new Setting(); 2219 svalue = value; 2220 values ~= svalue; 2221 for (;;) { 2222 skipPlatformAttribute(parser); 2223 ch = parser.skipSpaces; 2224 if (ch == '\n' || ch == 0) 2225 break; 2226 if (ch == '\"' || ch == '`') { 2227 value = parser.parseString; 2228 svalue = new Setting(); 2229 svalue = value; 2230 values ~= svalue; 2231 } else 2232 parser.error("array of strings expected"); 2233 } 2234 valueObj.array = values; 2235 } else { 2236 if (isObjectConvName) { 2237 string svalue = parser.parseString; 2238 valueObj[value] = svalue; 2239 } else { 2240 valueObj = value; 2241 } 2242 } 2243 skipPlatformAttribute(parser); 2244 skipEol(parser); 2245 continue; 2246 } else if (ch == '{') { 2247 // object 2248 parser.nextChar; // skip { 2249 if (isArrayName) { 2250 Setting[] values = valueObj.array; 2251 Setting item = new Setting(); 2252 item.clear(SettingType.OBJECT); 2253 item.parseSDL(parser, true); 2254 values ~= item; 2255 valueObj.array = values; 2256 } else { 2257 valueObj.parseSDL(parser, true); 2258 } 2259 continue; 2260 } else { 2261 parser.error("cannot parse SDL value"); 2262 } 2263 } 2264 if (insideCurly) 2265 parser.error("} expected"); 2266 return this; 2267 } 2268 2269 void parseSDL(string s) { 2270 clear(SettingType.NULL); 2271 JsonParser parser; 2272 parser.initialize(convertEols(s), true); 2273 parseSDL(parser); 2274 } 2275 2276 /// convert CR LF, LF CR, LF, CR to '\n' eol format 2277 static string convertEols(string src) { 2278 char[] res; 2279 res.assumeSafeAppend; 2280 for (int i = 0; i < src.length; i++) { 2281 char ch = src[i]; 2282 if (ch == '\r' || ch == '\n') { 2283 char nextch = i + 1 < src.length ? src[i + 1] : 0; 2284 if (nextch != ch && (nextch == '\r' || nextch == '\n')) { 2285 // pair \r\n or \n\r 2286 res ~= '\n'; 2287 i++; 2288 } else { 2289 // single \r or \n 2290 res ~= '\n'; 2291 } 2292 } else { 2293 res ~= ch; 2294 } 2295 } 2296 return res.dup; 2297 } 2298 2299 /// load from file; autodetect SDL format using ".sdl" and ".SDL" extension mask; returns true if loaded successfully 2300 bool load(string filename) { 2301 try { 2302 import std.algorithm : endsWith; 2303 string s = readText(filename); 2304 if (filename.endsWith(".sdl") || filename.endsWith(".SDL")) 2305 parseSDL(s); 2306 else 2307 parseJSON(s); 2308 return true; 2309 } catch (Exception e) { 2310 // Failed 2311 Log.e("exception while parsing json: ", e); 2312 return false; 2313 } 2314 } 2315 } 2316 2317