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