1 // Written in the D programming language. 2 /** 3 Haystack data types. 4 5 Copyright: Copyright (c) 2017, Radu Racariu <radu.racariu@gmail.com> 6 License: $(LINK2 www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 Authors: Radu Racariu 8 **/ 9 10 module haystack.tag; 11 // types that are public imported and part of the API 12 public import std.datetime : TimeOfDay; 13 public import std.datetime : Date; 14 public import std.datetime : DateTime; 15 public import std.datetime : SysTime; 16 17 import std.traits : Unqual; 18 19 /// Alternative name for a `Num` 20 public alias Number = Num; 21 22 /************************************************************ 23 Any haystack value type. 24 ************************************************************/ 25 struct Tag 26 { 27 import core.exception : RangeError; 28 import haystack.util.misc : SumType; 29 30 /// This tag allowed types 31 enum Type 32 { 33 Marker, 34 Na, 35 Bool, 36 Number, 37 Str, 38 XStr, 39 Coord, 40 Uri, 41 Ref, 42 Date, 43 Time, 44 DateTime, 45 List, 46 Dict, 47 Grid 48 } 49 50 // Re-map some declared types to their implementation 51 alias DateTime = SysTime; 52 53 // Adds the `SumType` traits 54 mixin SumType!Type; 55 56 this(inout Tag tag) inout pure nothrow 57 { 58 this.value = tag.value; 59 this.curType = tag.curType; 60 } 61 62 ref Tag opAssign(Tag val) return 63 { 64 clearCurValue(); 65 this.curType = val.curType; 66 static foreach (T; AllowedTypes) 67 { 68 if (curType == TagTypeForType!T) 69 { 70 T t = val.getValueForType!T(); 71 setValueForType!T(t); 72 return this; 73 } 74 } 75 return this; 76 } 77 78 // Auto-generate the consturctors and assign operator 79 // for all supported types 80 static foreach (T; AllowedTypes) 81 { 82 this(inout T t) inout pure nothrow 83 { 84 mixin(`value.`~valNameForType!T~` = t;`); 85 this.curType = TagTypeForType!T; 86 } 87 88 ref Tag opAssign(T val) return 89 { 90 clearCurValue(); 91 setValueForType!T(val); 92 this.curType = TagTypeForType!T; 93 return this; 94 } 95 } 96 97 /** 98 Construct a `Tag` from a string 99 */ 100 this(string val) inout pure nothrow 101 { 102 mixin(`value.`~valNameForType!Str~` = Str(val);`); 103 this.curType = Type.Str; 104 } 105 106 /** 107 Assigns a string to a `Tag` 108 */ 109 ref Tag opAssign(string val) return 110 { 111 clearCurValue(); 112 setValueForType(cast(Str) val); 113 this.curType = Type.Str; 114 return this; 115 } 116 117 /** 118 Construct a `Tag` from a double 119 */ 120 this(double val) inout pure nothrow 121 { 122 mixin(`value.`~valNameForType!Number~` = val;`); 123 this.curType = Type.Number; 124 } 125 126 /** 127 Asigns a double to a `Tag` 128 */ 129 ref Tag opAssign(double val) return 130 { 131 clearCurValue(); 132 setValueForType(cast(Num) val); 133 this.curType = Type.Number; 134 return this; 135 } 136 137 /** 138 Construct a `Tag` from a bool 139 */ 140 this(bool val) inout pure nothrow 141 { 142 mixin(`value.`~valNameForType!Bool~` = Bool(val);`); 143 this.curType = Type.Bool; 144 } 145 146 /** 147 Asigns a bool to a `Tag` 148 */ 149 ref Tag opAssign(bool val) return 150 { 151 clearCurValue(); 152 setValueForType(cast(Bool) val); 153 this.curType = Type.Bool; 154 return this; 155 } 156 157 158 /** 159 Gets the value for the type `T` 160 Returns `T.init` if there is not value or the value is not of type `T` 161 */ 162 T get(T)() pure inout 163 { 164 if (!hasValue || curType != TagTypeForType!T) 165 return T.init; 166 return getValueForType!T(); 167 } 168 169 /** 170 Returns true if there is a value of type `T` 171 */ 172 bool hasValue(T)() inout @safe nothrow 173 { 174 return curType == TagTypeForType!T; 175 } 176 177 /** 178 Returns true if there is a value of any type 179 */ 180 bool hasValue() pure inout @safe nothrow 181 { 182 return !empty; 183 } 184 185 /** 186 Returns the string representation of the current value 187 */ 188 string toString() inout 189 { 190 static foreach (T; AllowedTypes) 191 { 192 static if (!is(T == Str)) 193 { 194 if (curType == TagTypeForType!T) 195 return getValueForType!T().toString(); 196 } 197 else 198 { 199 if (curType == Type.Str) 200 return getValueForType!Str().val; 201 } 202 } 203 return ""; 204 } 205 206 // Check equality between 2 `Tag`s 207 bool opEquals()(auto ref const(Tag) other) const 208 { 209 if (this.curType != other.curType) 210 return false; 211 if (!this.hasValue && !other.hasValue) 212 return true; 213 214 static foreach (T; AllowedTypes) 215 { 216 if (curType == TagTypeForType!T) 217 return getValueForType!T() == other.getValueForType!T; 218 } 219 return false; 220 } 221 222 // Auto-generate equality operator for allowed types 223 static foreach (T; AllowedTypes) 224 { 225 static if (!is(T == Dict) && !is(T == List)) 226 { 227 bool opEquals()(auto ref const(T) value) const 228 { 229 if (this.curType != TagTypeForType!T) 230 return false; 231 return getValueForType!T() == (cast() value); 232 } 233 } 234 } 235 236 bool opEquals(T:int)(T value) const 237 { 238 if (this.curType != Type.Number) 239 return false; 240 return getValueForType!Number() == value; 241 } 242 243 bool opEquals(double value) const 244 { 245 if (this.curType != Type.Number) 246 return false; 247 return getValueForType!Number() == value; 248 } 249 250 bool opEquals(bool value) const 251 { 252 if (this.curType != Type.Bool) 253 return false; 254 return getValueForType!Bool() == value; 255 } 256 257 bool opEquals(string value) const 258 { 259 if (this.curType != Type.Str) 260 return false; 261 return getValueForType!Str() == value; 262 } 263 264 bool opEquals(const(List) value) const 265 { 266 if (this.curType != Type.List) 267 return false; 268 return (cast(List) getValueForType!List()) == (cast(List) value); 269 } 270 271 bool opEquals(const(Dict) value) const 272 { 273 if (this.curType != Type.Dict) 274 return false; 275 return (cast(Dict) getValueForType!Dict()) == (cast(Dict) value); 276 } 277 278 size_t toHash() pure const @safe nothrow 279 { 280 if (hasValue) 281 return 0; 282 283 static foreach (T; AllowedTypes) 284 { 285 if (curType == TagTypeForType!T) 286 { 287 static if (hasMember!(T, "toHash")) 288 return getValueForType!T().toHash(); 289 else 290 return (() @trusted => getValueForType!T().hashOf())(); 291 } 292 } 293 return 0; 294 } 295 296 int opCmp()(auto ref const(Tag) other) const 297 { 298 if (!this.hasValue) 299 return -1; 300 301 if (!other.hasValue) 302 return 1; 303 304 static foreach (T; AllowedTypes) 305 { 306 if (curType == TagTypeForType!T) 307 { 308 auto thisVal = getValueForType!T(); 309 auto thatVal = other.getValueForType!T(); 310 static if (hasMember!(T, "opCmp") || is (typeof(thisVal < thatVal))) 311 { 312 if (thisVal == thatVal) 313 return 0; 314 else if (thisVal < thatVal) 315 return -1; 316 else 317 return 1; 318 } 319 else 320 { 321 return 0; 322 } 323 } 324 } 325 return -1; 326 } 327 328 Tag opIndex(size_t index) pure inout 329 { 330 if (curType == Type.List) 331 return getValueForType!List()[index]; 332 if (curType == Type.Grid) 333 return Tag(cast(Tag[string]) getValueForType!Grid()[index]); 334 throw new RangeError(); 335 } 336 337 Tag opIndex(string index) pure inout 338 { 339 if (curType == Type.Dict) 340 return getValueForType!Dict()[index]; 341 throw new RangeError(); 342 } 343 344 auto opDispatch(string name, V...)(V vals) if (TypeHasMemeber!name) 345 { 346 enum hasFunc(T) = hasMember!(T, name) || (is(T == List) && name == "length"); 347 alias SupportingTypes = Filter!(ApplyRight!(hasFunc), AllowedTypes); 348 349 alias Type = SupportingTypes[0]; 350 Type t; 351 mixin(`alias ReturnType = typeof(t.`~name~`);`); 352 353 static foreach (T; SupportingTypes) 354 { 355 if (curType == TagTypeForType!T) 356 { 357 auto m = getValueForType!T(); 358 return mixin("m." ~ name); 359 } 360 } 361 return ReturnType.init; 362 } 363 } 364 365 // Aliases for complex `Tag` types 366 alias List = Tag[]; 367 alias Dict = Tag[string]; 368 alias Grid = GridImpl!(Dict); 369 370 /// Implements a simple pattern matching logic 371 template visit(Funcs...) if (Funcs.length > 0) 372 { 373 auto visit(Self)(auto ref Self tag) 374 { 375 import std.traits : ReturnType, Parameters; 376 377 static foreach (func; Funcs) // iterate all function handlers 378 { 379 static if (Parameters!func.length) // check if function has paramas 380 {{ 381 alias Return = ReturnType!func; // function return type 382 alias Param = Parameters!(func)[0]; // fist function param type 383 384 static foreach (T; Self.AllowedTypes) // iterate all Tag alllowed types 385 { 386 static if (is(Param:T)) // check if fist function param is convertible to tag type `T` 387 { 388 if (tag.curType == Self.TagTypeForType!T) // if the tag has a value of the type `T` 389 { 390 auto val = tag.getValueForType!T(); // det the `Tag` value for `T` 391 // call the handler with the current `Tag` value 392 static if (is(Return == void)) 393 { 394 func(val); 395 return; 396 } 397 else 398 { 399 return func(val); 400 } 401 } 402 } 403 } 404 }} 405 else 406 { 407 alias VisitReturn = typeof(return); 408 alias Return = ReturnType!func; 409 static if (is(VisitReturn == Return)) 410 { 411 return func(); 412 } 413 else 414 { 415 func(); 416 static if (is(VisitReturn == void)) 417 return; 418 else 419 return VisitReturn.init; 420 } 421 } 422 } 423 assert(false, "Can't match any handle for this Tag"); 424 } 425 } 426 unittest 427 { 428 Tag t; 429 t = "a string"; 430 assert(t.hasValue!Str()); 431 432 string str = t.visit!((Str s) => s.val); 433 assert(str == "a string"); 434 435 t.visit!((Num s) => s.val, () => str = ""); 436 assert(str == ""); 437 438 immutable Tag tag = immutable Tag([1.tag, "foo".tag, true.tag, (42.42).tag]); 439 440 assert(tag[0] == 1); 441 assert(tag[1] == "foo"); 442 assert(tag[2] == true); 443 assert(tag[3] == 42.42); 444 } 445 446 Str toStr()(auto ref const(Tag) tag) 447 { 448 import std.format : format; 449 import std.string : lastIndexOf; 450 import std.conv : to; 451 string tagVal = !tag.hasValue ? "null" : tag.toString(); 452 string tagType = to!string(tag.type); 453 return Str(format("%s(%s)", tagType[tagType.lastIndexOf('.') + 1..$], tagVal)); 454 } 455 456 unittest 457 { 458 Tag v; 459 assert(!v.hasValue); 460 // marker type 461 v = Marker(); 462 assert(v.hasValue); 463 assert(v.get!Marker == Marker()); 464 assert(marker == Marker()); 465 // bool type 466 v = Bool(true); 467 assert(v.get!Bool == true); 468 assert(v.get!Bool != false); 469 assert(v.get!Bool != 42); 470 assert(tag(false) != tag(true)); 471 // num type 472 v = Num(1); 473 assert(v.get!Num == Num(1)); 474 assert(v.get!Num == 1); 475 assert(v.get!Number == 1); 476 const Tag v2 = Num(2); 477 assert(v < v2); 478 479 v = Num(100.23, "%"); 480 assert(v.get!(Num).val == 100.23); 481 assert(v.get!(Num) == Num(100.23, "%")); 482 assert(tag(42) == tag(42)); 483 484 // str type 485 v = Str("foo bar"); 486 assert(v.get!Str != ""); 487 assert(v.get!Str == "foo bar"); 488 assert(tag("abc").get!Str == "abc"); 489 assert(v.toString() == "foo bar"); 490 // ref type 491 v = Ref("@baz"); 492 assert(v.get!Ref != Ref()); 493 assert(v.get!Ref == Ref("@baz")); 494 // list type 495 v = [Tag(Str("aa")), Tag(Num(2))]; 496 assert(v.length == 2); 497 assert(v[0] == cast(Str)"aa"); 498 assert(v[1] == cast(Num)2); 499 500 // dict type 501 Dict d; 502 d["test"] = cast(Str)"aaa"; 503 v = d; 504 assert(v["test"] == Str("aaa")); 505 506 // grid type 507 Dict row1; 508 row1["name"] = Str("Alice"); 509 row1["age"] = Num(20); 510 row1["user"] = Marker(); 511 Dict row2; 512 row2["name"] = Str("Bob"); 513 row2["age"] = Num(22); 514 row2["user"] = Marker(); 515 v = [row1.tag, row2.tag]; 516 assert(v.length == 2); 517 } 518 519 /** 520 Creates a Tag from a buildin type. 521 Returns: Tag 522 **/ 523 Tag tag(double val, string unit = "") pure 524 { 525 return Tag(Num(val, unit)); 526 } 527 /// ditto 528 Tag tag(long val, string unit = "") pure 529 { 530 return Tag(Num(val, unit)); 531 } 532 unittest 533 { 534 assert(double.infinity.tag == Tag(Num(double.infinity))); 535 assert(42.00.tag("C") == Tag(Num(42, "C"))); 536 537 assert(12.tag == Tag(Num(12))); 538 assert(42.tag("C") == Tag(Num(42, "C"))); 539 } 540 /// ditto 541 Tag tag(T:string)(T t) pure 542 { 543 return Tag(Str(t)); 544 } 545 unittest 546 { 547 assert("some string tag".tag == Tag(Str("some string tag"))); 548 } 549 /// ditto 550 Tag tag(T:bool)(T t) 551 { 552 return Tag(Bool(t)); 553 } 554 unittest 555 { 556 assert(false.tag == Tag(Bool(false))); 557 } 558 559 /** 560 Creates a Tag from a Tag allowed type. 561 Returns: Tag 562 **/ 563 Tag tag(T)(T t) if (Tag.allowed!(Unqual!T)) 564 { 565 return Tag(cast() t); 566 } 567 /// ditto 568 Tag tag(Num n) 569 { 570 return Tag(n); 571 } 572 /// ditto 573 Tag tag(List t) pure 574 { 575 return Tag(t); 576 } 577 unittest 578 { 579 assert(Ref("1234").tag == Tag(Ref("1234"))); 580 assert(SysTime(DateTime(2017, 1, 24, 12, 30, 33)).tag == Tag(SysTime(DateTime(2017, 1, 24, 12, 30, 33)))); 581 assert([1.tag, true.tag].tag == Tag([1.tag, true.tag])); 582 assert(["a": "foo".tag].tag == Tag(["a": "foo".tag])); 583 assert(Grid([["a": "foo".tag]]) == Tag(Grid([["a": "foo".tag]]))); 584 } 585 /** 586 Creates a Marker ($D Tag). 587 Returns: a Marker Tag 588 **/ 589 @property Tag marker() pure 590 { 591 return Tag(Marker()); 592 } 593 unittest 594 { 595 assert(marker == Tag(Marker())); 596 } 597 /** 598 Creates a Na ($D Tag). 599 Returns: a Na Tag 600 **/ 601 @property Tag na() pure 602 { 603 return Tag(Na()); 604 } 605 unittest 606 { 607 assert(na == Tag(Na())); 608 } 609 610 /** 611 Creates a Num ($D Tag). 612 Returns: a Num Tag 613 **/ 614 @property Tag num(double val, string unit = "") pure 615 { 616 return tag(val, unit); 617 } 618 /// ditto 619 @property Tag num(long val, string unit = "") pure 620 { 621 return tag(val, unit); 622 } 623 /// 624 unittest 625 { 626 assert(num(9, "m") == Tag(Num(9, "m"))); 627 } 628 629 /** 630 Creates a Str ($D Tag). 631 Returns: a Str Tag 632 **/ 633 @property Tag str(string val) pure 634 { 635 return tag(val); 636 } 637 /// 638 unittest 639 { 640 assert(str("hello world!") == Tag(Str("hello world!"))); 641 } 642 643 /** 644 Creates a Uri ($D Tag). 645 Returns: a Uri Tag 646 **/ 647 @property Tag uri(string val) pure 648 { 649 return Tag(Uri(val)); 650 } 651 /// 652 unittest 653 { 654 assert(uri("http://foo.bar") == Tag(Uri("http://foo.bar"))); 655 } 656 657 /** 658 Creates a Bool ($D Tag). 659 Returns: a Bool Tag 660 **/ 661 @property Tag boolean(bool val) pure 662 { 663 return tag(val); 664 } 665 /// 666 unittest 667 { 668 assert(boolean(false) != Tag(Bool(true))); 669 } 670 671 /************************************************************ 672 Marker tags are used to indicate a "type" or "is-a" relationship. 673 ************************************************************/ 674 struct Marker 675 { 676 string toString() const pure nothrow @nogc 677 { 678 return "M"; 679 } 680 } 681 unittest 682 { 683 auto m = marker(); 684 assert(m == Marker()); 685 } 686 /************************************************************ 687 Represents not available for missing data. 688 ************************************************************/ 689 struct Na 690 { 691 string toString() const pure nothrow @nogc 692 { 693 return "NA"; 694 } 695 } 696 unittest 697 { 698 auto m = marker(); 699 assert(m != Na()); 700 } 701 /************************************************************ 702 Holds boolean "true" or "false" values. 703 ************************************************************/ 704 struct Bool 705 { 706 // implicit a bool 707 alias val this; 708 /// the value 709 bool val; 710 711 string toString() const pure nothrow @nogc 712 { 713 return val ? "true" : "false"; 714 } 715 716 } 717 unittest 718 { 719 Bool b = cast(Bool) true; 720 assert(b); 721 assert(b != false); 722 assert(b.val != 12); 723 } 724 /************************************************************ 725 Holds a numeric 64 bit floating point value 726 ************************************************************/ 727 struct Num 728 { 729 /// the value 730 double val; 731 // implicit a double 732 alias val this; 733 /// the unit defined for this number 734 string unit; 735 736 this(Num n) inout pure nothrow 737 { 738 this.val = n.val; 739 this.unit = n.unit; 740 } 741 742 this(double val, string unit = null) inout pure nothrow 743 { 744 this.val = val; 745 this.unit = unit; 746 } 747 748 bool opEquals()(auto ref const(Num) num) const nothrow 749 { 750 return num.val == this.val && num.unit == this.unit; 751 } 752 753 bool opEquals(double d) const nothrow 754 { 755 return d == this.val && this.unit == string.init; 756 } 757 758 void opAssign(double val) nothrow 759 { 760 this.val = val; 761 } 762 763 @property bool isNaN() const pure nothrow 764 { 765 import std.math : isNaN; 766 return val.isNaN; 767 } 768 769 @property bool isINF() const pure nothrow 770 { 771 return val == val.infinity || val == (-1) * val.infinity; 772 } 773 774 @property T to(T)() const 775 { 776 import std.conv : to; 777 import std.traits : isIntegral; 778 779 static if (isIntegral!T) 780 if (isNaN) 781 return T.init; 782 return to!T(val); 783 } 784 785 string toString() inout 786 { 787 import std.conv : to; 788 return to!string(val) ~ (unit.length ? " " ~ unit : ""); 789 } 790 } 791 unittest 792 { 793 Num n = 100.0f; 794 assert(n == 100.0); 795 n += 2; 796 assert(n == 102.0); 797 Num x = cast(Num) 42; 798 assert(x == 42); 799 assert(x != 2); 800 auto y = Num(21, "cm"); 801 assert(y.val == 21 && y.unit == "cm"); 802 auto z = Num(1, "m"); 803 assert(z != y); 804 auto a = Num(12); 805 assert(a == 12 && a.unit == string.init); 806 807 a = Num(42, "$"); 808 auto t = a.tag(); 809 assert(t.get!(Num).val == 42 && t.get!(Num).unit == "$"); 810 811 const nan = Num.init; 812 assert(nan.to!int == 0); 813 } 814 /************************************************************ 815 Holds a string value 816 ************************************************************/ 817 struct Str 818 { 819 alias val this; // implicit a string 820 /// the value 821 string val; 822 823 string toString() const pure nothrow 824 { 825 return `"` ~ val ~ `"`; 826 } 827 } 828 unittest 829 { 830 Str s = cast(Str) "some text"; 831 Str x = Str("aaaa"); 832 assert(s == "some text"); 833 assert(s.val == "some text"); 834 assert(s != ""); 835 assert(Str("abc") == "abc"); 836 assert(Str("x") == Str("x")); 837 } 838 /************************************************************ 839 Latitude and Longitude global coordinates 840 ************************************************************/ 841 struct Coord 842 { 843 double lat, lng; 844 845 bool opEquals()(auto ref const(Coord) o) const 846 { 847 import std.math : approxEqual; 848 return approxEqual(lat, o.lat, 1e-8, 1e-8) && approxEqual(lng, o.lng, 1e-8, 1e-8); 849 } 850 851 string toString() const 852 { 853 import std.conv : to; 854 return "C(" ~ to!string(lat) ~ ", " ~ to!string(lng) ~ ")"; 855 } 856 } 857 unittest 858 { 859 Coord c1 = Coord(37.545826, -77.449188); 860 Coord c2 = Coord(37.545826, -77.449187); 861 assert(c1.lat == 37.545826); 862 assert(c1.lng == -77.449188); 863 assert(c1 != c2); 864 } 865 /************************************************************ 866 Extended typed string which specifies a type name a string encoding 867 ************************************************************/ 868 struct XStr 869 { 870 /// the type 871 string type; 872 /// the value 873 string val; 874 875 string toString() const pure nothrow 876 { 877 return type ~ "(" ~ val ~ ")"; 878 } 879 } 880 unittest 881 { 882 scope a = XStr("foo", "bar"); 883 assert(a.type == "foo" && a.val == "bar"); 884 } 885 886 /************************************************************ 887 Holds a Unversial Resource Identifier. 888 ************************************************************/ 889 struct Uri 890 { 891 /// the value 892 string val; 893 894 string toString() const 895 { 896 return "`" ~ val ~ "`"; 897 } 898 } 899 unittest 900 { 901 Uri s = cast(Uri) "/a/b/c"; 902 assert(s.val != ""); 903 assert(s == Uri("/a/b/c")); 904 } 905 /************************************************************ 906 Holds a Ref value 907 ************************************************************/ 908 struct Ref 909 { 910 /// the value 911 string val; 912 /// display 913 string dis; 914 915 this(string id, string dis = "") inout pure 916 { 917 this.val = id; 918 this.dis = dis; 919 } 920 921 /// constructs a Ref with a random UUID and an optional dis 922 static immutable(Ref) gen(string dis = "") 923 { 924 import std.uuid : randomUUID; 925 auto uuid = randomUUID(); 926 auto str = uuid.toString(); 927 return Ref(str[0 .. 18], dis); 928 } 929 930 @property string display() const 931 { 932 if (dis.length) 933 return dis; 934 return val; 935 } 936 937 size_t toHash() pure const @safe nothrow 938 { 939 return val.hashOf(); 940 } 941 942 bool opEquals()(auto ref const typeof(this) other) @safe pure const nothrow 943 { 944 return val == other.val; 945 } 946 947 string toString() const pure nothrow 948 { 949 return "@" ~ val; 950 } 951 952 @property bool empty() pure const nothrow 953 { 954 return val.length > 0; 955 } 956 } 957 unittest 958 { 959 Ref r = cast(Ref)"@foo"; 960 assert (r == Ref("@foo")); 961 assert(Ref.gen("xx").display == "xx"); 962 } 963 /************************************************************ 964 Holds an ISO 8601 time as hour, minute, seconds 965 and millisecs: 09:51:27.354 966 ************************************************************/ 967 struct Time 968 { 969 TimeOfDay tod; 970 int millis; 971 972 this(TimeOfDay tod, int millis = 0) 973 { 974 this.hour = tod.hour; 975 this.minute = tod.minute; 976 this.second = tod.second; 977 this.millis = millis; 978 } 979 980 this(int hour, int minute, int second, int millis = 0) 981 { 982 this.hour = hour; 983 this.minute = minute; 984 this.second = second; 985 this.millis = millis; 986 } 987 988 invariant() 989 { 990 assert(hour >= 0 && hour <= 23); 991 assert(minute >= 0 && minute <= 59); 992 assert(second >= 0 && second <= 59); 993 assert(millis >= 0 && millis <= 999); 994 } 995 996 alias tod this; 997 998 string toString() const pure nothrow 999 { 1000 import std.conv : to; 1001 return tod.toString ~ "." ~ to!string(millis); 1002 } 1003 } 1004 1005 unittest 1006 { 1007 Time t = Time(17, 47, 28, 99); 1008 assert(t.hour == 17); 1009 assert(t.minute == 47); 1010 assert(t.second == 28); 1011 assert(t.millis == 99); 1012 import std.exception : assertThrown; 1013 import core.time : TimeException; 1014 assertThrown!TimeException(Time(100, 47, 28)); 1015 } 1016 /** 1017 Check if Dict is empty. 1018 Returns: true if dict is empty. 1019 */ 1020 @property bool empty(const(Dict) dict) pure nothrow 1021 { 1022 return dict.length == 0; 1023 } 1024 1025 /** 1026 Check if Dict contains column. 1027 Returns: true if dict contains that column. 1028 */ 1029 bool has(const(Dict) dict, string col) pure nothrow 1030 { 1031 return (col in dict) != null; 1032 } 1033 1034 /** 1035 Check if Dict misses the column. 1036 Returns: true if dict doe not contain the column. 1037 */ 1038 bool missing(const(Dict) dict, string col) pure nothrow 1039 { 1040 return !dict.has(col); 1041 } 1042 unittest 1043 { 1044 Dict d; 1045 assert(d.empty); 1046 d["str"] = Str("a"); 1047 d["num"] = Num(12, "m/s"); 1048 d["num1"] = Num(45); 1049 d["marker"] = Marker(); 1050 d["bool"] = Bool(true); 1051 d["aaa"] = cast(Str)"foo"; 1052 1053 assert(d.has("str")); 1054 assert(d["str"].get!Str == "a"); 1055 assert(d["str"] == Str("a")); 1056 assert(d["num"] == Num(12, "m/s")); 1057 assert(d["num1"] == Num(45)); 1058 assert(d["marker"] == Marker()); 1059 assert(d["bool"] == Bool(true)); 1060 assert(d.missing("foo")); 1061 assert(!d.empty); 1062 } 1063 1064 /** 1065 Gets the 'id' key for the $(D Dict). Returns a $(D Ref.init) otherwise 1066 */ 1067 @property immutable(Ref) id(const(Dict) rec) 1068 { 1069 return rec.get!Ref("id"); 1070 } 1071 unittest 1072 { 1073 Dict d = ["id": Ref("id").tag]; 1074 assert(d.id == Ref("id")); 1075 assert(["x": 1.tag].id == Ref.init); 1076 } 1077 1078 /** 1079 Gets the 'dis' property for the $(D Dict). 1080 If the $(D Dict) has no 'id' or no 'dis' then an empty string is returned, 1081 if there is an 'id' property but without a 'dis' the 'id' value is returned. 1082 */ 1083 @property string dis(const(Dict) rec) 1084 { 1085 auto id = rec.get!Ref("id"); 1086 return id.dis != "" ? id.dis : id.val; 1087 } 1088 unittest 1089 { 1090 Dict d = ["id": Ref("id", "a dis").tag]; 1091 assert(d.dis == "a dis"); 1092 assert(["id": Ref("id").tag].dis == "id"); 1093 assert(["bad": 1.tag].dis == ""); 1094 } 1095 /** 1096 Get $(D Dict) property of type $(D T), or if property is missing $(D T.init) 1097 */ 1098 T get(T)(const(Dict) dict, string key) if (Tag.allowed!T) 1099 { 1100 if (dict.missing(key)) 1101 return T.init; 1102 return dict[key].get!T; 1103 } 1104 unittest 1105 { 1106 Dict d = ["val": Str("foo").tag]; 1107 assert(d.get!Str("val") == "foo"); 1108 } 1109 /** 1110 Test if $(D Dict) has property of type $(D T) 1111 */ 1112 bool has(T)(const(Dict) dict, string key) if (Tag.allowed!T) 1113 { 1114 if (dict.missing(key)) 1115 return false; 1116 return dict[key].hasValue!T; 1117 } 1118 unittest 1119 { 1120 Dict d = ["id": Ref("foo").tag, "num": 1.tag]; 1121 assert(d.has!Ref("id")); 1122 assert(!d.has!Bool("num")); 1123 assert(d.has!Num("num")); 1124 } 1125 1126 /** 1127 Test if the `Dict` has a `key` of null value 1128 1129 Params: 1130 dict = the ditionary. 1131 key = the key to look up 1132 1133 Returns: true if dict has a null value key. 1134 1135 */ 1136 @property bool isNull(const(Dict) dict, string key) 1137 { 1138 return dict.has(key) && dict[key] == Tag.init; 1139 } 1140 unittest 1141 { 1142 Dict d = ["foo": false.tag, "bar": Tag()]; 1143 assert(!d.isNull("id")); 1144 assert(d.isNull("bar")); 1145 } 1146 1147 /** 1148 Test if $(D Dict) misses property of type $(D T) 1149 */ 1150 bool missing(T)(const(Dict) dict, string key) if (Tag.allowed!T) 1151 { 1152 return !dict.has!T(key); 1153 } 1154 unittest 1155 { 1156 Dict d = ["num": 1.tag]; 1157 assert(!d.missing!Num("num")); 1158 assert(d.missing!Ref("num")); 1159 assert(d.missing!Bool("foo")); 1160 } 1161 1162 string toString(const(Dict) dict) 1163 { 1164 import std.array : appender; 1165 1166 auto buf = appender!(string)(); 1167 auto size = dict.length; 1168 buf.put('{'); 1169 foreach (entry; dict.byKeyValue) 1170 { 1171 buf.put(entry.key); 1172 buf.put(':'); 1173 buf.put(entry.value.toStr); 1174 if (--size > 0) 1175 buf.put(','); 1176 } 1177 buf.put('}'); 1178 return buf.data; 1179 } 1180 1181 string toString(const(List) list) 1182 { 1183 import std.array : appender; 1184 1185 auto buf = appender!(string)(); 1186 buf.put('['); 1187 foreach (i, entry; list) 1188 { 1189 buf.put(entry.toStr); 1190 if (i < list.length - 1) 1191 buf.put(", "); 1192 } 1193 buf.put(']'); 1194 return buf.data; 1195 } 1196 1197 /************************************************************ 1198 Haystack Grid. 1199 ************************************************************/ 1200 struct GridImpl(T) 1201 { 1202 /// Create $(D Grid) from a list of Dict 1203 this(inout T[] val) 1204 { 1205 this(val, T.init); 1206 } 1207 1208 /// Create a $(D Grid) from a list of $(D Dict) and a meta data $(D Dict) 1209 this(T[] val, T meta) 1210 { 1211 this._meta = meta; 1212 this.val = val; 1213 Col[string] cl; 1214 foreach(ref row; val) 1215 foreach (ref col; row.byKey) 1216 if (col !in cl) 1217 cl[col] = Col(col); 1218 this.columns = cl; 1219 } 1220 1221 /// Create $(D Grid) from const or immutable list of $(D Dict) 1222 this(const(T[]) val, const(T) meta) 1223 { 1224 this(cast(T[])val, cast(T) meta); 1225 } 1226 1227 /// Create dict from const or immutable Dict 1228 this(const(T[]) val, T meta) 1229 { 1230 this(cast(T[])val, meta); 1231 } 1232 1233 /// Create a $(D Grid) from a list of $(D Dict)s, a list of columns, and a meta data $(D Dict) 1234 this(const(T[]) val, Col[] cols, T meta = T.init, string ver = "3.0") 1235 { 1236 //this.ver = ver; 1237 this._meta = meta; 1238 this.val = cast(T[]) val; 1239 Col[string] cl; 1240 foreach(ref col; cols) 1241 cl[col.dis] = col; 1242 this.columns = cl; 1243 this.ver = ver; 1244 } 1245 1246 /// Create a $(D Grid) from a list of $(D Dict)s, a list of column names, and a meta data $(D Dict) 1247 this(const(T[]) val, string[] colsNames, T meta = T.init, string ver = "3.0") 1248 { 1249 this._meta = meta; 1250 this.val = cast(T[]) val; 1251 Col[string] cols; 1252 foreach(colName; colsNames) 1253 cols[colName] = Col(colName); 1254 this.columns = cols; 1255 this.ver = ver; 1256 } 1257 1258 /// This grid columns 1259 @property const(Col[]) cols() const 1260 { 1261 return columns.values; 1262 } 1263 /// This grid columns 1264 @property const(string[]) colNames() const 1265 { 1266 return columns.keys; 1267 } 1268 /// Has the column name 1269 @property bool hasCol(string col) const 1270 { 1271 return ((col in columns) !is null); 1272 } 1273 /// Mising a column 1274 @property bool missingCol(string col) const 1275 { 1276 return !hasCol(col); 1277 } 1278 1279 /// This grid meta data 1280 @property ref const(T) meta() const 1281 { 1282 return _meta; 1283 } 1284 1285 /// This grid rows 1286 @property size_t length() const 1287 { 1288 return val.length; 1289 } 1290 1291 /// True if the $(D Grid) had no meta and no rows 1292 @property bool empty() const 1293 { 1294 return val.length == 0 && meta.length == 0; 1295 } 1296 1297 const(T) opIndex(size_t index) const 1298 { 1299 return val[index]; 1300 } 1301 1302 int opApply(scope int delegate(ref immutable(T)) dg) const 1303 { 1304 int result = 0; 1305 foreach (dict; cast(immutable) val) 1306 { 1307 result = dg(dict); 1308 if (result) 1309 break; 1310 } 1311 return result; 1312 } 1313 1314 int opApply(scope int delegate(ref size_t i, ref immutable(T)) dg) const 1315 { 1316 int result = 0; 1317 foreach (i, dict; cast(immutable) val) 1318 { 1319 result = dg(i, dict); 1320 if (result) 1321 break; 1322 } 1323 return result; 1324 } 1325 1326 const(T[]) rows() const 1327 { 1328 return val; 1329 } 1330 1331 /// The column descriptor 1332 static struct Col 1333 { 1334 string dis; 1335 T meta; 1336 1337 this(string name, T meta = T.init) 1338 { 1339 this.dis = name; 1340 this.meta = meta; 1341 } 1342 } 1343 string ver = "3.0"; 1344 1345 string toString() const 1346 { 1347 import std.array : appender; 1348 auto buf = appender!(string)(); 1349 buf.put("<\n"); 1350 buf.put("ver: "); 1351 buf.put(ver); 1352 buf.put('\n'); 1353 buf.put("meta: "); 1354 buf.put((cast(Dict) meta).toString); 1355 buf.put('\n'); 1356 buf.put("[\n"); 1357 foreach (i, row; val) 1358 { 1359 buf.put((cast(Dict) row).toString()); 1360 if (i < val.length - 1) 1361 buf.put(','); 1362 buf.put('\n'); 1363 } 1364 buf.put(']'); 1365 buf.put(">\n"); 1366 return buf.data; 1367 } 1368 1369 this(immutable typeof(this) other) immutable 1370 { 1371 this.val = other.val; 1372 this._meta = other._meta; 1373 this.columns = other.columns; 1374 } 1375 1376 private: 1377 // Grid storage 1378 T[] val; 1379 T _meta; 1380 Col[string] columns; 1381 } 1382 unittest 1383 { 1384 // grid type 1385 Dict row1 = ["name": tag("Alice"), 1386 "age": tag(20), 1387 "user": marker]; 1388 Dict row2; 1389 row2["name"] = Str("Bob"); 1390 row2["age"] = Num(22); 1391 row2["user"] = Marker(); 1392 Grid grid = Grid([row1, row2]); 1393 assert(grid.cols.length == 3); 1394 assert(grid.hasCol("name")); 1395 assert(grid.missingCol("id")); 1396 foreach(ref rec; grid) 1397 assert(rec.length == 3); 1398 Tag t = [tag(1)]; 1399 row1 = ["name": tag([tag(1)])]; 1400 auto grid1 = Grid([row1]); 1401 assert( grid1[0]["name"][0] == Num(1).tag); 1402 } 1403 1404 /// Make an error $(D Grid) 1405 Grid errorGrid(string message) 1406 { 1407 return Grid([], ["err" : marker, "dis" : message.tag]); 1408 } 1409 Grid errorGrid(Exception ex) 1410 { 1411 auto desc = ["err" : marker, "dis" : ex.msg.tag]; 1412 if (ex.info) 1413 desc["errTrace"] = ex.info.toString.tag; 1414 return Grid([], desc); 1415 } 1416 unittest 1417 { 1418 assert(errorGrid(new Exception("an error")).meta["err"] == marker()); 1419 }