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