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