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