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