1 2 // Copyright Ferdinand Majerech 2011. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 /// Node of a YAML document. Used to read YAML data once it's loaded, 8 /// and to prepare data to emit. 9 module dyaml.node; 10 11 12 import std.algorithm; 13 import std.array; 14 import std.conv; 15 import std.datetime; 16 import std.exception; 17 import std.format; 18 import std.math; 19 import std.meta : AliasSeq; 20 import std.range; 21 import std.string; 22 import std.traits; 23 import std.typecons; 24 25 // FIXME: Switch back to upstream's when v2.101 is the oldest 26 // supported version (recommended: after v2.111 release). 27 import dyaml.stdsumtype; 28 29 import dyaml.event; 30 import dyaml.exception; 31 import dyaml.style; 32 33 /// Exception thrown at node related errors. 34 class NodeException : MarkedYAMLException 35 { 36 package: 37 // Construct a NodeException. 38 // 39 // Params: msg = Error message. 40 // start = Start position of the node. 41 this(string msg, const scope Mark start, 42 string file = __FILE__, size_t line = __LINE__) 43 @safe pure nothrow 44 { 45 super(msg, start, file, line); 46 } 47 } 48 49 // Node kinds. 50 enum NodeID : ubyte 51 { 52 scalar, 53 sequence, 54 mapping, 55 invalid 56 } 57 58 /// Null YAML type. Used in nodes with _null values. 59 struct YAMLNull 60 { 61 /// Used for string conversion. 62 string toString() const pure @safe nothrow {return "null";} 63 } 64 65 /// Invalid YAML type, used internally by SumType 66 private struct YAMLInvalid {} 67 68 // Merge YAML type, used to support "tag:yaml.org,2002:merge". 69 package struct YAMLMerge{} 70 71 // Key-value pair of YAML nodes, used in mappings. 72 private struct Pair 73 { 74 public: 75 /// Key node. 76 Node key; 77 /// Value node. 78 Node value; 79 80 /// Construct a Pair from two values. Will be converted to Nodes if needed. 81 this(K, V)(K key, V value) 82 { 83 static if(is(Unqual!K == Node)){this.key = key;} 84 else {this.key = Node(key);} 85 static if(is(Unqual!V == Node)){this.value = value;} 86 else {this.value = Node(value);} 87 } 88 89 /// Equality test with another Pair. 90 bool opEquals(const ref Pair rhs) const scope @safe 91 { 92 return key == rhs.key && value == rhs.value; 93 } 94 95 // Comparison with another Pair. 96 int opCmp(const scope ref Pair rhs) const scope @safe 97 { 98 const keyCmp = key.opCmp(rhs.key); 99 return keyCmp != 0 ? keyCmp 100 : value.opCmp(rhs.value); 101 } 102 103 /// 104 public void toString (scope void delegate(scope const(char)[]) @safe sink) 105 const scope @safe 106 { 107 // formattedWrite does not accept `scope` parameters 108 () @trusted { 109 formattedWrite(sink, "%s: %s", this.key, this.value); 110 }(); 111 } 112 } 113 114 enum NodeType 115 { 116 null_, 117 merge, 118 boolean, 119 integer, 120 decimal, 121 binary, 122 timestamp, 123 string, 124 mapping, 125 sequence, 126 invalid 127 } 128 129 /** YAML node. 130 * 131 * This is a pseudo-dynamic type that can store any YAML value, including a 132 * sequence or mapping of nodes. You can get data from a Node directly or 133 * iterate over it if it's a collection. 134 */ 135 struct Node 136 { 137 public: 138 alias Pair = .Pair; 139 140 package: 141 // YAML value type. 142 alias Value = SumType!( 143 YAMLInvalid, YAMLNull, YAMLMerge, 144 bool, long, real, ubyte[], SysTime, string, 145 Node.Pair[], Node[]); 146 147 // Can Value hold this type naturally? 148 enum allowed(T) = isIntegral!T || 149 isFloatingPoint!T || 150 isSomeString!T || 151 is(typeof({ Value i = T.init; })); 152 153 // Stored value. 154 Value value_; 155 // Start position of the node. 156 Mark startMark_; 157 158 // Tag of the node. 159 string tag_; 160 // Node scalar style. Used to remember style this node was loaded with. 161 ScalarStyle scalarStyle = ScalarStyle.invalid; 162 // Node collection style. Used to remember style this node was loaded with. 163 CollectionStyle collectionStyle = CollectionStyle.invalid; 164 165 public: 166 /** Construct a Node from a value. 167 * 168 * Any type except for Node can be stored in a Node, but default YAML 169 * types (integers, floats, strings, timestamps, etc.) will be stored 170 * more efficiently. To create a node representing a null value, 171 * construct it from YAMLNull. 172 * 173 * If value is a node, its value will be copied directly. The tag and 174 * other information attached to the original node will be discarded. 175 * 176 * If value is an array of nodes or pairs, it is stored directly. 177 * Otherwise, every value in the array is converted to a node, and 178 * those nodes are stored. 179 * 180 * Note that to emit any non-default types you store 181 * in a node, you need a Representer to represent them in YAML - 182 * otherwise emitting will fail. 183 * 184 * Params: value = Value to store in the node. 185 * tag = Overrides tag of the node when emitted, regardless 186 * of tag determined by Representer. Representer uses 187 * this to determine YAML data type when a D data type 188 * maps to multiple different YAML data types. Tag must 189 * be in full form, e.g. "tag:yaml.org,2002:int", not 190 * a shortcut, like "!!int". 191 */ 192 this(T)(T value, const string tag = null) @safe 193 if (allowed!T || isArray!T || isAssociativeArray!T || is(Unqual!T == Node) || castableToNode!T) 194 { 195 tag_ = tag; 196 197 //Unlike with assignment, we're just copying the value. 198 static if (is(Unqual!T == Node)) 199 { 200 setValue(value.value_); 201 } 202 else static if(isSomeString!T) 203 { 204 setValue(value.to!string); 205 } 206 else static if(is(Unqual!T == bool)) 207 { 208 setValue(cast(bool)value); 209 } 210 else static if(isIntegral!T) 211 { 212 setValue(cast(long)value); 213 } 214 else static if(isFloatingPoint!T) 215 { 216 setValue(cast(real)value); 217 } 218 else static if (isArray!T) 219 { 220 alias ElementT = Unqual!(ElementType!T); 221 // Construction from raw node or pair array. 222 static if(is(ElementT == Node) || is(ElementT == Node.Pair)) 223 { 224 setValue(value); 225 } 226 // Need to handle byte buffers separately. 227 else static if(is(ElementT == byte) || is(ElementT == ubyte)) 228 { 229 setValue(cast(ubyte[]) value); 230 } 231 else 232 { 233 Node[] nodes; 234 foreach(ref v; value) 235 { 236 nodes ~= Node(v); 237 } 238 setValue(nodes); 239 } 240 } 241 else static if (isAssociativeArray!T) 242 { 243 Node.Pair[] pairs; 244 foreach(k, ref v; value) 245 { 246 pairs ~= Pair(k, v); 247 } 248 setValue(pairs); 249 } 250 // User defined type. 251 else 252 { 253 setValue(value); 254 } 255 } 256 /// Construct a scalar node 257 @safe unittest 258 { 259 // Integer 260 { 261 auto node = Node(5); 262 } 263 // String 264 { 265 auto node = Node("Hello world!"); 266 } 267 // Floating point 268 { 269 auto node = Node(5.0f); 270 } 271 // Boolean 272 { 273 auto node = Node(true); 274 } 275 // Time 276 { 277 auto node = Node(SysTime(DateTime(2005, 6, 15, 20, 0, 0), UTC())); 278 } 279 // Integer, dumped as a string 280 { 281 auto node = Node(5, "tag:yaml.org,2002:str"); 282 } 283 } 284 /// Construct a sequence node 285 @safe unittest 286 { 287 // Will be emitted as a sequence (default for arrays) 288 { 289 auto seq = Node([1, 2, 3, 4, 5]); 290 } 291 // Will be emitted as a set (overridden tag) 292 { 293 auto set = Node([1, 2, 3, 4, 5], "tag:yaml.org,2002:set"); 294 } 295 // Can also store arrays of arrays 296 { 297 auto node = Node([[1,2], [3,4]]); 298 } 299 } 300 /// Construct a mapping node 301 @safe unittest 302 { 303 // Will be emitted as an unordered mapping (default for mappings) 304 auto map = Node([1 : "a", 2 : "b"]); 305 // Will be emitted as an ordered map (overridden tag) 306 auto omap = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:omap"); 307 // Will be emitted as pairs (overridden tag) 308 auto pairs = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:pairs"); 309 } 310 @safe unittest 311 { 312 { 313 auto node = Node(42); 314 assert(node.nodeID == NodeID.scalar); 315 assert(node.as!int == 42 && node.as!float == 42.0f && node.as!string == "42"); 316 } 317 318 { 319 auto node = Node("string"); 320 assert(node.as!string == "string"); 321 } 322 } 323 @safe unittest 324 { 325 with(Node([1, 2, 3])) 326 { 327 assert(nodeID == NodeID.sequence); 328 assert(length == 3); 329 assert(opIndex(2).as!int == 3); 330 } 331 332 } 333 @safe unittest 334 { 335 int[string] aa; 336 aa["1"] = 1; 337 aa["2"] = 2; 338 with(Node(aa)) 339 { 340 assert(nodeID == NodeID.mapping); 341 assert(length == 2); 342 assert(opIndex("2").as!int == 2); 343 } 344 } 345 @safe unittest 346 { 347 auto node = Node(Node(4, "tag:yaml.org,2002:str")); 348 assert(node == 4); 349 assert(node.tag_ == ""); 350 } 351 352 /** Construct a node from arrays of _keys and _values. 353 * 354 * Constructs a mapping node with key-value pairs from 355 * _keys and _values, keeping their order. Useful when order 356 * is important (ordered maps, pairs). 357 * 358 * 359 * keys and values must have equal length. 360 * 361 * 362 * If _keys and/or _values are nodes, they are stored directly/ 363 * Otherwise they are converted to nodes and then stored. 364 * 365 * Params: keys = Keys of the mapping, from first to last pair. 366 * values = Values of the mapping, from first to last pair. 367 * tag = Overrides tag of the node when emitted, regardless 368 * of tag determined by Representer. Representer uses 369 * this to determine YAML data type when a D data type 370 * maps to multiple different YAML data types. 371 * This is used to differentiate between YAML unordered 372 * mappings ("!!map"), ordered mappings ("!!omap"), and 373 * pairs ("!!pairs") which are all internally 374 * represented as an array of node pairs. Tag must be 375 * in full form, e.g. "tag:yaml.org,2002:omap", not a 376 * shortcut, like "!!omap". 377 * 378 */ 379 this(K, V)(K[] keys, V[] values, const string tag = null) 380 if(!(isSomeString!(K[]) || isSomeString!(V[]))) 381 in(keys.length == values.length, 382 "Lengths of keys and values arrays to construct " ~ 383 "a YAML node from don't match") 384 { 385 tag_ = tag; 386 387 Node.Pair[] pairs; 388 foreach(i; 0 .. keys.length){pairs ~= Pair(keys[i], values[i]);} 389 setValue(pairs); 390 } 391 /// 392 @safe unittest 393 { 394 // Will be emitted as an unordered mapping (default for mappings) 395 auto map = Node([1, 2], ["a", "b"]); 396 // Will be emitted as an ordered map (overridden tag) 397 auto omap = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:omap"); 398 // Will be emitted as pairs (overriden tag) 399 auto pairs = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:pairs"); 400 } 401 @safe unittest 402 { 403 with(Node(["1", "2"], [1, 2])) 404 { 405 assert(nodeID == NodeID.mapping); 406 assert(length == 2); 407 assert(opIndex("2").as!int == 2); 408 } 409 410 } 411 412 /// Is this node valid (initialized)? 413 @property bool isValid() const scope @safe pure nothrow @nogc 414 { 415 return value_.match!((const YAMLInvalid _) => false, _ => true); 416 } 417 418 /// Return tag of the node. 419 @property string tag() const return scope @safe pure nothrow @nogc 420 { 421 return tag_; 422 } 423 424 /// Return the start position of the node. 425 @property Mark startMark() const return scope @safe pure nothrow @nogc 426 { 427 return startMark_; 428 } 429 430 /** Equality test. 431 * 432 * If T is Node, recursively compares all subnodes. 433 * This might be quite expensive if testing entire documents. 434 * 435 * If T is not Node, gets a value of type T from the node and tests 436 * equality with that. 437 * 438 * To test equality with a null YAML value, use YAMLNull. 439 * 440 * Params: rhs = Variable to test equality with. 441 * 442 * Returns: true if equal, false otherwise. 443 */ 444 bool opEquals(const scope Node rhs) const scope @safe 445 { 446 return opCmp(rhs) == 0; 447 } 448 bool opEquals(T)(const scope auto ref T rhs) const @safe 449 { 450 try 451 { 452 auto stored = get!(T, No.stringConversion); 453 // NaNs aren't normally equal to each other, but we'll pretend they are. 454 static if(isFloatingPoint!T) 455 { 456 return rhs == stored || (isNaN(rhs) && isNaN(stored)); 457 } 458 else 459 { 460 return rhs == stored; 461 } 462 } 463 catch(NodeException e) 464 { 465 return false; 466 } 467 } 468 /// 469 @safe unittest 470 { 471 auto node = Node(42); 472 473 assert(node == 42); 474 assert(node != "42"); 475 assert(node != "43"); 476 477 auto node2 = Node(YAMLNull()); 478 assert(node2 == YAMLNull()); 479 480 const node3 = Node(42); 481 assert(node3 == 42); 482 } 483 484 /// Shortcut for get(). 485 alias as = get; 486 487 /** Get the value of the node as specified type. 488 * 489 * If the specifed type does not match type in the node, 490 * conversion is attempted. The stringConversion template 491 * parameter can be used to disable conversion from non-string 492 * types to strings. 493 * 494 * Numeric values are range checked, throwing if out of range of 495 * requested type. 496 * 497 * Timestamps are stored as std.datetime.SysTime. 498 * Binary values are decoded and stored as ubyte[]. 499 * 500 * To get a null value, use get!YAMLNull . This is to 501 * prevent getting null values for types such as strings or classes. 502 * 503 * $(BR)$(B Mapping default values:) 504 * 505 * $(PBR 506 * The '=' key can be used to denote the default value of a mapping. 507 * This can be used when a node is scalar in early versions of a program, 508 * but is replaced by a mapping later. Even if the node is a mapping, the 509 * get method can be used as if it was a scalar if it has a default value. 510 * This way, new YAML files where the node is a mapping can still be read 511 * by old versions of the program, which expect the node to be a scalar. 512 * ) 513 * 514 * Returns: Value of the node as specified type. 515 * 516 * Throws: NodeException if unable to convert to specified type, or if 517 * the value is out of range of requested type. 518 */ 519 inout(T) get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() inout @safe return scope 520 { 521 static assert (allowed!(Unqual!T) || 522 hasNodeConstructor!(inout(Unqual!T)) || 523 (!hasIndirections!(Unqual!T) && hasNodeConstructor!(Unqual!T))); 524 525 static if(!allowed!(Unqual!T)) 526 { 527 static if (hasSimpleNodeConstructor!(Unqual!T) || hasSimpleNodeConstructor!(inout(Unqual!T))) 528 { 529 alias params = AliasSeq!(this); 530 } 531 else static if (hasExpandedNodeConstructor!(Unqual!T) || hasExpandedNodeConstructor!(inout(Unqual!T))) 532 { 533 alias params = AliasSeq!(this, tag_); 534 } 535 else 536 { 537 static assert(0, "Unknown Node constructor?"); 538 } 539 540 static if (is(T == class)) 541 { 542 return new inout T(params); 543 } 544 else static if (is(T == struct)) 545 { 546 return T(params); 547 } 548 else 549 { 550 static assert(0, "Unhandled user type"); 551 } 552 } else { 553 static if (canBeType!T) 554 if (isType!(Unqual!T)) { return getValue!T; } 555 556 // If we're getting from a mapping and we're not getting Node.Pair[], 557 // we're getting the default value. 558 if(nodeID == NodeID.mapping){return this["="].get!( T, stringConversion);} 559 560 static if(isSomeString!T) 561 { 562 static if(!stringConversion) 563 { 564 enforce(type == NodeType..string, new NodeException( 565 "Node stores unexpected type: " ~ text(type) ~ 566 ". Expected: " ~ typeid(T).toString(), startMark_)); 567 return to!T(getValue!string); 568 } 569 else 570 { 571 // Try to convert to string. 572 try 573 { 574 return coerceValue!T().dup; 575 } 576 catch (MatchException e) 577 { 578 throw new NodeException("Unable to convert node value to string", startMark_); 579 } 580 } 581 } 582 else static if(isFloatingPoint!T) 583 { 584 final switch (type) 585 { 586 case NodeType.integer: 587 return to!T(getValue!long); 588 case NodeType.decimal: 589 return to!T(getValue!real); 590 case NodeType.binary: 591 case NodeType..string: 592 case NodeType.boolean: 593 case NodeType.null_: 594 case NodeType.merge: 595 case NodeType.invalid: 596 case NodeType.timestamp: 597 case NodeType.mapping: 598 case NodeType.sequence: 599 throw new NodeException("Node stores unexpected type: " ~ text(type) ~ 600 ". Expected: " ~ typeid(T).toString, startMark_); 601 } 602 } 603 else static if(isIntegral!T) 604 { 605 enforce(type == NodeType.integer, new NodeException("Node stores unexpected type: " ~ text(type) ~ 606 ". Expected: " ~ typeid(T).toString, startMark_)); 607 immutable temp = getValue!long; 608 enforce(temp >= T.min && temp <= T.max, 609 new NodeException("Integer value of type " ~ typeid(T).toString() ~ 610 " out of range. Value: " ~ to!string(temp), startMark_)); 611 return temp.to!T; 612 } 613 else throw new NodeException("Node stores unexpected type: " ~ text(type) ~ 614 ". Expected: " ~ typeid(T).toString, startMark_); 615 } 616 } 617 /// ditto 618 T get(T)() const 619 if (hasIndirections!(Unqual!T) && hasNodeConstructor!(Unqual!T) && (!hasNodeConstructor!(inout(Unqual!T)))) 620 { 621 static if (hasSimpleNodeConstructor!T) 622 { 623 alias params = AliasSeq!(this); 624 } 625 else static if (hasExpandedNodeConstructor!T) 626 { 627 alias params = AliasSeq!(this, tag_); 628 } 629 else 630 { 631 static assert(0, "Unknown Node constructor?"); 632 } 633 static if (is(T == class)) 634 { 635 return new T(params); 636 } 637 else static if (is(T == struct)) 638 { 639 return T(params); 640 } 641 else 642 { 643 static assert(0, "Unhandled user type"); 644 } 645 } 646 /// Automatic type conversion 647 @safe unittest 648 { 649 auto node = Node(42); 650 651 assert(node.get!int == 42); 652 assert(node.get!string == "42"); 653 assert(node.get!double == 42.0); 654 } 655 /// Scalar node to struct and vice versa 656 @safe unittest 657 { 658 import dyaml.dumper : dumper; 659 import dyaml.loader : Loader; 660 static struct MyStruct 661 { 662 int x, y, z; 663 664 this(int x, int y, int z) @safe 665 { 666 this.x = x; 667 this.y = y; 668 this.z = z; 669 } 670 671 this(scope const Node node) @safe 672 { 673 // `std.array.split` is not marked as taking a `scope` range, 674 // but we don't escape a reference. 675 scope parts = () @trusted { return node.as!string().split(":"); }(); 676 x = parts[0].to!int; 677 y = parts[1].to!int; 678 z = parts[2].to!int; 679 } 680 681 Node opCast(T: Node)() @safe 682 { 683 //Using custom scalar format, x:y:z. 684 auto scalar = format("%s:%s:%s", x, y, z); 685 //Representing as a scalar, with custom tag to specify this data type. 686 return Node(scalar, "!mystruct.tag"); 687 } 688 } 689 690 auto appender = new Appender!string; 691 692 // Dump struct to yaml document 693 dumper().dump(appender, Node(MyStruct(1,2,3))); 694 695 // Read yaml document back as a MyStruct 696 auto loader = Loader.fromString(appender.data); 697 Node node = loader.load(); 698 assert(node.as!MyStruct == MyStruct(1,2,3)); 699 } 700 /// Sequence node to struct and vice versa 701 @safe unittest 702 { 703 import dyaml.dumper : dumper; 704 import dyaml.loader : Loader; 705 static struct MyStruct 706 { 707 int x, y, z; 708 709 this(int x, int y, int z) @safe 710 { 711 this.x = x; 712 this.y = y; 713 this.z = z; 714 } 715 716 this(Node node) @safe 717 { 718 x = node[0].as!int; 719 y = node[1].as!int; 720 z = node[2].as!int; 721 } 722 723 Node opCast(T: Node)() 724 { 725 return Node([x, y, z], "!mystruct.tag"); 726 } 727 } 728 729 auto appender = new Appender!string; 730 731 // Dump struct to yaml document 732 dumper().dump(appender, Node(MyStruct(1,2,3))); 733 734 // Read yaml document back as a MyStruct 735 auto loader = Loader.fromString(appender.data); 736 Node node = loader.load(); 737 assert(node.as!MyStruct == MyStruct(1,2,3)); 738 } 739 /// Mapping node to struct and vice versa 740 @safe unittest 741 { 742 import dyaml.dumper : dumper; 743 import dyaml.loader : Loader; 744 static struct MyStruct 745 { 746 int x, y, z; 747 748 Node opCast(T: Node)() 749 { 750 auto pairs = [Node.Pair("x", x), 751 Node.Pair("y", y), 752 Node.Pair("z", z)]; 753 return Node(pairs, "!mystruct.tag"); 754 } 755 756 this(int x, int y, int z) 757 { 758 this.x = x; 759 this.y = y; 760 this.z = z; 761 } 762 763 this(Node node) @safe 764 { 765 x = node["x"].as!int; 766 y = node["y"].as!int; 767 z = node["z"].as!int; 768 } 769 } 770 771 auto appender = new Appender!string; 772 773 // Dump struct to yaml document 774 dumper().dump(appender, Node(MyStruct(1,2,3))); 775 776 // Read yaml document back as a MyStruct 777 auto loader = Loader.fromString(appender.data); 778 Node node = loader.load(); 779 assert(node.as!MyStruct == MyStruct(1,2,3)); 780 } 781 /// Classes can be used too 782 @system unittest { 783 import dyaml.dumper : dumper; 784 import dyaml.loader : Loader; 785 786 static class MyClass 787 { 788 int x, y, z; 789 790 this(int x, int y, int z) 791 { 792 this.x = x; 793 this.y = y; 794 this.z = z; 795 } 796 797 this(scope const Node node) @safe inout 798 { 799 // `std.array.split` is not marked as taking a `scope` range, 800 // but we don't escape a reference. 801 scope parts = () @trusted { return node.as!string().split(":"); }(); 802 x = parts[0].to!int; 803 y = parts[1].to!int; 804 z = parts[2].to!int; 805 } 806 807 ///Useful for Node.as!string. 808 override string toString() 809 { 810 return format("MyClass(%s, %s, %s)", x, y, z); 811 } 812 813 Node opCast(T: Node)() @safe 814 { 815 //Using custom scalar format, x:y:z. 816 auto scalar = format("%s:%s:%s", x, y, z); 817 //Representing as a scalar, with custom tag to specify this data type. 818 return Node(scalar, "!myclass.tag"); 819 } 820 override bool opEquals(Object o) 821 { 822 if (auto other = cast(MyClass)o) 823 { 824 return (other.x == x) && (other.y == y) && (other.z == z); 825 } 826 return false; 827 } 828 } 829 auto appender = new Appender!string; 830 831 // Dump class to yaml document 832 dumper().dump(appender, Node(new MyClass(1,2,3))); 833 834 // Read yaml document back as a MyClass 835 auto loader = Loader.fromString(appender.data); 836 Node node = loader.load(); 837 assert(node.as!MyClass == new MyClass(1,2,3)); 838 } 839 // Make sure custom tags and styles are kept. 840 @safe unittest 841 { 842 static struct MyStruct 843 { 844 Node opCast(T: Node)() 845 { 846 auto node = Node("hi", "!mystruct.tag"); 847 node.setStyle(ScalarStyle.doubleQuoted); 848 return node; 849 } 850 } 851 852 auto node = Node(MyStruct.init); 853 assert(node.tag == "!mystruct.tag"); 854 assert(node.scalarStyle == ScalarStyle.doubleQuoted); 855 } 856 // ditto, but for collection style 857 @safe unittest 858 { 859 static struct MyStruct 860 { 861 Node opCast(T: Node)() 862 { 863 auto node = Node(["hi"], "!mystruct.tag"); 864 node.setStyle(CollectionStyle.flow); 865 return node; 866 } 867 } 868 869 auto node = Node(MyStruct.init); 870 assert(node.tag == "!mystruct.tag"); 871 assert(node.collectionStyle == CollectionStyle.flow); 872 } 873 @safe unittest 874 { 875 assertThrown!NodeException(Node("42").get!int); 876 assertThrown!NodeException(Node("42").get!double); 877 assertThrown!NodeException(Node(long.max).get!ushort); 878 Node(YAMLNull()).get!YAMLNull; 879 } 880 @safe unittest 881 { 882 const node = Node(42); 883 assert(node.get!int == 42); 884 assert(node.get!string == "42"); 885 assert(node.get!double == 42.0); 886 887 immutable node2 = Node(42); 888 assert(node2.get!int == 42); 889 assert(node2.get!(const int) == 42); 890 assert(node2.get!(immutable int) == 42); 891 assert(node2.get!string == "42"); 892 assert(node2.get!(const string) == "42"); 893 assert(node2.get!(immutable string) == "42"); 894 assert(node2.get!double == 42.0); 895 assert(node2.get!(const double) == 42.0); 896 assert(node2.get!(immutable double) == 42.0); 897 } 898 899 /** If this is a collection, return its _length. 900 * 901 * Otherwise, throw NodeException. 902 * 903 * Returns: Number of elements in a sequence or key-value pairs in a mapping. 904 * 905 * Throws: NodeException if this is not a sequence nor a mapping. 906 */ 907 @property size_t length() const @safe 908 { 909 final switch(nodeID) 910 { 911 case NodeID.sequence: 912 return getValue!(Node[]).length; 913 case NodeID.mapping: 914 return getValue!(Pair[]).length; 915 case NodeID.scalar: 916 case NodeID.invalid: 917 throw new NodeException("Trying to get length of a " ~ nodeTypeString ~ " node", 918 startMark_); 919 } 920 } 921 @safe unittest 922 { 923 auto node = Node([1,2,3]); 924 assert(node.length == 3); 925 const cNode = Node([1,2,3]); 926 assert(cNode.length == 3); 927 immutable iNode = Node([1,2,3]); 928 assert(iNode.length == 3); 929 } 930 931 /** Get the element at specified index. 932 * 933 * If the node is a sequence, index must be integral. 934 * 935 * 936 * If the node is a mapping, return the value corresponding to the first 937 * key equal to index. containsKey() can be used to determine if a mapping 938 * has a specific key. 939 * 940 * To get element at a null index, use YAMLNull for index. 941 * 942 * Params: index = Index to use. 943 * 944 * Returns: Value corresponding to the index. 945 * 946 * Throws: NodeException if the index could not be found, 947 * non-integral index is used with a sequence or the node is 948 * not a collection. 949 */ 950 ref inout(Node) opIndex(T)(T index) inout return scope @safe 951 { 952 final switch (nodeID) 953 { 954 case NodeID.sequence: 955 checkSequenceIndex(index); 956 static if(isIntegral!T) 957 { 958 return getValue!(Node[])[index]; 959 } 960 else 961 { 962 assert(false, "Only integers may index sequence nodes"); 963 } 964 case NodeID.mapping: 965 auto idx = findPair(index); 966 if(idx >= 0) 967 { 968 return getValue!(Pair[])[idx].value; 969 } 970 971 string msg = "Mapping index not found" ~ (isSomeString!T ? ": " ~ to!string(index) : ""); 972 throw new NodeException(msg, startMark_); 973 case NodeID.scalar: 974 case NodeID.invalid: 975 throw new NodeException("Trying to index a " ~ nodeTypeString ~ " node", startMark_); 976 } 977 } 978 /// 979 @safe unittest 980 { 981 Node narray = Node([11, 12, 13, 14]); 982 Node nmap = Node(["11", "12", "13", "14"], [11, 12, 13, 14]); 983 984 assert(narray[0].as!int == 11); 985 assert(null !is collectException(narray[42])); 986 assert(nmap["11"].as!int == 11); 987 assert(nmap["14"].as!int == 14); 988 } 989 @safe unittest 990 { 991 Node narray = Node([11, 12, 13, 14]); 992 Node nmap = Node(["11", "12", "13", "14"], [11, 12, 13, 14]); 993 994 assert(narray[0].as!int == 11); 995 assert(null !is collectException(narray[42])); 996 assert(nmap["11"].as!int == 11); 997 assert(nmap["14"].as!int == 14); 998 assert(null !is collectException(nmap["42"])); 999 1000 narray.add(YAMLNull()); 1001 nmap.add(YAMLNull(), "Nothing"); 1002 assert(narray[4].as!YAMLNull == YAMLNull()); 1003 assert(nmap[YAMLNull()].as!string == "Nothing"); 1004 1005 assertThrown!NodeException(nmap[11]); 1006 assertThrown!NodeException(nmap[14]); 1007 } 1008 1009 /** Determine if a collection contains specified value. 1010 * 1011 * If the node is a sequence, check if it contains the specified value. 1012 * If it's a mapping, check if it has a value that matches specified value. 1013 * 1014 * Params: rhs = Item to look for. Use YAMLNull to check for a null value. 1015 * 1016 * Returns: true if rhs was found, false otherwise. 1017 * 1018 * Throws: NodeException if the node is not a collection. 1019 */ 1020 bool contains(T)(T rhs) const 1021 { 1022 return contains_!(T, No.key, "contains")(rhs); 1023 } 1024 @safe unittest 1025 { 1026 auto mNode = Node(["1", "2", "3"]); 1027 assert(mNode.contains("2")); 1028 const cNode = Node(["1", "2", "3"]); 1029 assert(cNode.contains("2")); 1030 immutable iNode = Node(["1", "2", "3"]); 1031 assert(iNode.contains("2")); 1032 } 1033 1034 1035 /** Determine if a mapping contains specified key. 1036 * 1037 * Params: rhs = Key to look for. Use YAMLNull to check for a null key. 1038 * 1039 * Returns: true if rhs was found, false otherwise. 1040 * 1041 * Throws: NodeException if the node is not a mapping. 1042 */ 1043 bool containsKey(T)(T rhs) const 1044 { 1045 return contains_!(T, Yes.key, "containsKey")(rhs); 1046 } 1047 1048 // Unittest for contains() and containsKey(). 1049 @safe unittest 1050 { 1051 auto seq = Node([1, 2, 3, 4, 5]); 1052 assert(seq.contains(3)); 1053 assert(seq.contains(5)); 1054 assert(!seq.contains("5")); 1055 assert(!seq.contains(6)); 1056 assert(!seq.contains(float.nan)); 1057 assertThrown!NodeException(seq.containsKey(5)); 1058 1059 auto seq2 = Node(["1", "2"]); 1060 assert(seq2.contains("1")); 1061 assert(!seq2.contains(1)); 1062 1063 auto map = Node(["1", "2", "3", "4"], [1, 2, 3, 4]); 1064 assert(map.contains(1)); 1065 assert(!map.contains("1")); 1066 assert(!map.contains(5)); 1067 assert(!map.contains(float.nan)); 1068 assert(map.containsKey("1")); 1069 assert(map.containsKey("4")); 1070 assert(!map.containsKey(1)); 1071 assert(!map.containsKey("5")); 1072 1073 assert(!seq.contains(YAMLNull())); 1074 assert(!map.contains(YAMLNull())); 1075 assert(!map.containsKey(YAMLNull())); 1076 seq.add(YAMLNull()); 1077 map.add("Nothing", YAMLNull()); 1078 assert(seq.contains(YAMLNull())); 1079 assert(map.contains(YAMLNull())); 1080 assert(!map.containsKey(YAMLNull())); 1081 map.add(YAMLNull(), "Nothing"); 1082 assert(map.containsKey(YAMLNull())); 1083 1084 auto map2 = Node([1, 2, 3, 4], [1, 2, 3, 4]); 1085 assert(!map2.contains("1")); 1086 assert(map2.contains(1)); 1087 assert(!map2.containsKey("1")); 1088 assert(map2.containsKey(1)); 1089 1090 // scalar 1091 assertThrown!NodeException(Node(1).contains(4)); 1092 assertThrown!NodeException(Node(1).containsKey(4)); 1093 1094 auto mapNan = Node([1.0, 2, double.nan], [1, double.nan, 5]); 1095 1096 assert(mapNan.contains(double.nan)); 1097 assert(mapNan.containsKey(double.nan)); 1098 } 1099 1100 /// Assignment (shallow copy) by value. 1101 void opAssign()(auto ref Node rhs) 1102 { 1103 assumeWontThrow(setValue(rhs.value_)); 1104 startMark_ = rhs.startMark_; 1105 tag_ = rhs.tag_; 1106 scalarStyle = rhs.scalarStyle; 1107 collectionStyle = rhs.collectionStyle; 1108 } 1109 // Unittest for opAssign(). 1110 @safe unittest 1111 { 1112 auto seq = Node([1, 2, 3, 4, 5]); 1113 auto assigned = seq; 1114 assert(seq == assigned, 1115 "Node.opAssign() doesn't produce an equivalent copy"); 1116 } 1117 1118 /** Set element at specified index in a collection. 1119 * 1120 * This method can only be called on collection nodes. 1121 * 1122 * If the node is a sequence, index must be integral. 1123 * 1124 * If the node is a mapping, sets the _value corresponding to the first 1125 * key matching index (including conversion, so e.g. "42" matches 42). 1126 * 1127 * If the node is a mapping and no key matches index, a new key-value 1128 * pair is added to the mapping. In sequences the index must be in 1129 * range. This ensures behavior siilar to D arrays and associative 1130 * arrays. 1131 * 1132 * To set element at a null index, use YAMLNull for index. 1133 * 1134 * Params: 1135 * value = Value to assign. 1136 * index = Index of the value to set. 1137 * 1138 * Throws: NodeException if the node is not a collection, index is out 1139 * of range or if a non-integral index is used on a sequence node. 1140 */ 1141 void opIndexAssign(K, V)(V value, K index) 1142 { 1143 final switch (nodeID) 1144 { 1145 case NodeID.sequence: 1146 checkSequenceIndex(index); 1147 static if(isIntegral!K || is(Unqual!K == bool)) 1148 { 1149 auto nodes = getValue!(Node[]); 1150 static if(is(Unqual!V == Node)){nodes[index] = value;} 1151 else {nodes[index] = Node(value);} 1152 setValue(nodes); 1153 return; 1154 } 1155 assert(false, "Only integers may index sequence nodes"); 1156 case NodeID.mapping: 1157 const idx = findPair(index); 1158 if(idx < 0){add(index, value);} 1159 else 1160 { 1161 auto pairs = as!(Node.Pair[])(); 1162 static if(is(Unqual!V == Node)){pairs[idx].value = value;} 1163 else {pairs[idx].value = Node(value);} 1164 setValue(pairs); 1165 } 1166 return; 1167 case NodeID.scalar: 1168 case NodeID.invalid: 1169 throw new NodeException("Trying to index a " ~ nodeTypeString ~ " node", startMark_); 1170 } 1171 } 1172 @safe unittest 1173 { 1174 with(Node([1, 2, 3, 4, 3])) 1175 { 1176 opIndexAssign(42, 3); 1177 assert(length == 5); 1178 assert(opIndex(3).as!int == 42); 1179 1180 opIndexAssign(YAMLNull(), 0); 1181 assert(opIndex(0) == YAMLNull()); 1182 } 1183 with(Node(["1", "2", "3"], [4, 5, 6])) 1184 { 1185 opIndexAssign(42, "3"); 1186 opIndexAssign(123, 456); 1187 assert(length == 4); 1188 assert(opIndex("3").as!int == 42); 1189 assert(opIndex(456).as!int == 123); 1190 1191 opIndexAssign(43, 3); 1192 //3 and "3" should be different 1193 assert(length == 5); 1194 assert(opIndex("3").as!int == 42); 1195 assert(opIndex(3).as!int == 43); 1196 1197 opIndexAssign(YAMLNull(), "2"); 1198 assert(opIndex("2") == YAMLNull()); 1199 } 1200 } 1201 1202 /** Return a range object iterating over a sequence, getting each 1203 * element as T. 1204 * 1205 * If T is Node, simply iterate over the nodes in the sequence. 1206 * Otherwise, convert each node to T during iteration. 1207 * 1208 * Throws: NodeException if the node is not a sequence or an element 1209 * could not be converted to specified type. 1210 */ 1211 template sequence(T = Node) 1212 { 1213 struct Range(N) 1214 { 1215 N subnodes; 1216 size_t position; 1217 1218 this(N nodes) 1219 { 1220 subnodes = nodes; 1221 position = 0; 1222 } 1223 1224 /* Input range functionality. */ 1225 bool empty() const @property { return position >= subnodes.length; } 1226 1227 void popFront() 1228 { 1229 enforce(!empty, "Attempted to popFront an empty sequence"); 1230 position++; 1231 } 1232 1233 T front() const @property 1234 { 1235 enforce(!empty, "Attempted to take the front of an empty sequence"); 1236 static if (is(Unqual!T == Node)) 1237 return subnodes[position]; 1238 else 1239 return subnodes[position].as!T; 1240 } 1241 1242 /* Forward range functionality. */ 1243 Range save() { return this; } 1244 1245 /* Bidirectional range functionality. */ 1246 void popBack() 1247 { 1248 enforce(!empty, "Attempted to popBack an empty sequence"); 1249 subnodes = subnodes[0 .. $ - 1]; 1250 } 1251 1252 T back() 1253 { 1254 enforce(!empty, "Attempted to take the back of an empty sequence"); 1255 static if (is(Unqual!T == Node)) 1256 return subnodes[$ - 1]; 1257 else 1258 return subnodes[$ - 1].as!T; 1259 } 1260 1261 /* Random-access range functionality. */ 1262 size_t length() const @property { return subnodes.length; } 1263 T opIndex(size_t index) 1264 { 1265 static if (is(Unqual!T == Node)) 1266 return subnodes[index]; 1267 else 1268 return subnodes[index].as!T; 1269 } 1270 1271 static assert(isInputRange!Range); 1272 static assert(isForwardRange!Range); 1273 static assert(isBidirectionalRange!Range); 1274 static assert(isRandomAccessRange!Range); 1275 } 1276 auto sequence() 1277 { 1278 enforce(nodeID == NodeID.sequence, 1279 new NodeException("Trying to 'sequence'-iterate over a " ~ nodeTypeString ~ " node", 1280 startMark_)); 1281 return Range!(Node[])(get!(Node[])); 1282 } 1283 auto sequence() const 1284 { 1285 enforce(nodeID == NodeID.sequence, 1286 new NodeException("Trying to 'sequence'-iterate over a " ~ nodeTypeString ~ " node", 1287 startMark_)); 1288 return Range!(const(Node)[])(get!(Node[])); 1289 } 1290 } 1291 @safe unittest 1292 { 1293 Node n1 = Node([1, 2, 3, 4]); 1294 int[int] array; 1295 Node n2 = Node(array); 1296 const n3 = Node([1, 2, 3, 4]); 1297 1298 auto r = n1.sequence!int.map!(x => x * 10); 1299 assert(r.equal([10, 20, 30, 40])); 1300 1301 assertThrown(n2.sequence); 1302 1303 auto r2 = n3.sequence!int.map!(x => x * 10); 1304 assert(r2.equal([10, 20, 30, 40])); 1305 } 1306 1307 /** Return a range object iterating over mapping's pairs. 1308 * 1309 * Throws: NodeException if the node is not a mapping. 1310 * 1311 */ 1312 template mapping() 1313 { 1314 struct Range(T) 1315 { 1316 T pairs; 1317 size_t position; 1318 1319 this(T pairs) @safe 1320 { 1321 this.pairs = pairs; 1322 position = 0; 1323 } 1324 1325 /* Input range functionality. */ 1326 bool empty() @safe { return position >= pairs.length; } 1327 1328 void popFront() @safe 1329 { 1330 enforce(!empty, "Attempted to popFront an empty mapping"); 1331 position++; 1332 } 1333 1334 auto front() @safe 1335 { 1336 enforce(!empty, "Attempted to take the front of an empty mapping"); 1337 return pairs[position]; 1338 } 1339 1340 /* Forward range functionality. */ 1341 Range save() @safe { return this; } 1342 1343 /* Bidirectional range functionality. */ 1344 void popBack() @safe 1345 { 1346 enforce(!empty, "Attempted to popBack an empty mapping"); 1347 pairs = pairs[0 .. $ - 1]; 1348 } 1349 1350 auto back() @safe 1351 { 1352 enforce(!empty, "Attempted to take the back of an empty mapping"); 1353 return pairs[$ - 1]; 1354 } 1355 1356 /* Random-access range functionality. */ 1357 size_t length() const @property @safe { return pairs.length; } 1358 auto opIndex(size_t index) @safe { return pairs[index]; } 1359 1360 static assert(isInputRange!Range); 1361 static assert(isForwardRange!Range); 1362 static assert(isBidirectionalRange!Range); 1363 static assert(isRandomAccessRange!Range); 1364 } 1365 1366 auto mapping() 1367 { 1368 enforce(nodeID == NodeID.mapping, 1369 new NodeException("Trying to 'mapping'-iterate over a " 1370 ~ nodeTypeString ~ " node", startMark_)); 1371 return Range!(Node.Pair[])(get!(Node.Pair[])); 1372 } 1373 auto mapping() const 1374 { 1375 enforce(nodeID == NodeID.mapping, 1376 new NodeException("Trying to 'mapping'-iterate over a " 1377 ~ nodeTypeString ~ " node", startMark_)); 1378 return Range!(const(Node.Pair)[])(get!(Node.Pair[])); 1379 } 1380 } 1381 @safe unittest 1382 { 1383 int[int] array; 1384 Node n = Node(array); 1385 n[1] = "foo"; 1386 n[2] = "bar"; 1387 n[3] = "baz"; 1388 1389 string[int] test; 1390 foreach (pair; n.mapping) 1391 test[pair.key.as!int] = pair.value.as!string.idup; 1392 1393 assert(test[1] == "foo"); 1394 assert(test[2] == "bar"); 1395 assert(test[3] == "baz"); 1396 1397 int[int] constArray = [1: 2, 3: 4]; 1398 const x = Node(constArray); 1399 foreach (pair; x.mapping) 1400 assert(pair.value == constArray[pair.key.as!int]); 1401 } 1402 1403 /** Return a range object iterating over mapping's keys. 1404 * 1405 * If K is Node, simply iterate over the keys in the mapping. 1406 * Otherwise, convert each key to T during iteration. 1407 * 1408 * Throws: NodeException if the nodes is not a mapping or an element 1409 * could not be converted to specified type. 1410 */ 1411 auto mappingKeys(K = Node)() const 1412 { 1413 enforce(nodeID == NodeID.mapping, 1414 new NodeException("Trying to 'mappingKeys'-iterate over a " 1415 ~ nodeTypeString ~ " node", startMark_)); 1416 static if (is(Unqual!K == Node)) 1417 return mapping.map!(pair => pair.key); 1418 else 1419 return mapping.map!(pair => pair.key.as!K); 1420 } 1421 @safe unittest 1422 { 1423 int[int] array; 1424 Node m1 = Node(array); 1425 m1["foo"] = 2; 1426 m1["bar"] = 3; 1427 1428 assert(m1.mappingKeys.equal(["foo", "bar"]) || m1.mappingKeys.equal(["bar", "foo"])); 1429 1430 const cm1 = Node(["foo": 2, "bar": 3]); 1431 1432 assert(cm1.mappingKeys.equal(["foo", "bar"]) || cm1.mappingKeys.equal(["bar", "foo"])); 1433 } 1434 1435 /** Return a range object iterating over mapping's values. 1436 * 1437 * If V is Node, simply iterate over the values in the mapping. 1438 * Otherwise, convert each key to V during iteration. 1439 * 1440 * Throws: NodeException if the nodes is not a mapping or an element 1441 * could not be converted to specified type. 1442 */ 1443 auto mappingValues(V = Node)() const 1444 { 1445 enforce(nodeID == NodeID.mapping, 1446 new NodeException("Trying to 'mappingValues'-iterate over a " 1447 ~ nodeTypeString ~ " node", startMark_)); 1448 static if (is(Unqual!V == Node)) 1449 return mapping.map!(pair => pair.value); 1450 else 1451 return mapping.map!(pair => pair.value.as!V); 1452 } 1453 @safe unittest 1454 { 1455 int[int] array; 1456 Node m1 = Node(array); 1457 m1["foo"] = 2; 1458 m1["bar"] = 3; 1459 1460 assert(m1.mappingValues.equal([2, 3]) || m1.mappingValues.equal([3, 2])); 1461 1462 const cm1 = Node(["foo": 2, "bar": 3]); 1463 1464 assert(cm1.mappingValues.equal([2, 3]) || cm1.mappingValues.equal([3, 2])); 1465 } 1466 1467 1468 /** Foreach over a sequence, getting each element as T. 1469 * 1470 * If T is Node, simply iterate over the nodes in the sequence. 1471 * Otherwise, convert each node to T during iteration. 1472 * 1473 * Throws: NodeException if the node is not a sequence or an 1474 * element could not be converted to specified type. 1475 */ 1476 int opApply(D)(D dg) if (isDelegate!D && (Parameters!D.length == 1)) 1477 { 1478 enforce(nodeID == NodeID.sequence, 1479 new NodeException("Trying to sequence-foreach over a " ~ nodeTypeString ~ " node", 1480 startMark_)); 1481 1482 int result; 1483 foreach(ref node; get!(Node[])) 1484 { 1485 static if(is(Unqual!(Parameters!D[0]) == Node)) 1486 { 1487 result = dg(node); 1488 } 1489 else 1490 { 1491 Parameters!D[0] temp = node.as!(Parameters!D[0]); 1492 result = dg(temp); 1493 } 1494 if(result){break;} 1495 } 1496 return result; 1497 } 1498 /// ditto 1499 int opApply(D)(D dg) const if (isDelegate!D && (Parameters!D.length == 1)) 1500 { 1501 enforce(nodeID == NodeID.sequence, 1502 new NodeException("Trying to sequence-foreach over a " ~ nodeTypeString ~ " node", 1503 startMark_)); 1504 1505 int result; 1506 foreach(ref node; get!(Node[])) 1507 { 1508 static if(is(Unqual!(Parameters!D[0]) == Node)) 1509 { 1510 result = dg(node); 1511 } 1512 else 1513 { 1514 Parameters!D[0] temp = node.as!(Parameters!D[0]); 1515 result = dg(temp); 1516 } 1517 if(result){break;} 1518 } 1519 return result; 1520 } 1521 @safe unittest 1522 { 1523 Node n1 = Node(11); 1524 Node n2 = Node(12); 1525 Node n3 = Node(13); 1526 Node n4 = Node(14); 1527 Node narray = Node([n1, n2, n3, n4]); 1528 const cNArray = narray; 1529 1530 int[] array, array2, array3; 1531 foreach(int value; narray) 1532 { 1533 array ~= value; 1534 } 1535 foreach(Node node; narray) 1536 { 1537 array2 ~= node.as!int; 1538 } 1539 foreach (const Node node; cNArray) 1540 { 1541 array3 ~= node.as!int; 1542 } 1543 assert(array == [11, 12, 13, 14]); 1544 assert(array2 == [11, 12, 13, 14]); 1545 assert(array3 == [11, 12, 13, 14]); 1546 } 1547 @safe unittest 1548 { 1549 string[] testStrs = ["1", "2", "3"]; 1550 auto node1 = Node(testStrs); 1551 int i = 0; 1552 foreach (string elem; node1) 1553 { 1554 assert(elem == testStrs[i]); 1555 i++; 1556 } 1557 const node2 = Node(testStrs); 1558 i = 0; 1559 foreach (string elem; node2) 1560 { 1561 assert(elem == testStrs[i]); 1562 i++; 1563 } 1564 immutable node3 = Node(testStrs); 1565 i = 0; 1566 foreach (string elem; node3) 1567 { 1568 assert(elem == testStrs[i]); 1569 i++; 1570 } 1571 } 1572 @safe unittest 1573 { 1574 auto node = Node(["a":1, "b":2, "c":3]); 1575 const cNode = node; 1576 assertThrown({foreach (Node n; node) {}}()); 1577 assertThrown({foreach (const Node n; cNode) {}}()); 1578 } 1579 1580 /** Foreach over a mapping, getting each key/value as K/V. 1581 * 1582 * If the K and/or V is Node, simply iterate over the nodes in the mapping. 1583 * Otherwise, convert each key/value to T during iteration. 1584 * 1585 * Throws: NodeException if the node is not a mapping or an 1586 * element could not be converted to specified type. 1587 */ 1588 int opApply(DG)(DG dg) if (isDelegate!DG && (Parameters!DG.length == 2)) 1589 { 1590 alias K = Parameters!DG[0]; 1591 alias V = Parameters!DG[1]; 1592 enforce(nodeID == NodeID.mapping, 1593 new NodeException("Trying to mapping-foreach over a " ~ nodeTypeString ~ " node", 1594 startMark_)); 1595 1596 int result; 1597 foreach(ref pair; get!(Node.Pair[])) 1598 { 1599 static if(is(Unqual!K == Node) && is(Unqual!V == Node)) 1600 { 1601 result = dg(pair.key, pair.value); 1602 } 1603 else static if(is(Unqual!K == Node)) 1604 { 1605 V tempValue = pair.value.as!V; 1606 result = dg(pair.key, tempValue); 1607 } 1608 else static if(is(Unqual!V == Node)) 1609 { 1610 K tempKey = pair.key.as!K; 1611 result = dg(tempKey, pair.value); 1612 } 1613 else 1614 { 1615 K tempKey = pair.key.as!K; 1616 V tempValue = pair.value.as!V; 1617 result = dg(tempKey, tempValue); 1618 } 1619 1620 if(result){break;} 1621 } 1622 return result; 1623 } 1624 /// ditto 1625 int opApply(DG)(DG dg) const if (isDelegate!DG && (Parameters!DG.length == 2)) 1626 { 1627 alias K = Parameters!DG[0]; 1628 alias V = Parameters!DG[1]; 1629 enforce(nodeID == NodeID.mapping, 1630 new NodeException("Trying to mapping-foreach over a " ~ nodeTypeString ~ " node", 1631 startMark_)); 1632 1633 int result; 1634 foreach(ref pair; get!(Node.Pair[])) 1635 { 1636 static if(is(Unqual!K == Node) && is(Unqual!V == Node)) 1637 { 1638 result = dg(pair.key, pair.value); 1639 } 1640 else static if(is(Unqual!K == Node)) 1641 { 1642 V tempValue = pair.value.as!V; 1643 result = dg(pair.key, tempValue); 1644 } 1645 else static if(is(Unqual!V == Node)) 1646 { 1647 K tempKey = pair.key.as!K; 1648 result = dg(tempKey, pair.value); 1649 } 1650 else 1651 { 1652 K tempKey = pair.key.as!K; 1653 V tempValue = pair.value.as!V; 1654 result = dg(tempKey, tempValue); 1655 } 1656 1657 if(result){break;} 1658 } 1659 return result; 1660 } 1661 @safe unittest 1662 { 1663 Node n1 = Node(cast(long)11); 1664 Node n2 = Node(cast(long)12); 1665 Node n3 = Node(cast(long)13); 1666 Node n4 = Node(cast(long)14); 1667 1668 Node k1 = Node("11"); 1669 Node k2 = Node("12"); 1670 Node k3 = Node("13"); 1671 Node k4 = Node("14"); 1672 1673 Node nmap1 = Node([Pair(k1, n1), 1674 Pair(k2, n2), 1675 Pair(k3, n3), 1676 Pair(k4, n4)]); 1677 1678 int[string] expected = ["11" : 11, 1679 "12" : 12, 1680 "13" : 13, 1681 "14" : 14]; 1682 int[string] array; 1683 foreach(string key, int value; nmap1) 1684 { 1685 array[key] = value; 1686 } 1687 assert(array == expected); 1688 1689 Node nmap2 = Node([Pair(k1, Node(cast(long)5)), 1690 Pair(k2, Node(true)), 1691 Pair(k3, Node(cast(real)1.0)), 1692 Pair(k4, Node("yarly"))]); 1693 1694 foreach(scope string key, scope Node value; nmap2) 1695 { 1696 switch(key) 1697 { 1698 case "11": assert(value.as!int == 5 ); break; 1699 case "12": assert(value.as!bool == true ); break; 1700 case "13": assert(value.as!float == 1.0 ); break; 1701 case "14": assert(value.as!string == "yarly"); break; 1702 default: assert(false); 1703 } 1704 } 1705 const nmap3 = nmap2; 1706 1707 foreach(const Node key, const Node value; nmap3) 1708 { 1709 switch(key.as!string) 1710 { 1711 case "11": assert(value.as!int == 5 ); break; 1712 case "12": assert(value.as!bool == true ); break; 1713 case "13": assert(value.as!float == 1.0 ); break; 1714 case "14": assert(value.as!string == "yarly"); break; 1715 default: assert(false); 1716 } 1717 } 1718 } 1719 @safe unittest 1720 { 1721 string[int] testStrs = [0: "1", 1: "2", 2: "3"]; 1722 auto node1 = Node(testStrs); 1723 foreach (const int i, string elem; node1) 1724 { 1725 assert(elem == testStrs[i]); 1726 } 1727 const node2 = Node(testStrs); 1728 foreach (const int i, string elem; node2) 1729 { 1730 assert(elem == testStrs[i]); 1731 } 1732 immutable node3 = Node(testStrs); 1733 foreach (const int i, string elem; node3) 1734 { 1735 assert(elem == testStrs[i]); 1736 } 1737 } 1738 @safe unittest 1739 { 1740 auto node = Node(["a", "b", "c"]); 1741 const cNode = node; 1742 assertThrown({foreach (Node a, Node b; node) {}}()); 1743 assertThrown({foreach (const Node a, const Node b; cNode) {}}()); 1744 } 1745 1746 /** Add an element to a sequence. 1747 * 1748 * This method can only be called on sequence nodes. 1749 * 1750 * If value is a node, it is copied to the sequence directly. Otherwise 1751 * value is converted to a node and then stored in the sequence. 1752 * 1753 * $(P When emitting, all values in the sequence will be emitted. When 1754 * using the !!set tag, the user needs to ensure that all elements in 1755 * the sequence are unique, otherwise $(B invalid) YAML code will be 1756 * emitted.) 1757 * 1758 * Params: value = Value to _add to the sequence. 1759 */ 1760 void add(T)(T value) 1761 { 1762 if (!isValid) 1763 { 1764 setValue(Node[].init); 1765 } 1766 enforce(nodeID == NodeID.sequence, 1767 new NodeException("Trying to add an element to a " ~ nodeTypeString ~ " node", startMark_)); 1768 1769 auto nodes = get!(Node[])(); 1770 static if(is(Unqual!T == Node)){nodes ~= value;} 1771 else {nodes ~= Node(value);} 1772 setValue(nodes); 1773 } 1774 @safe unittest 1775 { 1776 with(Node([1, 2, 3, 4])) 1777 { 1778 add(5.0f); 1779 assert(opIndex(4).as!float == 5.0f); 1780 } 1781 with(Node()) 1782 { 1783 add(5.0f); 1784 assert(opIndex(0).as!float == 5.0f); 1785 } 1786 with(Node(5.0f)) 1787 { 1788 assertThrown!NodeException(add(5.0f)); 1789 } 1790 with(Node([5.0f : true])) 1791 { 1792 assertThrown!NodeException(add(5.0f)); 1793 } 1794 } 1795 1796 /** Add a key-value pair to a mapping. 1797 * 1798 * This method can only be called on mapping nodes. 1799 * 1800 * If key and/or value is a node, it is copied to the mapping directly. 1801 * Otherwise it is converted to a node and then stored in the mapping. 1802 * 1803 * $(P It is possible for the same key to be present more than once in a 1804 * mapping. When emitting, all key-value pairs will be emitted. 1805 * This is useful with the "!!pairs" tag, but will result in 1806 * $(B invalid) YAML with "!!map" and "!!omap" tags.) 1807 * 1808 * Params: key = Key to _add. 1809 * value = Value to _add. 1810 */ 1811 void add(K, V)(K key, V value) 1812 { 1813 if (!isValid) 1814 { 1815 setValue(Node.Pair[].init); 1816 } 1817 enforce(nodeID == NodeID.mapping, 1818 new NodeException("Trying to add a key-value pair to a " ~ 1819 nodeTypeString ~ " node", 1820 startMark_)); 1821 1822 auto pairs = get!(Node.Pair[])(); 1823 pairs ~= Pair(key, value); 1824 setValue(pairs); 1825 } 1826 @safe unittest 1827 { 1828 with(Node([1, 2], [3, 4])) 1829 { 1830 add(5, "6"); 1831 assert(opIndex(5).as!string == "6"); 1832 } 1833 with(Node()) 1834 { 1835 add(5, "6"); 1836 assert(opIndex(5).as!string == "6"); 1837 } 1838 with(Node(5.0f)) 1839 { 1840 assertThrown!NodeException(add(5, "6")); 1841 } 1842 with(Node([5.0f])) 1843 { 1844 assertThrown!NodeException(add(5, "6")); 1845 } 1846 } 1847 1848 /** Determine whether a key is in a mapping, and access its value. 1849 * 1850 * This method can only be called on mapping nodes. 1851 * 1852 * Params: key = Key to search for. 1853 * 1854 * Returns: A pointer to the value (as a Node) corresponding to key, 1855 * or null if not found. 1856 * 1857 * Note: Any modification to the node can invalidate the returned 1858 * pointer. 1859 * 1860 * See_Also: contains 1861 */ 1862 inout(Node*) opBinaryRight(string op, K)(K key) inout 1863 if (op == "in") 1864 { 1865 enforce(nodeID == NodeID.mapping, new NodeException("Trying to use 'in' on a " ~ 1866 nodeTypeString ~ " node", startMark_)); 1867 1868 auto idx = findPair(key); 1869 if(idx < 0) 1870 { 1871 return null; 1872 } 1873 else 1874 { 1875 return &(get!(Node.Pair[])[idx].value); 1876 } 1877 } 1878 @safe unittest 1879 { 1880 auto mapping = Node(["foo", "baz"], ["bar", "qux"]); 1881 assert("bad" !in mapping && ("bad" in mapping) is null); 1882 Node* foo = "foo" in mapping; 1883 assert(foo !is null); 1884 assert(*foo == Node("bar")); 1885 assert(foo.get!string == "bar"); 1886 *foo = Node("newfoo"); 1887 assert(mapping["foo"] == Node("newfoo")); 1888 } 1889 @safe unittest 1890 { 1891 auto mNode = Node(["a": 2]); 1892 assert("a" in mNode); 1893 const cNode = Node(["a": 2]); 1894 assert("a" in cNode); 1895 immutable iNode = Node(["a": 2]); 1896 assert("a" in iNode); 1897 } 1898 1899 /** Remove first (if any) occurence of a value in a collection. 1900 * 1901 * This method can only be called on collection nodes. 1902 * 1903 * If the node is a sequence, the first node matching value is removed. 1904 * If the node is a mapping, the first key-value pair where _value 1905 * matches specified value is removed. 1906 * 1907 * Params: rhs = Value to _remove. 1908 * 1909 * Throws: NodeException if the node is not a collection. 1910 */ 1911 void remove(T)(T rhs) 1912 { 1913 remove_!(T, No.key, "remove")(rhs); 1914 } 1915 @safe unittest 1916 { 1917 with(Node([1, 2, 3, 4, 3])) 1918 { 1919 remove(3); 1920 assert(length == 4); 1921 assert(opIndex(2).as!int == 4); 1922 assert(opIndex(3).as!int == 3); 1923 1924 add(YAMLNull()); 1925 assert(length == 5); 1926 remove(YAMLNull()); 1927 assert(length == 4); 1928 } 1929 with(Node(["1", "2", "3"], [4, 5, 6])) 1930 { 1931 remove(4); 1932 assert(length == 2); 1933 add("nullkey", YAMLNull()); 1934 assert(length == 3); 1935 remove(YAMLNull()); 1936 assert(length == 2); 1937 } 1938 } 1939 1940 /** Remove element at the specified index of a collection. 1941 * 1942 * This method can only be called on collection nodes. 1943 * 1944 * If the node is a sequence, index must be integral. 1945 * 1946 * If the node is a mapping, remove the first key-value pair where 1947 * key matches index. 1948 * 1949 * If the node is a mapping and no key matches index, nothing is removed 1950 * and no exception is thrown. This ensures behavior siilar to D arrays 1951 * and associative arrays. 1952 * 1953 * Params: index = Index to remove at. 1954 * 1955 * Throws: NodeException if the node is not a collection, index is out 1956 * of range or if a non-integral index is used on a sequence node. 1957 */ 1958 void removeAt(T)(T index) 1959 { 1960 remove_!(T, Yes.key, "removeAt")(index); 1961 } 1962 @safe unittest 1963 { 1964 with(Node([1, 2, 3, 4, 3])) 1965 { 1966 removeAt(3); 1967 assertThrown!NodeException(removeAt("3")); 1968 assert(length == 4); 1969 assert(opIndex(3).as!int == 3); 1970 } 1971 with(Node(["1", "2", "3"], [4, 5, 6])) 1972 { 1973 // no integer 2 key, so don't remove anything 1974 removeAt(2); 1975 assert(length == 3); 1976 removeAt("2"); 1977 assert(length == 2); 1978 add(YAMLNull(), "nullval"); 1979 assert(length == 3); 1980 removeAt(YAMLNull()); 1981 assert(length == 2); 1982 } 1983 } 1984 1985 /// Compare with another _node. 1986 int opCmp(const scope ref Node rhs) const scope @safe 1987 { 1988 const bool hasNullTag = this.tag_ is null; 1989 // Only one of them is null: we can order nodes 1990 if ((hasNullTag) ^ (rhs.tag is null)) 1991 return hasNullTag ? -1 : 1; 1992 // Either both `null` or both have a value 1993 if (!hasNullTag) 1994 if (int result = std.algorithm.comparison.cmp(tag_, rhs.tag_)) 1995 return result; 1996 1997 static int cmp(T1, T2)(T1 a, T2 b) 1998 { 1999 return a > b ? 1 : 2000 a < b ? -1 : 2001 0; 2002 } 2003 2004 // Compare validity: if both valid, we have to compare further. 2005 if (!this.isValid()) 2006 return rhs.isValid() ? -1 : 0; 2007 if (!rhs.isValid()) 2008 return 1; 2009 if (const typeCmp = cmp(type, rhs.type)) 2010 return typeCmp; 2011 2012 static int compareCollections(T)(const scope ref Node lhs, const scope ref Node rhs) 2013 { 2014 const c1 = lhs.getValue!T; 2015 const c2 = rhs.getValue!T; 2016 if(c1 is c2){return 0;} 2017 if(c1.length != c2.length) 2018 { 2019 return cmp(c1.length, c2.length); 2020 } 2021 // Equal lengths, compare items. 2022 foreach(i; 0 .. c1.length) 2023 { 2024 const itemCmp = c1[i].opCmp(c2[i]); 2025 if(itemCmp != 0){return itemCmp;} 2026 } 2027 return 0; 2028 } 2029 2030 final switch(type) 2031 { 2032 case NodeType..string: 2033 return std.algorithm.cmp(getValue!string, 2034 rhs.getValue!string); 2035 case NodeType.integer: 2036 return cmp(getValue!long, rhs.getValue!long); 2037 case NodeType.boolean: 2038 const b1 = getValue!bool; 2039 const b2 = rhs.getValue!bool; 2040 return b1 ? b2 ? 0 : 1 2041 : b2 ? -1 : 0; 2042 case NodeType.binary: 2043 const b1 = getValue!(ubyte[]); 2044 const b2 = rhs.getValue!(ubyte[]); 2045 return std.algorithm.cmp(b1, b2); 2046 case NodeType.null_: 2047 return 0; 2048 case NodeType.decimal: 2049 const r1 = getValue!real; 2050 const r2 = rhs.getValue!real; 2051 if(isNaN(r1)) 2052 { 2053 return isNaN(r2) ? 0 : -1; 2054 } 2055 if(isNaN(r2)) 2056 { 2057 return 1; 2058 } 2059 // Fuzzy equality. 2060 if(r1 <= r2 + real.epsilon && r1 >= r2 - real.epsilon) 2061 { 2062 return 0; 2063 } 2064 return cmp(r1, r2); 2065 case NodeType.timestamp: 2066 const t1 = getValue!SysTime; 2067 const t2 = rhs.getValue!SysTime; 2068 return cmp(t1, t2); 2069 case NodeType.mapping: 2070 return compareCollections!(Pair[])(this, rhs); 2071 case NodeType.sequence: 2072 return compareCollections!(Node[])(this, rhs); 2073 case NodeType.merge: 2074 assert(false, "Cannot compare merge nodes"); 2075 case NodeType.invalid: 2076 assert(false, "Cannot compare invalid nodes"); 2077 } 2078 } 2079 2080 // Ensure opCmp is symmetric for collections 2081 @safe unittest 2082 { 2083 auto node1 = Node( 2084 [ 2085 Node("New York Yankees", "tag:yaml.org,2002:str"), 2086 Node("Atlanta Braves", "tag:yaml.org,2002:str") 2087 ], "tag:yaml.org,2002:seq" 2088 ); 2089 auto node2 = Node( 2090 [ 2091 Node("Detroit Tigers", "tag:yaml.org,2002:str"), 2092 Node("Chicago cubs", "tag:yaml.org,2002:str") 2093 ], "tag:yaml.org,2002:seq" 2094 ); 2095 assert(node1 > node2); 2096 assert(node2 < node1); 2097 } 2098 2099 // Compute hash of the node. 2100 hash_t toHash() nothrow const @trusted 2101 { 2102 const valueHash = value_.match!(v => hashOf(v)); 2103 2104 return tag_ is null ? valueHash : tag_.hashOf(valueHash); 2105 } 2106 @safe unittest 2107 { 2108 assert(Node(42).toHash() != Node(41).toHash()); 2109 assert(Node(42).toHash() != Node(42, "some-tag").toHash()); 2110 } 2111 2112 /// Get type of the node value. 2113 @property NodeType type() const scope @safe pure nothrow @nogc 2114 { 2115 return this.value_.match!( 2116 (const bool _) => NodeType.boolean, 2117 (const long _) => NodeType.integer, 2118 (const Node[] _) => NodeType.sequence, 2119 (const ubyte[] _) => NodeType.binary, 2120 (const string _) => NodeType..string, 2121 (const Node.Pair[] _) => NodeType.mapping, 2122 (const SysTime _) => NodeType.timestamp, 2123 (const YAMLNull _) => NodeType.null_, 2124 (const YAMLMerge _) => NodeType.merge, 2125 (const real _) => NodeType.decimal, 2126 (const YAMLInvalid _) => NodeType.invalid, 2127 ); 2128 } 2129 2130 /// Get the kind of node this is. 2131 @property NodeID nodeID() const scope @safe pure nothrow @nogc 2132 { 2133 final switch (type) 2134 { 2135 case NodeType.sequence: 2136 return NodeID.sequence; 2137 case NodeType.mapping: 2138 return NodeID.mapping; 2139 case NodeType.boolean: 2140 case NodeType.integer: 2141 case NodeType.binary: 2142 case NodeType..string: 2143 case NodeType.timestamp: 2144 case NodeType.null_: 2145 case NodeType.merge: 2146 case NodeType.decimal: 2147 return NodeID.scalar; 2148 case NodeType.invalid: 2149 return NodeID.invalid; 2150 } 2151 } 2152 package: 2153 2154 // Get a string representation of the node tree. Used for debugging. 2155 // 2156 // Params: level = Level of the node in the tree. 2157 // 2158 // Returns: String representing the node tree. 2159 @property string debugString(uint level = 0) const scope @safe 2160 { 2161 string indent; 2162 foreach(i; 0 .. level){indent ~= " ";} 2163 2164 final switch (nodeID) 2165 { 2166 case NodeID.invalid: 2167 return indent ~ "invalid"; 2168 case NodeID.sequence: 2169 string result = indent ~ "sequence:\n"; 2170 foreach(ref node; get!(Node[])) 2171 { 2172 result ~= node.debugString(level + 1); 2173 } 2174 return result; 2175 case NodeID.mapping: 2176 string result = indent ~ "mapping:\n"; 2177 foreach(ref pair; get!(Node.Pair[])) 2178 { 2179 result ~= indent ~ " pair\n"; 2180 result ~= pair.key.debugString(level + 2); 2181 result ~= pair.value.debugString(level + 2); 2182 } 2183 return result; 2184 case NodeID.scalar: 2185 return indent ~ "scalar(" ~ 2186 (convertsTo!string ? get!string : text(type)) ~ ")\n"; 2187 } 2188 } 2189 2190 2191 public: 2192 @property string nodeTypeString() const scope @safe pure nothrow @nogc 2193 { 2194 final switch (nodeID) 2195 { 2196 case NodeID.mapping: 2197 return "mapping"; 2198 case NodeID.sequence: 2199 return "sequence"; 2200 case NodeID.scalar: 2201 return "scalar"; 2202 case NodeID.invalid: 2203 return "invalid"; 2204 } 2205 } 2206 2207 // Determine if the value can be converted to specified type. 2208 @property bool convertsTo(T)() const 2209 { 2210 if(isType!T){return true;} 2211 2212 // Every type allowed in Value should be convertible to string. 2213 static if(isSomeString!T) {return true;} 2214 else static if(isFloatingPoint!T){return type.among!(NodeType.integer, NodeType.decimal);} 2215 else static if(isIntegral!T) {return type == NodeType.integer;} 2216 else static if(is(Unqual!T==bool)){return type == NodeType.boolean;} 2217 else {return false;} 2218 } 2219 /** 2220 * Sets the style of this node when dumped. 2221 * 2222 * Params: style = Any valid style. 2223 */ 2224 void setStyle(CollectionStyle style) @safe 2225 { 2226 enforce(!isValid || (nodeID.among(NodeID.mapping, NodeID.sequence)), new NodeException( 2227 "Cannot set collection style for non-collection nodes", startMark_)); 2228 collectionStyle = style; 2229 } 2230 /// Ditto 2231 void setStyle(ScalarStyle style) @safe 2232 { 2233 enforce(!isValid || (nodeID == NodeID.scalar), new NodeException( 2234 "Cannot set scalar style for non-scalar nodes", startMark_)); 2235 scalarStyle = style; 2236 } 2237 /// 2238 @safe unittest 2239 { 2240 import dyaml.dumper; 2241 auto stream = new Appender!string(); 2242 auto node = Node([1, 2, 3, 4, 5]); 2243 node.setStyle(CollectionStyle.block); 2244 2245 auto dumper = dumper(); 2246 dumper.dump(stream, node); 2247 } 2248 /// 2249 @safe unittest 2250 { 2251 import dyaml.dumper; 2252 auto stream = new Appender!string(); 2253 auto node = Node(4); 2254 node.setStyle(ScalarStyle.literal); 2255 2256 auto dumper = dumper(); 2257 dumper.dump(stream, node); 2258 } 2259 @safe unittest 2260 { 2261 assertThrown!NodeException(Node(4).setStyle(CollectionStyle.block)); 2262 assertThrown!NodeException(Node([4]).setStyle(ScalarStyle.literal)); 2263 } 2264 @safe unittest 2265 { 2266 import dyaml.dumper; 2267 { 2268 auto stream = new Appender!string(); 2269 auto node = Node([1, 2, 3, 4, 5]); 2270 node.setStyle(CollectionStyle.block); 2271 auto dumper = dumper(); 2272 dumper.explicitEnd = false; 2273 dumper.explicitStart = false; 2274 dumper.YAMLVersion = null; 2275 dumper.dump(stream, node); 2276 2277 //Block style should start with a hyphen. 2278 assert(stream.data[0] == '-'); 2279 } 2280 { 2281 auto stream = new Appender!string(); 2282 auto node = Node([1, 2, 3, 4, 5]); 2283 node.setStyle(CollectionStyle.flow); 2284 auto dumper = dumper(); 2285 dumper.explicitEnd = false; 2286 dumper.explicitStart = false; 2287 dumper.YAMLVersion = null; 2288 dumper.dump(stream, node); 2289 2290 //Flow style should start with a bracket. 2291 assert(stream.data[0] == '['); 2292 } 2293 { 2294 auto stream = new Appender!string(); 2295 auto node = Node(1); 2296 node.setStyle(ScalarStyle.singleQuoted); 2297 auto dumper = dumper(); 2298 dumper.explicitEnd = false; 2299 dumper.explicitStart = false; 2300 dumper.YAMLVersion = null; 2301 dumper.dump(stream, node); 2302 2303 assert(stream.data == "!!int '1'\n"); 2304 } 2305 { 2306 auto stream = new Appender!string(); 2307 auto node = Node(1); 2308 node.setStyle(ScalarStyle.doubleQuoted); 2309 auto dumper = dumper(); 2310 dumper.explicitEnd = false; 2311 dumper.explicitStart = false; 2312 dumper.YAMLVersion = null; 2313 dumper.dump(stream, node); 2314 2315 assert(stream.data == "!!int \"1\"\n"); 2316 } 2317 } 2318 2319 private: 2320 // Determine if the value stored by the node is of specified type. 2321 // 2322 // This only works for default YAML types, not for user defined types. 2323 @property bool isType(T)() const 2324 { 2325 return value_.match!( 2326 (const T _) => true, 2327 _ => false, 2328 ); 2329 } 2330 2331 /// Check at compile time if a type is stored natively 2332 enum canBeType (T) = is(typeof({ value_.match!((const T _) => true, _ => false); })); 2333 2334 2335 // Implementation of contains() and containsKey(). 2336 bool contains_(T, Flag!"key" key, string func)(T rhs) const 2337 { 2338 final switch (nodeID) 2339 { 2340 case NodeID.mapping: 2341 return findPair!(T, key)(rhs) >= 0; 2342 case NodeID.sequence: 2343 static if(!key) 2344 { 2345 foreach(ref node; getValue!(Node[])) 2346 { 2347 if(node == rhs){return true;} 2348 } 2349 return false; 2350 } 2351 else 2352 { 2353 throw new NodeException("Trying to use " ~ func ~ "() on a " ~ nodeTypeString ~ " node", 2354 startMark_); 2355 } 2356 case NodeID.scalar: 2357 case NodeID.invalid: 2358 throw new NodeException("Trying to use " ~ func ~ "() on a " ~ nodeTypeString ~ " node", 2359 startMark_); 2360 } 2361 2362 } 2363 2364 // Implementation of remove() and removeAt() 2365 void remove_(T, Flag!"key" key, string func)(T rhs) 2366 { 2367 static void removeElem(E, I)(ref Node node, I index) 2368 { 2369 auto elems = node.getValue!(E[]); 2370 moveAll(elems[cast(size_t)index + 1 .. $], elems[cast(size_t)index .. $ - 1]); 2371 elems.length = elems.length - 1; 2372 node.setValue(elems); 2373 } 2374 2375 final switch (nodeID) 2376 { 2377 case NodeID.mapping: 2378 const index = findPair!(T, key)(rhs); 2379 if(index >= 0){removeElem!Pair(this, index);} 2380 break; 2381 case NodeID.sequence: 2382 static long getIndex(ref Node node, ref T rhs) 2383 { 2384 foreach(idx, ref elem; node.get!(Node[])) 2385 { 2386 if(elem.convertsTo!T && elem.as!(T, No.stringConversion) == rhs) 2387 { 2388 return idx; 2389 } 2390 } 2391 return -1; 2392 } 2393 2394 const index = select!key(rhs, getIndex(this, rhs)); 2395 2396 // This throws if the index is not integral. 2397 checkSequenceIndex(index); 2398 2399 static if(isIntegral!(typeof(index))){removeElem!Node(this, index); break; } 2400 else {assert(false, "Non-integral sequence index");} 2401 case NodeID.scalar: 2402 case NodeID.invalid: 2403 throw new NodeException("Trying to " ~ func ~ "() from a " ~ nodeTypeString ~ " node", 2404 startMark_); 2405 } 2406 } 2407 2408 // Get index of pair with key (or value, if key is false) matching index. 2409 // Cannot be inferred @safe due to https://issues.dlang.org/show_bug.cgi?id=16528 2410 sizediff_t findPair(T, Flag!"key" key = Yes.key)(const scope ref T index) 2411 const scope @safe 2412 { 2413 const pairs = getValue!(Pair[])(); 2414 const(Node)* node; 2415 foreach(idx, ref const(Pair) pair; pairs) 2416 { 2417 static if(key){node = &pair.key;} 2418 else {node = &pair.value;} 2419 2420 2421 const bool typeMatch = (isFloatingPoint!T && (node.type.among!(NodeType.integer, NodeType.decimal))) || 2422 (isIntegral!T && node.type == NodeType.integer) || 2423 (is(Unqual!T==bool) && node.type == NodeType.boolean) || 2424 (isSomeString!T && node.type == NodeType..string) || 2425 (node.isType!T); 2426 if(typeMatch && *node == index) 2427 { 2428 return idx; 2429 } 2430 } 2431 return -1; 2432 } 2433 2434 // Check if index is integral and in range. 2435 void checkSequenceIndex(T)(T index) const scope @safe 2436 { 2437 assert(nodeID == NodeID.sequence, 2438 "checkSequenceIndex() called on a " ~ nodeTypeString ~ " node"); 2439 2440 static if(!isIntegral!T) 2441 { 2442 throw new NodeException("Indexing a sequence with a non-integral type.", startMark_); 2443 } 2444 else 2445 { 2446 enforce(index >= 0 && index < getValue!(Node[]).length, 2447 new NodeException("Sequence index out of range: " ~ to!string(index), 2448 startMark_)); 2449 } 2450 } 2451 // Safe wrapper for getting a value out of the variant. 2452 inout(T) getValue(T)() @safe return scope inout 2453 { 2454 alias RType = typeof(return); 2455 return value_.tryMatch!((RType r) => r); 2456 } 2457 // Safe wrapper for coercing a value out of the variant. 2458 inout(T) coerceValue(T)() @trusted scope return inout 2459 { 2460 alias RType = typeof(return); 2461 static if (is(typeof({ RType rt = T.init; T t = RType.init; }))) 2462 alias TType = T; 2463 else // `inout` matters (indirection) 2464 alias TType = RType; 2465 2466 // `inout(Node[]).to!string` apparently is not safe: 2467 // struct SumTypeBug { 2468 // import std.conv; 2469 // Node[] data; 2470 // 2471 // string bug () inout @safe 2472 // { 2473 // return this.data.to!string; 2474 // } 2475 // } 2476 // Doesn't compile with DMD v2.100.0 2477 return this.value_.tryMatch!( 2478 (inout bool v) @safe => v.to!TType, 2479 (inout long v) @safe => v.to!TType, 2480 (inout Node[] v) @trusted => v.to!TType, 2481 (inout ubyte[] v) @safe => v.to!TType, 2482 (inout string v) @safe => v.to!TType, 2483 (inout Node.Pair[] v) @trusted => v.to!TType, 2484 (inout SysTime v) @trusted => v.to!TType, 2485 (inout real v) @safe => v.to!TType, 2486 (inout YAMLNull v) @safe => null.to!TType, 2487 ); 2488 } 2489 // Safe wrapper for setting a value for the variant. 2490 void setValue(T)(T value) @trusted 2491 { 2492 static if (allowed!T) 2493 { 2494 value_ = value; 2495 } 2496 else 2497 { 2498 auto tmpNode = cast(Node)value; 2499 tag_ = tmpNode.tag; 2500 scalarStyle = tmpNode.scalarStyle; 2501 collectionStyle = tmpNode.collectionStyle; 2502 value_ = tmpNode.value_; 2503 } 2504 } 2505 2506 /// 2507 public void toString (DGT) (scope DGT sink) 2508 const scope @safe 2509 { 2510 this.value_.match!( 2511 (const bool v) => formattedWrite(sink, v ? "true" : "false"), 2512 (const long v) => formattedWrite(sink, "%s", v), 2513 (const Node[] v) => formattedWrite(sink, "[%(%s, %)]", v), 2514 (const ubyte[] v) => formattedWrite(sink, "%s", v), 2515 (const string v) => formattedWrite(sink, `"%s"`, v), 2516 (const Node.Pair[] v) => formattedWrite(sink, "{%(%s, %)}", v), 2517 (const SysTime v) => formattedWrite(sink, "%s", v), 2518 (const YAMLNull v) => formattedWrite(sink, "%s", v), 2519 (const YAMLMerge v) => formattedWrite(sink, "%s", v), 2520 (const real v) => formattedWrite(sink, "%s", v), 2521 (const YAMLInvalid v) => formattedWrite(sink, "%s", v), 2522 ); 2523 } 2524 } 2525 2526 package: 2527 // Merge pairs into an array of pairs based on merge rules in the YAML spec. 2528 // 2529 // Any new pair will only be added if there is not already a pair 2530 // with the same key. 2531 // 2532 // Params: pairs = Appender managing the array of pairs to merge into. 2533 // toMerge = Pairs to merge. 2534 void merge(ref Appender!(Node.Pair[]) pairs, Node.Pair[] toMerge) @safe 2535 { 2536 bool eq(ref Node.Pair a, ref Node.Pair b) @safe 2537 { 2538 return a.key == b.key; 2539 } 2540 2541 foreach(ref pair; toMerge) if(!canFind!eq(pairs.data, pair)) 2542 { 2543 pairs.put(pair); 2544 } 2545 } 2546 2547 enum hasNodeConstructor(T) = hasSimpleNodeConstructor!T || hasExpandedNodeConstructor!T; 2548 template hasSimpleNodeConstructor(T) 2549 { 2550 static if (is(T == struct)) 2551 { 2552 enum hasSimpleNodeConstructor = is(typeof(T(Node.init))); 2553 } 2554 else static if (is(T == class)) 2555 { 2556 enum hasSimpleNodeConstructor = is(typeof(new T(Node.init))); 2557 } 2558 else enum hasSimpleNodeConstructor = false; 2559 } 2560 template hasExpandedNodeConstructor(T) 2561 { 2562 static if (is(T == struct)) 2563 { 2564 enum hasExpandedNodeConstructor = is(typeof(T(Node.init, ""))); 2565 } 2566 else static if (is(T == class)) 2567 { 2568 enum hasExpandedNodeConstructor = is(typeof(new T(Node.init, ""))); 2569 } 2570 else enum hasExpandedNodeConstructor = false; 2571 } 2572 enum castableToNode(T) = (is(T == struct) || is(T == class)) && is(typeof(T.opCast!Node()) : Node); 2573 2574 @safe unittest 2575 { 2576 import dyaml : Loader, Node; 2577 2578 static struct Foo 2579 { 2580 string[] bars; 2581 2582 this(const Node node) 2583 { 2584 foreach(value; node["bars"].sequence) 2585 { 2586 bars ~= value.as!string.idup; 2587 } 2588 } 2589 } 2590 2591 Loader.fromString(`{ bars: ["a", "b"] }`) 2592 .load 2593 .as!(Foo); 2594 } 2595 @safe unittest 2596 { 2597 import dyaml : Loader, Node; 2598 import std : split, to; 2599 2600 static class MyClass 2601 { 2602 int x, y, z; 2603 2604 this(Node node) 2605 { 2606 auto parts = node.as!string().split(":"); 2607 x = parts[0].to!int; 2608 y = parts[1].to!int; 2609 z = parts[2].to!int; 2610 } 2611 } 2612 2613 auto loader = Loader.fromString(`"1:2:3"`); 2614 Node node = loader.load(); 2615 auto mc = node.get!MyClass; 2616 } 2617 @safe unittest 2618 { 2619 import dyaml : Loader, Node; 2620 import std : split, to; 2621 2622 static class MyClass 2623 { 2624 int x, y, z; 2625 2626 this(Node node) 2627 { 2628 auto parts = node.as!string().split(":"); 2629 x = parts[0].to!int; 2630 y = parts[1].to!int; 2631 z = parts[2].to!int; 2632 } 2633 } 2634 2635 auto loader = Loader.fromString(`"1:2:3"`); 2636 const node = loader.load(); 2637 auto mc = node.get!MyClass; 2638 }