2011-08-16 12:53:13 +00:00
|
|
|
|
|
|
|
// Copyright Ferdinand Majerech 2011.
|
|
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
|
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
|
|
|
// http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Node of a YAML document. Used to read YAML data once it's loaded,
|
|
|
|
/// and to prepare data to emit.
|
2011-08-16 12:53:13 +00:00
|
|
|
module dyaml.node;
|
|
|
|
|
|
|
|
|
|
|
|
import std.algorithm;
|
2012-12-29 23:25:23 +00:00
|
|
|
import std.array;
|
2011-08-16 12:53:13 +00:00
|
|
|
import std.conv;
|
|
|
|
import std.datetime;
|
|
|
|
import std.exception;
|
|
|
|
import std.math;
|
2011-10-30 17:12:02 +00:00
|
|
|
import std.range;
|
2011-10-11 13:58:23 +00:00
|
|
|
import std.string;
|
2011-08-16 12:53:13 +00:00
|
|
|
import std.traits;
|
|
|
|
import std.typecons;
|
2011-10-30 17:12:02 +00:00
|
|
|
import std.variant;
|
2011-08-16 12:53:13 +00:00
|
|
|
|
|
|
|
import dyaml.event;
|
|
|
|
import dyaml.exception;
|
2011-10-27 21:13:14 +00:00
|
|
|
import dyaml.style;
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Exception thrown at node related errors.
|
2011-08-16 12:53:13 +00:00
|
|
|
class NodeException : YAMLException
|
|
|
|
{
|
|
|
|
package:
|
2014-07-18 23:58:24 +00:00
|
|
|
// Construct a NodeException.
|
|
|
|
//
|
|
|
|
// Params: msg = Error message.
|
|
|
|
// start = Start position of the node.
|
2011-10-13 09:30:14 +00:00
|
|
|
this(string msg, Mark start, string file = __FILE__, int line = __LINE__)
|
2012-12-27 20:50:09 +00:00
|
|
|
@safe
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2011-10-18 14:12:22 +00:00
|
|
|
super(msg ~ "\nNode at: " ~ start.toString(), file, line);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Node kinds.
|
2011-08-16 12:53:13 +00:00
|
|
|
package enum NodeID : ubyte
|
|
|
|
{
|
2018-08-27 00:49:14 +00:00
|
|
|
scalar,
|
|
|
|
sequence,
|
|
|
|
mapping
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Null YAML type. Used in nodes with _null values.
|
2011-10-30 09:52:40 +00:00
|
|
|
struct YAMLNull
|
|
|
|
{
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Used for string conversion.
|
2012-09-08 23:42:13 +00:00
|
|
|
string toString() const pure @safe nothrow {return "null";}
|
2011-10-30 09:52:40 +00:00
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Merge YAML type, used to support "tag:yaml.org,2002:merge".
|
2011-08-16 12:53:13 +00:00
|
|
|
package struct YAMLMerge{}
|
|
|
|
|
2015-02-21 13:31:55 +00:00
|
|
|
// Key-value pair of YAML nodes, used in mappings.
|
2014-07-19 01:07:45 +00:00
|
|
|
private struct Pair
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
/// Key node.
|
|
|
|
Node key;
|
|
|
|
/// Value node.
|
|
|
|
Node value;
|
|
|
|
|
|
|
|
/// Construct a Pair from two values. Will be converted to Nodes if needed.
|
2018-03-23 21:35:16 +00:00
|
|
|
this(K, V)(K key, V value)
|
2014-07-19 01:07:45 +00:00
|
|
|
{
|
|
|
|
static if(is(Unqual!K == Node)){this.key = key;}
|
|
|
|
else {this.key = Node(key);}
|
|
|
|
static if(is(Unqual!V == Node)){this.value = value;}
|
|
|
|
else {this.value = Node(value);}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Equality test with another Pair.
|
|
|
|
bool opEquals(const ref Pair rhs) const @safe
|
|
|
|
{
|
2019-01-20 02:42:59 +00:00
|
|
|
return key == rhs.key && value == rhs.value;
|
2014-07-19 01:07:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Comparison with another Pair.
|
2019-01-20 02:42:59 +00:00
|
|
|
int opCmp(ref const(Pair) rhs) const @safe
|
2014-07-19 01:07:45 +00:00
|
|
|
{
|
2019-01-20 02:42:59 +00:00
|
|
|
const keyCmp = key.opCmp(rhs.key);
|
2014-07-19 01:07:45 +00:00
|
|
|
return keyCmp != 0 ? keyCmp
|
2019-01-20 02:42:59 +00:00
|
|
|
: value.opCmp(rhs.value);
|
2014-07-19 01:07:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 07:37:50 +00:00
|
|
|
enum NodeType
|
|
|
|
{
|
|
|
|
null_,
|
|
|
|
merge,
|
|
|
|
boolean,
|
|
|
|
integer,
|
|
|
|
decimal,
|
|
|
|
binary,
|
|
|
|
timestamp,
|
|
|
|
string,
|
|
|
|
mapping,
|
|
|
|
sequence
|
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** YAML node.
|
|
|
|
*
|
|
|
|
* This is a pseudo-dynamic type that can store any YAML value, including a
|
|
|
|
* sequence or mapping of nodes. You can get data from a Node directly or
|
|
|
|
* iterate over it if it's a collection.
|
|
|
|
*/
|
2011-08-16 12:53:13 +00:00
|
|
|
struct Node
|
|
|
|
{
|
|
|
|
public:
|
2014-07-19 01:07:45 +00:00
|
|
|
alias Pair = .Pair;
|
2013-12-17 14:17:15 +00:00
|
|
|
|
2011-08-16 12:53:13 +00:00
|
|
|
package:
|
2014-07-18 23:58:24 +00:00
|
|
|
// YAML value type.
|
2018-06-13 08:17:20 +00:00
|
|
|
alias Value = Algebraic!(YAMLNull, YAMLMerge, bool, long, real, ubyte[], SysTime, string,
|
2019-01-15 07:37:50 +00:00
|
|
|
Node.Pair[], Node[]);
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2019-01-15 07:37:50 +00:00
|
|
|
// Can Value hold this type naturally?
|
2018-06-10 05:51:51 +00:00
|
|
|
enum allowed(T) = isIntegral!T ||
|
|
|
|
isFloatingPoint!T ||
|
|
|
|
isSomeString!T ||
|
|
|
|
is(Unqual!T == bool) ||
|
|
|
|
Value.allowed!T;
|
2012-01-23 14:57:26 +00:00
|
|
|
|
2015-02-21 13:31:55 +00:00
|
|
|
// Stored value.
|
2011-08-16 12:53:13 +00:00
|
|
|
Value value_;
|
2015-02-21 13:31:55 +00:00
|
|
|
// Start position of the node.
|
2011-08-16 12:53:13 +00:00
|
|
|
Mark startMark_;
|
2011-10-20 13:48:21 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Tag of the node.
|
2018-03-01 03:20:43 +00:00
|
|
|
string tag_;
|
2014-07-18 23:58:24 +00:00
|
|
|
// Node scalar style. Used to remember style this node was loaded with.
|
2018-08-27 00:49:14 +00:00
|
|
|
ScalarStyle scalarStyle = ScalarStyle.invalid;
|
2014-07-18 23:58:24 +00:00
|
|
|
// Node collection style. Used to remember style this node was loaded with.
|
2018-08-27 00:49:14 +00:00
|
|
|
CollectionStyle collectionStyle = CollectionStyle.invalid;
|
2011-08-16 12:53:13 +00:00
|
|
|
|
|
|
|
public:
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Construct a Node from a value.
|
|
|
|
*
|
|
|
|
* Any type except for Node can be stored in a Node, but default YAML
|
|
|
|
* types (integers, floats, strings, timestamps, etc.) will be stored
|
|
|
|
* more efficiently. To create a node representing a null value,
|
|
|
|
* construct it from YAMLNull.
|
|
|
|
*
|
2018-06-10 08:28:58 +00:00
|
|
|
* If value is a node, its value will be copied directly. The tag and
|
|
|
|
* other information attached to the original node will be discarded.
|
|
|
|
*
|
|
|
|
* If value is an array of nodes or pairs, it is stored directly.
|
|
|
|
* Otherwise, every value in the array is converted to a node, and
|
|
|
|
* those nodes are stored.
|
2014-08-01 00:51:35 +00:00
|
|
|
*
|
|
|
|
* Note that to emit any non-default types you store
|
|
|
|
* in a node, you need a Representer to represent them in YAML -
|
|
|
|
* otherwise emitting will fail.
|
|
|
|
*
|
|
|
|
* Params: value = Value to store in the node.
|
|
|
|
* tag = Overrides tag of the node when emitted, regardless
|
|
|
|
* of tag determined by Representer. Representer uses
|
|
|
|
* this to determine YAML data type when a D data type
|
|
|
|
* maps to multiple different YAML data types. Tag must
|
|
|
|
* be in full form, e.g. "tag:yaml.org,2002:int", not
|
|
|
|
* a shortcut, like "!!int".
|
|
|
|
*/
|
2019-01-15 07:37:50 +00:00
|
|
|
this(T)(T value, const string tag = null) @safe
|
|
|
|
if (allowed!T || isArray!T || isAssociativeArray!T || is(Unqual!T == Node) || castableToNode!T)
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2018-03-01 03:20:43 +00:00
|
|
|
tag_ = tag;
|
2011-08-21 01:56:21 +00:00
|
|
|
|
2018-06-10 08:28:58 +00:00
|
|
|
//Unlike with assignment, we're just copying the value.
|
|
|
|
static if (is(Unqual!T == Node))
|
|
|
|
{
|
|
|
|
setValue(value.value_);
|
|
|
|
}
|
|
|
|
else static if(isSomeString!T)
|
|
|
|
{
|
|
|
|
setValue(value.to!string);
|
|
|
|
}
|
|
|
|
else static if(is(Unqual!T == bool))
|
|
|
|
{
|
|
|
|
setValue(cast(bool)value);
|
|
|
|
}
|
|
|
|
else static if(isIntegral!T)
|
|
|
|
{
|
|
|
|
setValue(cast(long)value);
|
|
|
|
}
|
|
|
|
else static if(isFloatingPoint!T)
|
|
|
|
{
|
|
|
|
setValue(cast(real)value);
|
|
|
|
}
|
|
|
|
else static if (isArray!T)
|
|
|
|
{
|
|
|
|
alias ElementT = Unqual!(ElementType!T);
|
|
|
|
// Construction from raw node or pair array.
|
|
|
|
static if(is(ElementT == Node) || is(ElementT == Node.Pair))
|
|
|
|
{
|
|
|
|
setValue(value);
|
|
|
|
}
|
|
|
|
// Need to handle byte buffers separately.
|
|
|
|
else static if(is(ElementT == byte) || is(ElementT == ubyte))
|
|
|
|
{
|
|
|
|
setValue(cast(ubyte[]) value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Node[] nodes;
|
|
|
|
foreach(ref v; value)
|
|
|
|
{
|
|
|
|
nodes ~= Node(v);
|
|
|
|
}
|
|
|
|
setValue(nodes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else static if (isAssociativeArray!T)
|
|
|
|
{
|
|
|
|
Node.Pair[] pairs;
|
|
|
|
foreach(k, ref v; value)
|
|
|
|
{
|
|
|
|
pairs ~= Pair(k, v);
|
|
|
|
}
|
|
|
|
setValue(pairs);
|
|
|
|
}
|
2014-07-18 23:58:24 +00:00
|
|
|
// User defined type.
|
2018-06-10 08:28:58 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
setValue(value);
|
|
|
|
}
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
/// Construct a scalar node
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
// Integer
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
auto node = Node(5);
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
// String
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
auto node = Node("Hello world!");
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
// Floating point
|
2015-06-03 18:30:19 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
auto node = Node(5.0f);
|
|
|
|
}
|
|
|
|
// Boolean
|
|
|
|
{
|
|
|
|
auto node = Node(true);
|
|
|
|
}
|
|
|
|
// Time
|
|
|
|
{
|
|
|
|
auto node = Node(SysTime(DateTime(2005, 06, 15, 20, 00, 00), UTC()));
|
|
|
|
}
|
|
|
|
// Integer, dumped as a string
|
|
|
|
{
|
|
|
|
auto node = Node(5, "tag:yaml.org,2002:str");
|
2015-06-03 18:30:19 +00:00
|
|
|
}
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
/// Construct a sequence node
|
|
|
|
@safe unittest
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
// Will be emitted as a sequence (default for arrays)
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
auto seq = Node([1, 2, 3, 4, 5]);
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
// Will be emitted as a set (overridden tag)
|
2011-10-11 13:58:23 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
auto set = Node([1, 2, 3, 4, 5], "tag:yaml.org,2002:set");
|
2011-10-11 13:58:23 +00:00
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
// Can also store arrays of arrays
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
auto node = Node([[1,2], [3,4]]);
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
/// Construct a mapping node
|
2018-04-07 20:36:38 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
2018-06-10 08:28:58 +00:00
|
|
|
// Will be emitted as an unordered mapping (default for mappings)
|
|
|
|
auto map = Node([1 : "a", 2 : "b"]);
|
|
|
|
// Will be emitted as an ordered map (overridden tag)
|
|
|
|
auto omap = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:omap");
|
|
|
|
// Will be emitted as pairs (overridden tag)
|
|
|
|
auto pairs = Node([1 : "a", 2 : "b"], "tag:yaml.org,2002:pairs");
|
|
|
|
}
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
{
|
|
|
|
auto node = Node(42);
|
2019-01-15 07:37:50 +00:00
|
|
|
assert(node.isScalar && !node.isSequence && !node.isMapping);
|
2018-06-10 08:28:58 +00:00
|
|
|
assert(node.as!int == 42 && node.as!float == 42.0f && node.as!string == "42");
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
auto node = Node("string");
|
|
|
|
assert(node.as!string == "string");
|
|
|
|
}
|
2018-04-07 20:36:38 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
with(Node([1, 2, 3]))
|
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
assert(!isScalar() && isSequence && !isMapping);
|
2011-08-21 21:53:19 +00:00
|
|
|
assert(length == 3);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex(2).as!int == 3);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2011-10-11 13:58:23 +00:00
|
|
|
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
int[string] aa;
|
|
|
|
aa["1"] = 1;
|
|
|
|
aa["2"] = 2;
|
|
|
|
with(Node(aa))
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
assert(!isScalar() && !isSequence && isMapping);
|
2011-08-21 21:53:19 +00:00
|
|
|
assert(length == 2);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex("2").as!int == 2);
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
2018-06-10 08:28:58 +00:00
|
|
|
}
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto node = Node(Node(4, "tag:yaml.org,2002:str"));
|
|
|
|
assert(node == 4);
|
|
|
|
assert(node.tag_ == "");
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Construct a node from arrays of _keys and _values.
|
|
|
|
*
|
|
|
|
* Constructs a mapping node with key-value pairs from
|
|
|
|
* _keys and _values, keeping their order. Useful when order
|
|
|
|
* is important (ordered maps, pairs).
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* keys and values must have equal length.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* If _keys and/or _values are nodes, they are stored directly/
|
|
|
|
* Otherwise they are converted to nodes and then stored.
|
|
|
|
*
|
|
|
|
* Params: keys = Keys of the mapping, from first to last pair.
|
|
|
|
* values = Values of the mapping, from first to last pair.
|
|
|
|
* tag = Overrides tag of the node when emitted, regardless
|
|
|
|
* of tag determined by Representer. Representer uses
|
|
|
|
* this to determine YAML data type when a D data type
|
|
|
|
* maps to multiple different YAML data types.
|
|
|
|
* This is used to differentiate between YAML unordered
|
|
|
|
* mappings ("!!map"), ordered mappings ("!!omap"), and
|
|
|
|
* pairs ("!!pairs") which are all internally
|
|
|
|
* represented as an array of node pairs. Tag must be
|
|
|
|
* in full form, e.g. "tag:yaml.org,2002:omap", not a
|
|
|
|
* shortcut, like "!!omap".
|
|
|
|
*
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
this(K, V)(K[] keys, V[] values, const string tag = null)
|
2011-10-11 13:58:23 +00:00
|
|
|
if(!(isSomeString!(K[]) || isSomeString!(V[])))
|
2011-08-21 01:56:21 +00:00
|
|
|
in
|
|
|
|
{
|
2014-05-19 17:53:18 +00:00
|
|
|
assert(keys.length == values.length,
|
2016-11-04 00:50:32 +00:00
|
|
|
"Lengths of keys and values arrays to construct " ~
|
2011-08-21 01:56:21 +00:00
|
|
|
"a YAML node from don't match");
|
|
|
|
}
|
2018-07-12 08:06:46 +00:00
|
|
|
do
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2018-03-01 03:20:43 +00:00
|
|
|
tag_ = tag;
|
2011-08-21 01:56:21 +00:00
|
|
|
|
|
|
|
Node.Pair[] pairs;
|
2011-08-21 21:53:19 +00:00
|
|
|
foreach(i; 0 .. keys.length){pairs ~= Pair(keys[i], values[i]);}
|
2018-04-10 06:36:09 +00:00
|
|
|
setValue(pairs);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-04-07 20:36:38 +00:00
|
|
|
///
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
// Will be emitted as an unordered mapping (default for mappings)
|
|
|
|
auto map = Node([1, 2], ["a", "b"]);
|
|
|
|
// Will be emitted as an ordered map (overridden tag)
|
|
|
|
auto omap = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:omap");
|
|
|
|
// Will be emitted as pairs (overriden tag)
|
|
|
|
auto pairs = Node([1, 2], ["a", "b"], "tag:yaml.org,2002:pairs");
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
with(Node(["1", "2"], [1, 2]))
|
2011-08-21 01:56:21 +00:00
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
assert(!isScalar() && !isSequence && isMapping);
|
2011-08-21 21:53:19 +00:00
|
|
|
assert(length == 2);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex("2").as!int == 2);
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
2011-10-11 13:58:23 +00:00
|
|
|
|
2011-08-21 01:56:21 +00:00
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Is this node valid (initialized)?
|
2014-05-19 17:53:18 +00:00
|
|
|
@property bool isValid() const @safe pure nothrow
|
2013-10-16 21:16:41 +00:00
|
|
|
{
|
|
|
|
return value_.hasValue;
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Is this node a scalar value?
|
2014-05-19 17:53:18 +00:00
|
|
|
@property bool isScalar() const @safe nothrow
|
2013-10-16 21:16:41 +00:00
|
|
|
{
|
|
|
|
return !(isMapping || isSequence);
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Is this node a sequence?
|
2014-05-19 17:53:18 +00:00
|
|
|
@property bool isSequence() const @safe nothrow
|
2013-10-16 21:16:41 +00:00
|
|
|
{
|
|
|
|
return isType!(Node[]);
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Is this node a mapping?
|
2014-05-19 17:53:18 +00:00
|
|
|
@property bool isMapping() const @safe nothrow
|
2013-10-16 21:16:41 +00:00
|
|
|
{
|
2014-05-19 17:53:18 +00:00
|
|
|
return isType!(Pair[]);
|
2013-10-16 21:16:41 +00:00
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Is this node null?
|
2014-05-19 17:53:18 +00:00
|
|
|
@property bool isNull() const @safe nothrow
|
2013-10-16 21:16:41 +00:00
|
|
|
{
|
|
|
|
return isType!YAMLNull;
|
|
|
|
}
|
2012-04-07 01:59:54 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Return tag of the node.
|
2018-04-10 06:36:09 +00:00
|
|
|
@property string tag() const @safe nothrow
|
|
|
|
{
|
|
|
|
return tag_;
|
|
|
|
}
|
2011-10-20 13:48:21 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Equality test.
|
|
|
|
*
|
|
|
|
* If T is Node, recursively compares all subnodes.
|
|
|
|
* This might be quite expensive if testing entire documents.
|
|
|
|
*
|
|
|
|
* If T is not Node, gets a value of type T from the node and tests
|
|
|
|
* equality with that.
|
|
|
|
*
|
|
|
|
* To test equality with a null YAML value, use YAMLNull.
|
|
|
|
*
|
|
|
|
* Params: rhs = Variable to test equality with.
|
|
|
|
*
|
|
|
|
* Returns: true if equal, false otherwise.
|
|
|
|
*/
|
2019-01-20 02:42:59 +00:00
|
|
|
bool opEquals(const Node rhs) const @safe
|
|
|
|
{
|
|
|
|
return opCmp(rhs) == 0;
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
bool opEquals(T)(const auto ref T rhs) const
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2019-01-20 02:42:59 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
auto stored = get!(T, No.stringConversion);
|
|
|
|
// NaNs aren't normally equal to each other, but we'll pretend they are.
|
|
|
|
static if(isFloatingPoint!T)
|
|
|
|
{
|
|
|
|
return rhs == stored || (isNaN(rhs) && isNaN(stored));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return rhs == stored;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(NodeException e)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2014-08-01 00:51:35 +00:00
|
|
|
///
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2012-03-01 10:44:05 +00:00
|
|
|
{
|
|
|
|
auto node = Node(42);
|
2014-05-19 17:53:18 +00:00
|
|
|
|
2012-03-01 10:44:05 +00:00
|
|
|
assert(node == 42);
|
|
|
|
assert(node != "42");
|
|
|
|
assert(node != "43");
|
|
|
|
|
|
|
|
auto node2 = Node(YAMLNull());
|
|
|
|
assert(node2 == YAMLNull());
|
2018-04-10 06:36:09 +00:00
|
|
|
|
|
|
|
const node3 = Node(42);
|
|
|
|
assert(node3 == 42);
|
2012-03-01 10:44:05 +00:00
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Shortcut for get().
|
2018-06-13 08:17:20 +00:00
|
|
|
alias as = get;
|
2011-10-22 15:06:32 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Get the value of the node as specified type.
|
|
|
|
*
|
|
|
|
* If the specifed type does not match type in the node,
|
|
|
|
* conversion is attempted. The stringConversion template
|
|
|
|
* parameter can be used to disable conversion from non-string
|
|
|
|
* types to strings.
|
|
|
|
*
|
|
|
|
* Numeric values are range checked, throwing if out of range of
|
|
|
|
* requested type.
|
|
|
|
*
|
|
|
|
* Timestamps are stored as std.datetime.SysTime.
|
|
|
|
* Binary values are decoded and stored as ubyte[].
|
|
|
|
*
|
|
|
|
* To get a null value, use get!YAMLNull . This is to
|
|
|
|
* prevent getting null values for types such as strings or classes.
|
|
|
|
*
|
|
|
|
* $(BR)$(B Mapping default values:)
|
|
|
|
*
|
|
|
|
* $(PBR
|
|
|
|
* The '=' key can be used to denote the default value of a mapping.
|
|
|
|
* This can be used when a node is scalar in early versions of a program,
|
|
|
|
* but is replaced by a mapping later. Even if the node is a mapping, the
|
|
|
|
* get method can be used as if it was a scalar if it has a default value.
|
|
|
|
* This way, new YAML files where the node is a mapping can still be read
|
|
|
|
* by old versions of the program, which expect the node to be a scalar.
|
|
|
|
* )
|
|
|
|
*
|
|
|
|
* Returns: Value of the node as specified type.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if unable to convert to specified type, or if
|
|
|
|
* the value is out of range of requested type.
|
|
|
|
*/
|
2018-04-10 06:53:49 +00:00
|
|
|
inout(T) get(T, Flag!"stringConversion" stringConversion = Yes.stringConversion)() inout
|
2019-01-15 07:37:50 +00:00
|
|
|
if (allowed!(Unqual!T) || hasNodeConstructor!(Unqual!T))
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
if(isType!(Unqual!T)){return getValue!T;}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2019-01-15 07:37:50 +00:00
|
|
|
static if(!allowed!(Unqual!T))
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
static if (hasSimpleNodeConstructor!T)
|
|
|
|
{
|
|
|
|
alias params = AliasSeq!(this);
|
|
|
|
}
|
|
|
|
else static if (hasExpandedNodeConstructor!T)
|
|
|
|
{
|
|
|
|
alias params = AliasSeq!(this, tag_);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
static assert(0, "Unknown Node constructor?");
|
|
|
|
}
|
2011-10-11 13:58:23 +00:00
|
|
|
|
2019-01-15 07:37:50 +00:00
|
|
|
static if (is(T == class))
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
return new inout T(params);
|
|
|
|
}
|
|
|
|
else static if (is(T == struct))
|
|
|
|
{
|
|
|
|
return T(params);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2012-03-01 10:44:05 +00:00
|
|
|
else
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
static assert(0, "Unhandled user type");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// If we're getting from a mapping and we're not getting Node.Pair[],
|
|
|
|
// we're getting the default value.
|
|
|
|
if(isMapping){return this["="].get!( T, stringConversion);}
|
|
|
|
|
|
|
|
static if(isSomeString!T)
|
|
|
|
{
|
|
|
|
static if(!stringConversion)
|
2012-03-01 10:44:05 +00:00
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
if(isString){return to!T(getValue!string);}
|
|
|
|
throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
|
|
|
|
". Expected: " ~ typeid(T).toString(), startMark_);
|
2012-03-01 10:44:05 +00:00
|
|
|
}
|
2019-01-15 07:37:50 +00:00
|
|
|
else
|
2012-03-01 10:44:05 +00:00
|
|
|
{
|
2019-01-15 07:37:50 +00:00
|
|
|
// Try to convert to string.
|
|
|
|
try
|
|
|
|
{
|
|
|
|
return coerceValue!T();
|
|
|
|
}
|
|
|
|
catch(VariantException e)
|
|
|
|
{
|
|
|
|
throw new NodeException("Unable to convert node value to string", startMark_);
|
|
|
|
}
|
2012-03-01 10:44:05 +00:00
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2019-01-15 07:37:50 +00:00
|
|
|
else static if(isFloatingPoint!T)
|
|
|
|
{
|
|
|
|
/// Can convert int to float.
|
|
|
|
if(isInt()) {return to!T(getValue!long);}
|
|
|
|
else if(isFloat()){return to!T(getValue!real);}
|
|
|
|
else throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
|
|
|
|
". Expected: " ~ typeid(T).toString, startMark_);
|
|
|
|
}
|
|
|
|
else static if(isIntegral!T)
|
|
|
|
{
|
|
|
|
enforce(isInt(), new NodeException("Node stores unexpected type: " ~ type.toString() ~
|
|
|
|
". Expected: " ~ typeid(T).toString, startMark_));
|
|
|
|
immutable temp = getValue!long;
|
|
|
|
enforce(temp >= T.min && temp <= T.max,
|
|
|
|
new NodeException("Integer value of type " ~ typeid(T).toString() ~
|
|
|
|
" out of range. Value: " ~ to!string(temp), startMark_));
|
|
|
|
return temp.to!T;
|
|
|
|
}
|
2018-04-09 22:31:21 +00:00
|
|
|
else throw new NodeException("Node stores unexpected type: " ~ type.toString() ~
|
2018-04-10 08:37:13 +00:00
|
|
|
". Expected: " ~ typeid(T).toString, startMark_);
|
|
|
|
}
|
2012-03-01 10:44:05 +00:00
|
|
|
}
|
2018-04-07 20:36:38 +00:00
|
|
|
/// Automatic type conversion
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto node = Node(42);
|
|
|
|
|
|
|
|
assert(node.get!int == 42);
|
|
|
|
assert(node.get!string == "42");
|
|
|
|
assert(node.get!double == 42.0);
|
|
|
|
}
|
2019-01-15 07:37:50 +00:00
|
|
|
/// Scalar node to struct and vice versa
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import dyaml.dumper : dumper;
|
|
|
|
import dyaml.loader : Loader;
|
|
|
|
static struct MyStruct
|
|
|
|
{
|
|
|
|
int x, y, z;
|
|
|
|
|
|
|
|
this(int x, int y, int z) @safe
|
|
|
|
{
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.z = z;
|
|
|
|
}
|
|
|
|
|
|
|
|
this(Node node) @safe
|
|
|
|
{
|
|
|
|
auto parts = node.as!string().split(":");
|
|
|
|
x = parts[0].to!int;
|
|
|
|
y = parts[1].to!int;
|
|
|
|
z = parts[2].to!int;
|
|
|
|
}
|
|
|
|
|
|
|
|
Node opCast(T: Node)() @safe
|
|
|
|
{
|
|
|
|
//Using custom scalar format, x:y:z.
|
|
|
|
auto scalar = format("%s:%s:%s", x, y, z);
|
|
|
|
//Representing as a scalar, with custom tag to specify this data type.
|
|
|
|
return Node(scalar, "!mystruct.tag");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto appender = new Appender!string;
|
|
|
|
|
|
|
|
// Dump struct to yaml document
|
|
|
|
dumper(appender).dump(Node(MyStruct(1,2,3)));
|
|
|
|
|
|
|
|
// Read yaml document back as a MyStruct
|
|
|
|
auto loader = Loader.fromString(appender.data);
|
|
|
|
Node node = loader.load();
|
|
|
|
assert(node.as!MyStruct == MyStruct(1,2,3));
|
|
|
|
}
|
|
|
|
/// Sequence node to struct and vice versa
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import dyaml.dumper : dumper;
|
|
|
|
import dyaml.loader : Loader;
|
|
|
|
static struct MyStruct
|
|
|
|
{
|
|
|
|
int x, y, z;
|
|
|
|
|
|
|
|
this(int x, int y, int z) @safe
|
|
|
|
{
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.z = z;
|
|
|
|
}
|
|
|
|
|
|
|
|
this(Node node) @safe
|
|
|
|
{
|
|
|
|
x = node[0].as!int;
|
|
|
|
y = node[1].as!int;
|
|
|
|
z = node[2].as!int;
|
|
|
|
}
|
|
|
|
|
|
|
|
Node opCast(T: Node)()
|
|
|
|
{
|
|
|
|
return Node([x, y, z], "!mystruct.tag");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto appender = new Appender!string;
|
|
|
|
|
|
|
|
// Dump struct to yaml document
|
|
|
|
dumper(appender).dump(Node(MyStruct(1,2,3)));
|
|
|
|
|
|
|
|
// Read yaml document back as a MyStruct
|
|
|
|
auto loader = Loader.fromString(appender.data);
|
|
|
|
Node node = loader.load();
|
|
|
|
assert(node.as!MyStruct == MyStruct(1,2,3));
|
|
|
|
}
|
|
|
|
/// Mapping node to struct and vice versa
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import dyaml.dumper : dumper;
|
|
|
|
import dyaml.loader : Loader;
|
|
|
|
static struct MyStruct
|
|
|
|
{
|
|
|
|
int x, y, z;
|
|
|
|
|
|
|
|
Node opCast(T: Node)()
|
|
|
|
{
|
|
|
|
auto pairs = [Node.Pair("x", x),
|
|
|
|
Node.Pair("y", y),
|
|
|
|
Node.Pair("z", z)];
|
|
|
|
return Node(pairs, "!mystruct.tag");
|
|
|
|
}
|
|
|
|
|
|
|
|
this(int x, int y, int z)
|
|
|
|
{
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.z = z;
|
|
|
|
}
|
|
|
|
|
|
|
|
this(Node node) @safe
|
|
|
|
{
|
|
|
|
x = node["x"].as!int;
|
|
|
|
y = node["y"].as!int;
|
|
|
|
z = node["z"].as!int;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto appender = new Appender!string;
|
|
|
|
|
|
|
|
// Dump struct to yaml document
|
|
|
|
dumper(appender).dump(Node(MyStruct(1,2,3)));
|
|
|
|
|
|
|
|
// Read yaml document back as a MyStruct
|
|
|
|
auto loader = Loader.fromString(appender.data);
|
|
|
|
Node node = loader.load();
|
|
|
|
assert(node.as!MyStruct == MyStruct(1,2,3));
|
|
|
|
}
|
|
|
|
/// Classes can be used too
|
|
|
|
@system unittest {
|
|
|
|
import dyaml.dumper : dumper;
|
|
|
|
import dyaml.loader : Loader;
|
|
|
|
|
|
|
|
static class MyClass
|
|
|
|
{
|
|
|
|
int x, y, z;
|
|
|
|
|
|
|
|
this(int x, int y, int z)
|
|
|
|
{
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.z = z;
|
|
|
|
}
|
|
|
|
|
|
|
|
this(Node node) @safe inout
|
|
|
|
{
|
|
|
|
auto parts = node.as!string().split(":");
|
|
|
|
x = parts[0].to!int;
|
|
|
|
y = parts[1].to!int;
|
|
|
|
z = parts[2].to!int;
|
|
|
|
}
|
|
|
|
|
|
|
|
///Useful for Node.as!string.
|
|
|
|
override string toString()
|
|
|
|
{
|
|
|
|
return format("MyClass(%s, %s, %s)", x, y, z);
|
|
|
|
}
|
|
|
|
|
|
|
|
Node opCast(T: Node)() @safe
|
|
|
|
{
|
|
|
|
//Using custom scalar format, x:y:z.
|
|
|
|
auto scalar = format("%s:%s:%s", x, y, z);
|
|
|
|
//Representing as a scalar, with custom tag to specify this data type.
|
|
|
|
return Node(scalar, "!myclass.tag");
|
|
|
|
}
|
|
|
|
override bool opEquals(Object o)
|
|
|
|
{
|
|
|
|
if (auto other = cast(MyClass)o)
|
|
|
|
{
|
|
|
|
return (other.x == x) && (other.y == y) && (other.z == z);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto appender = new Appender!string;
|
|
|
|
|
|
|
|
// Dump class to yaml document
|
|
|
|
dumper(appender).dump(Node(new MyClass(1,2,3)));
|
|
|
|
|
|
|
|
// Read yaml document back as a MyClass
|
|
|
|
auto loader = Loader.fromString(appender.data);
|
|
|
|
Node node = loader.load();
|
|
|
|
assert(node.as!MyClass == new MyClass(1,2,3));
|
|
|
|
}
|
2019-01-20 02:42:59 +00:00
|
|
|
// Make sure custom tags and styles are kept.
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
static struct MyStruct
|
|
|
|
{
|
|
|
|
Node opCast(T: Node)()
|
|
|
|
{
|
|
|
|
auto node = Node("hi", "!mystruct.tag");
|
|
|
|
node.setStyle(ScalarStyle.doubleQuoted);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto node = Node(MyStruct.init);
|
|
|
|
assert(node.tag == "!mystruct.tag");
|
|
|
|
assert(node.scalarStyle == ScalarStyle.doubleQuoted);
|
|
|
|
}
|
|
|
|
// ditto, but for collection style
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
static struct MyStruct
|
|
|
|
{
|
|
|
|
Node opCast(T: Node)()
|
|
|
|
{
|
|
|
|
auto node = Node(["hi"], "!mystruct.tag");
|
|
|
|
node.setStyle(CollectionStyle.flow);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto node = Node(MyStruct.init);
|
|
|
|
assert(node.tag == "!mystruct.tag");
|
|
|
|
assert(node.collectionStyle == CollectionStyle.flow);
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2012-03-01 10:44:05 +00:00
|
|
|
{
|
|
|
|
assertThrown!NodeException(Node("42").get!int);
|
2018-04-10 08:37:13 +00:00
|
|
|
assertThrown!NodeException(Node("42").get!double);
|
|
|
|
assertThrown!NodeException(Node(long.max).get!ushort);
|
2012-03-01 10:44:05 +00:00
|
|
|
Node(YAMLNull()).get!YAMLNull;
|
2011-10-30 17:12:02 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
@safe unittest
|
2011-10-30 17:12:02 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
const node = Node(42);
|
|
|
|
assert(node.get!int == 42);
|
|
|
|
assert(node.get!string == "42");
|
|
|
|
assert(node.get!double == 42.0);
|
2011-10-30 17:12:02 +00:00
|
|
|
|
2018-04-10 06:36:09 +00:00
|
|
|
immutable node2 = Node(42);
|
|
|
|
assert(node2.get!int == 42);
|
|
|
|
assert(node2.get!(const int) == 42);
|
|
|
|
assert(node2.get!(immutable int) == 42);
|
|
|
|
assert(node2.get!string == "42");
|
|
|
|
assert(node2.get!(const string) == "42");
|
|
|
|
assert(node2.get!(immutable string) == "42");
|
|
|
|
assert(node2.get!double == 42.0);
|
|
|
|
assert(node2.get!(const double) == 42.0);
|
|
|
|
assert(node2.get!(immutable double) == 42.0);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** If this is a collection, return its _length.
|
|
|
|
*
|
|
|
|
* Otherwise, throw NodeException.
|
|
|
|
*
|
|
|
|
* Returns: Number of elements in a sequence or key-value pairs in a mapping.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if this is not a sequence nor a mapping.
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
@property size_t length() const @safe
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
if(isSequence) { return getValue!(Node[]).length; }
|
|
|
|
else if(isMapping) { return getValue!(Pair[]).length; }
|
2018-04-09 22:31:21 +00:00
|
|
|
throw new NodeException("Trying to get length of a " ~ nodeTypeString ~ " node",
|
2012-03-01 14:23:53 +00:00
|
|
|
startMark_);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto node = Node([1,2,3]);
|
|
|
|
assert(node.length == 3);
|
|
|
|
const cNode = Node([1,2,3]);
|
|
|
|
assert(cNode.length == 3);
|
|
|
|
immutable iNode = Node([1,2,3]);
|
|
|
|
assert(iNode.length == 3);
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Get the element at specified index.
|
|
|
|
*
|
|
|
|
* If the node is a sequence, index must be integral.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* If the node is a mapping, return the value corresponding to the first
|
|
|
|
* key equal to index. containsKey() can be used to determine if a mapping
|
|
|
|
* has a specific key.
|
|
|
|
*
|
|
|
|
* To get element at a null index, use YAMLNull for index.
|
|
|
|
*
|
|
|
|
* Params: index = Index to use.
|
|
|
|
*
|
|
|
|
* Returns: Value corresponding to the index.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the index could not be found,
|
|
|
|
* non-integral index is used with a sequence or the node is
|
|
|
|
* not a collection.
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
ref inout(Node) opIndex(T)(T index) inout @safe
|
|
|
|
{
|
2011-08-16 12:53:13 +00:00
|
|
|
if(isSequence)
|
|
|
|
{
|
2011-08-21 21:53:19 +00:00
|
|
|
checkSequenceIndex(index);
|
2018-04-10 06:36:09 +00:00
|
|
|
static if(isIntegral!T)
|
2011-10-30 17:12:02 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
return getValue!(Node[])[index];
|
2011-10-30 17:12:02 +00:00
|
|
|
}
|
2011-08-21 21:53:19 +00:00
|
|
|
assert(false);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
else if(isMapping)
|
|
|
|
{
|
2011-08-21 21:53:19 +00:00
|
|
|
auto idx = findPair(index);
|
2011-10-30 17:12:02 +00:00
|
|
|
if(idx >= 0)
|
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
return getValue!(Pair[])[idx].value;
|
2011-10-30 17:12:02 +00:00
|
|
|
}
|
2011-08-21 21:53:19 +00:00
|
|
|
|
2011-10-21 18:40:37 +00:00
|
|
|
string msg = "Mapping index not found" ~ (isSomeString!T ? ": " ~ to!string(index) : "");
|
2018-04-09 22:31:21 +00:00
|
|
|
throw new NodeException(msg, startMark_);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-04-09 22:31:21 +00:00
|
|
|
throw new NodeException("Trying to index a " ~ nodeTypeString ~ " node", startMark_);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2014-07-18 23:58:24 +00:00
|
|
|
///
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2014-07-19 00:00:04 +00:00
|
|
|
Node narray = Node([11, 12, 13, 14]);
|
|
|
|
Node nmap = Node(["11", "12", "13", "14"], [11, 12, 13, 14]);
|
|
|
|
|
|
|
|
assert(narray[0].as!int == 11);
|
|
|
|
assert(null !is collectException(narray[42]));
|
|
|
|
assert(nmap["11"].as!int == 11);
|
|
|
|
assert(nmap["14"].as!int == 14);
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2014-07-19 00:00:04 +00:00
|
|
|
{
|
|
|
|
Node narray = Node([11, 12, 13, 14]);
|
|
|
|
Node nmap = Node(["11", "12", "13", "14"], [11, 12, 13, 14]);
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(narray[0].as!int == 11);
|
2011-08-16 12:53:13 +00:00
|
|
|
assert(null !is collectException(narray[42]));
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(nmap["11"].as!int == 11);
|
|
|
|
assert(nmap["14"].as!int == 14);
|
2011-08-16 12:53:13 +00:00
|
|
|
assert(null !is collectException(nmap["42"]));
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
narray.add(YAMLNull());
|
|
|
|
nmap.add(YAMLNull(), "Nothing");
|
|
|
|
assert(narray[4].as!YAMLNull == YAMLNull());
|
|
|
|
assert(nmap[YAMLNull()].as!string == "Nothing");
|
|
|
|
|
|
|
|
assertThrown!NodeException(nmap[11]);
|
|
|
|
assertThrown!NodeException(nmap[14]);
|
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Determine if a collection contains specified value.
|
|
|
|
*
|
|
|
|
* If the node is a sequence, check if it contains the specified value.
|
|
|
|
* If it's a mapping, check if it has a value that matches specified value.
|
|
|
|
*
|
|
|
|
* Params: rhs = Item to look for. Use YAMLNull to check for a null value.
|
|
|
|
*
|
|
|
|
* Returns: true if rhs was found, false otherwise.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a collection.
|
|
|
|
*/
|
2018-03-23 21:35:16 +00:00
|
|
|
bool contains(T)(T rhs) const
|
2012-03-01 10:44:05 +00:00
|
|
|
{
|
2012-03-01 12:17:32 +00:00
|
|
|
return contains_!(T, No.key, "contains")(rhs);
|
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto mNode = Node(["1", "2", "3"]);
|
|
|
|
assert(mNode.contains("2"));
|
|
|
|
const cNode = Node(["1", "2", "3"]);
|
|
|
|
assert(cNode.contains("2"));
|
|
|
|
immutable iNode = Node(["1", "2", "3"]);
|
|
|
|
assert(iNode.contains("2"));
|
|
|
|
}
|
2012-03-01 12:17:32 +00:00
|
|
|
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Determine if a mapping contains specified key.
|
|
|
|
*
|
|
|
|
* Params: rhs = Key to look for. Use YAMLNull to check for a null key.
|
|
|
|
*
|
|
|
|
* Returns: true if rhs was found, false otherwise.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a mapping.
|
|
|
|
*/
|
2018-03-23 21:35:16 +00:00
|
|
|
bool containsKey(T)(T rhs) const
|
2012-03-01 12:17:32 +00:00
|
|
|
{
|
|
|
|
return contains_!(T, Yes.key, "containsKey")(rhs);
|
2012-03-01 10:44:05 +00:00
|
|
|
}
|
2012-03-01 12:17:32 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Unittest for contains() and containsKey().
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2012-03-01 10:44:05 +00:00
|
|
|
{
|
|
|
|
auto seq = Node([1, 2, 3, 4, 5]);
|
|
|
|
assert(seq.contains(3));
|
|
|
|
assert(seq.contains(5));
|
|
|
|
assert(!seq.contains("5"));
|
|
|
|
assert(!seq.contains(6));
|
|
|
|
assert(!seq.contains(float.nan));
|
2012-03-01 12:17:32 +00:00
|
|
|
assertThrown!NodeException(seq.containsKey(5));
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
auto seq2 = Node(["1", "2"]);
|
|
|
|
assert(seq2.contains("1"));
|
|
|
|
assert(!seq2.contains(1));
|
|
|
|
|
|
|
|
auto map = Node(["1", "2", "3", "4"], [1, 2, 3, 4]);
|
|
|
|
assert(map.contains(1));
|
|
|
|
assert(!map.contains("1"));
|
|
|
|
assert(!map.contains(5));
|
|
|
|
assert(!map.contains(float.nan));
|
2012-03-01 12:17:32 +00:00
|
|
|
assert(map.containsKey("1"));
|
|
|
|
assert(map.containsKey("4"));
|
|
|
|
assert(!map.containsKey(1));
|
|
|
|
assert(!map.containsKey("5"));
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
assert(!seq.contains(YAMLNull()));
|
|
|
|
assert(!map.contains(YAMLNull()));
|
2012-03-01 12:17:32 +00:00
|
|
|
assert(!map.containsKey(YAMLNull()));
|
2012-03-01 10:44:05 +00:00
|
|
|
seq.add(YAMLNull());
|
|
|
|
map.add("Nothing", YAMLNull());
|
|
|
|
assert(seq.contains(YAMLNull()));
|
|
|
|
assert(map.contains(YAMLNull()));
|
2012-03-01 12:17:32 +00:00
|
|
|
assert(!map.containsKey(YAMLNull()));
|
|
|
|
map.add(YAMLNull(), "Nothing");
|
|
|
|
assert(map.containsKey(YAMLNull()));
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
auto map2 = Node([1, 2, 3, 4], [1, 2, 3, 4]);
|
|
|
|
assert(!map2.contains("1"));
|
|
|
|
assert(map2.contains(1));
|
2012-03-01 12:17:32 +00:00
|
|
|
assert(!map2.containsKey("1"));
|
|
|
|
assert(map2.containsKey(1));
|
2012-03-01 10:44:05 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// scalar
|
2012-03-01 10:44:05 +00:00
|
|
|
assertThrown!NodeException(Node(1).contains(4));
|
2012-03-01 12:17:32 +00:00
|
|
|
assertThrown!NodeException(Node(1).containsKey(4));
|
2012-03-01 10:44:05 +00:00
|
|
|
|
2012-03-01 12:17:32 +00:00
|
|
|
auto mapNan = Node([1.0, 2, double.nan], [1, double.nan, 5]);
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
assert(mapNan.contains(double.nan));
|
2012-03-01 12:17:32 +00:00
|
|
|
assert(mapNan.containsKey(double.nan));
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
|
2012-12-27 20:50:53 +00:00
|
|
|
/// Assignment (shallow copy) by value.
|
2018-06-10 04:15:45 +00:00
|
|
|
void opAssign()(auto ref Node rhs)
|
2012-12-27 20:50:53 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
assumeWontThrow(setValue(rhs.value_));
|
2012-12-27 20:50:53 +00:00
|
|
|
startMark_ = rhs.startMark_;
|
|
|
|
tag_ = rhs.tag_;
|
|
|
|
scalarStyle = rhs.scalarStyle;
|
|
|
|
collectionStyle = rhs.collectionStyle;
|
|
|
|
}
|
|
|
|
// Unittest for opAssign().
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2012-12-27 20:50:53 +00:00
|
|
|
{
|
|
|
|
auto seq = Node([1, 2, 3, 4, 5]);
|
|
|
|
auto assigned = seq;
|
2014-05-19 17:53:18 +00:00
|
|
|
assert(seq == assigned,
|
2012-12-27 20:50:53 +00:00
|
|
|
"Node.opAssign() doesn't produce an equivalent copy");
|
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Set element at specified index in a collection.
|
|
|
|
*
|
|
|
|
* This method can only be called on collection nodes.
|
|
|
|
*
|
|
|
|
* If the node is a sequence, index must be integral.
|
|
|
|
*
|
|
|
|
* If the node is a mapping, sets the _value corresponding to the first
|
|
|
|
* key matching index (including conversion, so e.g. "42" matches 42).
|
|
|
|
*
|
|
|
|
* If the node is a mapping and no key matches index, a new key-value
|
|
|
|
* pair is added to the mapping. In sequences the index must be in
|
|
|
|
* range. This ensures behavior siilar to D arrays and associative
|
|
|
|
* arrays.
|
|
|
|
*
|
|
|
|
* To set element at a null index, use YAMLNull for index.
|
|
|
|
*
|
2018-04-07 20:36:38 +00:00
|
|
|
* Params:
|
|
|
|
* value = Value to assign.
|
|
|
|
* index = Index of the value to set.
|
2014-08-01 00:51:35 +00:00
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a collection, index is out
|
|
|
|
* of range or if a non-integral index is used on a sequence node.
|
|
|
|
*/
|
2018-04-10 06:53:49 +00:00
|
|
|
void opIndexAssign(K, V)(V value, K index)
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
if(isSequence())
|
|
|
|
{
|
2014-07-18 23:58:24 +00:00
|
|
|
// This ensures K is integral.
|
2011-08-21 21:53:19 +00:00
|
|
|
checkSequenceIndex(index);
|
2017-07-26 21:47:53 +00:00
|
|
|
static if(isIntegral!K || is(Unqual!K == bool))
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
auto nodes = getValue!(Node[]);
|
2011-10-28 22:31:14 +00:00
|
|
|
static if(is(Unqual!V == Node)){nodes[index] = value;}
|
|
|
|
else {nodes[index] = Node(value);}
|
2018-04-10 06:36:09 +00:00
|
|
|
setValue(nodes);
|
2011-08-21 21:53:19 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
else if(isMapping())
|
|
|
|
{
|
2011-10-30 19:24:43 +00:00
|
|
|
const idx = findPair(index);
|
2011-08-21 21:53:19 +00:00
|
|
|
if(idx < 0){add(index, value);}
|
|
|
|
else
|
|
|
|
{
|
2011-10-22 15:06:32 +00:00
|
|
|
auto pairs = as!(Node.Pair[])();
|
2011-10-28 22:31:14 +00:00
|
|
|
static if(is(Unqual!V == Node)){pairs[idx].value = value;}
|
|
|
|
else {pairs[idx].value = Node(value);}
|
2018-04-10 06:36:09 +00:00
|
|
|
setValue(pairs);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-09 22:31:21 +00:00
|
|
|
throw new NodeException("Trying to index a " ~ nodeTypeString ~ " node", startMark_);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
with(Node([1, 2, 3, 4, 3]))
|
|
|
|
{
|
|
|
|
opIndexAssign(42, 3);
|
|
|
|
assert(length == 5);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex(3).as!int == 42);
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
opIndexAssign(YAMLNull(), 0);
|
|
|
|
assert(opIndex(0) == YAMLNull());
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
with(Node(["1", "2", "3"], [4, 5, 6]))
|
|
|
|
{
|
|
|
|
opIndexAssign(42, "3");
|
|
|
|
opIndexAssign(123, 456);
|
|
|
|
assert(length == 4);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex("3").as!int == 42);
|
|
|
|
assert(opIndex(456).as!int == 123);
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
opIndexAssign(43, 3);
|
|
|
|
//3 and "3" should be different
|
|
|
|
assert(length == 5);
|
|
|
|
assert(opIndex("3").as!int == 42);
|
|
|
|
assert(opIndex(3).as!int == 43);
|
|
|
|
|
|
|
|
opIndexAssign(YAMLNull(), "2");
|
|
|
|
assert(opIndex("2") == YAMLNull());
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-01 11:13:35 +00:00
|
|
|
/** Return a range object iterating over a sequence, getting each
|
|
|
|
* element as T.
|
2018-04-10 01:02:30 +00:00
|
|
|
*
|
2017-08-01 11:13:35 +00:00
|
|
|
* If T is Node, simply iterate over the nodes in the sequence.
|
|
|
|
* Otherwise, convert each node to T during iteration.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a sequence or an element
|
|
|
|
* could not be converted to specified type.
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
template sequence(T = Node)
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
struct Range(N)
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
N subnodes;
|
2017-08-01 11:13:35 +00:00
|
|
|
size_t position;
|
|
|
|
|
2018-04-10 06:36:09 +00:00
|
|
|
this(N nodes)
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
subnodes = nodes;
|
|
|
|
position = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Input range functionality. */
|
2018-06-20 09:51:23 +00:00
|
|
|
bool empty() const @property { return position >= subnodes.length; }
|
2017-08-01 15:31:34 +00:00
|
|
|
|
2018-04-10 01:02:30 +00:00
|
|
|
void popFront()
|
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to popFront an empty sequence");
|
2018-04-10 01:02:30 +00:00
|
|
|
position++;
|
2017-08-01 15:31:34 +00:00
|
|
|
}
|
|
|
|
|
2018-06-20 09:51:23 +00:00
|
|
|
T front() const @property
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to take the front of an empty sequence");
|
2017-08-01 11:13:35 +00:00
|
|
|
static if (is(Unqual!T == Node))
|
|
|
|
return subnodes[position];
|
|
|
|
else
|
|
|
|
return subnodes[position].as!T;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Forward range functionality. */
|
|
|
|
Range save() { return this; }
|
|
|
|
|
|
|
|
/* Bidirectional range functionality. */
|
2018-04-10 01:02:30 +00:00
|
|
|
void popBack()
|
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to popBack an empty sequence");
|
2018-04-10 01:02:30 +00:00
|
|
|
subnodes = subnodes[0 .. $ - 1];
|
2017-08-01 15:31:34 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 01:02:30 +00:00
|
|
|
T back()
|
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to take the back of an empty sequence");
|
2017-08-01 11:13:35 +00:00
|
|
|
static if (is(Unqual!T == Node))
|
|
|
|
return subnodes[$ - 1];
|
|
|
|
else
|
|
|
|
return subnodes[$ - 1].as!T;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Random-access range functionality. */
|
2017-08-01 11:37:28 +00:00
|
|
|
size_t length() const @property { return subnodes.length; }
|
2017-08-01 11:13:35 +00:00
|
|
|
T opIndex(size_t index)
|
|
|
|
{
|
|
|
|
static if (is(Unqual!T == Node))
|
|
|
|
return subnodes[index];
|
|
|
|
else
|
|
|
|
return subnodes[index].as!T;
|
|
|
|
}
|
2017-08-01 11:37:28 +00:00
|
|
|
|
|
|
|
static assert(isInputRange!Range);
|
|
|
|
static assert(isForwardRange!Range);
|
|
|
|
static assert(isBidirectionalRange!Range);
|
|
|
|
static assert(isRandomAccessRange!Range);
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
auto sequence()
|
|
|
|
{
|
|
|
|
enforce(isSequence,
|
|
|
|
new NodeException("Trying to 'sequence'-iterate over a " ~ nodeTypeString ~ " node",
|
|
|
|
startMark_));
|
|
|
|
return Range!(Node[])(get!(Node[]));
|
|
|
|
}
|
|
|
|
auto sequence() const
|
|
|
|
{
|
|
|
|
enforce(isSequence,
|
|
|
|
new NodeException("Trying to 'sequence'-iterate over a " ~ nodeTypeString ~ " node",
|
|
|
|
startMark_));
|
|
|
|
return Range!(const(Node)[])(get!(Node[]));
|
|
|
|
}
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
Node n1 = Node([1, 2, 3, 4]);
|
|
|
|
int[int] array;
|
|
|
|
Node n2 = Node(array);
|
2018-04-10 06:36:09 +00:00
|
|
|
const n3 = Node([1, 2, 3, 4]);
|
2017-08-01 11:13:35 +00:00
|
|
|
|
|
|
|
auto r = n1.sequence!int.map!(x => x * 10);
|
|
|
|
assert(r.equal([10, 20, 30, 40]));
|
|
|
|
|
|
|
|
assertThrown(n2.sequence);
|
2018-04-10 06:36:09 +00:00
|
|
|
|
|
|
|
auto r2 = n3.sequence!int.map!(x => x * 10);
|
|
|
|
assert(r2.equal([10, 20, 30, 40]));
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Return a range object iterating over mapping's pairs.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a mapping.
|
|
|
|
*
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
template mapping()
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
struct Range(T)
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
T pairs;
|
2017-08-01 11:13:35 +00:00
|
|
|
size_t position;
|
|
|
|
|
2018-04-10 06:36:09 +00:00
|
|
|
this(T pairs) @safe
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
this.pairs = pairs;
|
|
|
|
position = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Input range functionality. */
|
2018-03-23 21:35:16 +00:00
|
|
|
bool empty() @safe { return position >= pairs.length; }
|
2017-08-01 15:31:34 +00:00
|
|
|
|
2018-03-23 21:35:16 +00:00
|
|
|
void popFront() @safe
|
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to popFront an empty mapping");
|
2018-03-23 21:35:16 +00:00
|
|
|
position++;
|
2017-08-01 15:31:34 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 06:36:09 +00:00
|
|
|
auto front() @safe
|
2018-03-23 21:35:16 +00:00
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to take the front of an empty mapping");
|
2018-03-23 21:35:16 +00:00
|
|
|
return pairs[position];
|
2017-08-01 15:31:34 +00:00
|
|
|
}
|
2017-08-01 11:13:35 +00:00
|
|
|
|
|
|
|
/* Forward range functionality. */
|
2018-03-23 21:35:16 +00:00
|
|
|
Range save() @safe { return this; }
|
2017-08-01 11:13:35 +00:00
|
|
|
|
|
|
|
/* Bidirectional range functionality. */
|
2018-03-23 21:35:16 +00:00
|
|
|
void popBack() @safe
|
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to popBack an empty mapping");
|
2018-03-23 21:35:16 +00:00
|
|
|
pairs = pairs[0 .. $ - 1];
|
2017-08-01 15:31:34 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 06:36:09 +00:00
|
|
|
auto back() @safe
|
2018-03-23 21:35:16 +00:00
|
|
|
{
|
2017-08-01 15:31:34 +00:00
|
|
|
enforce(!empty, "Attempted to take the back of an empty mapping");
|
2018-03-23 21:35:16 +00:00
|
|
|
return pairs[$ - 1];
|
2017-08-01 15:31:34 +00:00
|
|
|
}
|
2017-08-01 11:13:35 +00:00
|
|
|
|
|
|
|
/* Random-access range functionality. */
|
2018-03-23 21:35:16 +00:00
|
|
|
size_t length() const @property @safe { return pairs.length; }
|
2018-04-10 06:36:09 +00:00
|
|
|
auto opIndex(size_t index) @safe { return pairs[index]; }
|
2017-08-01 11:37:28 +00:00
|
|
|
|
|
|
|
static assert(isInputRange!Range);
|
|
|
|
static assert(isForwardRange!Range);
|
|
|
|
static assert(isBidirectionalRange!Range);
|
|
|
|
static assert(isRandomAccessRange!Range);
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
|
|
|
|
auto mapping()
|
|
|
|
{
|
|
|
|
enforce(isMapping,
|
|
|
|
new NodeException("Trying to 'mapping'-iterate over a "
|
|
|
|
~ nodeTypeString ~ " node", startMark_));
|
|
|
|
return Range!(Node.Pair[])(get!(Node.Pair[]));
|
|
|
|
}
|
|
|
|
auto mapping() const
|
|
|
|
{
|
|
|
|
enforce(isMapping,
|
|
|
|
new NodeException("Trying to 'mapping'-iterate over a "
|
|
|
|
~ nodeTypeString ~ " node", startMark_));
|
|
|
|
return Range!(const(Node.Pair)[])(get!(Node.Pair[]));
|
|
|
|
}
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
int[int] array;
|
|
|
|
Node n = Node(array);
|
|
|
|
n[1] = "foo";
|
|
|
|
n[2] = "bar";
|
|
|
|
n[3] = "baz";
|
|
|
|
|
|
|
|
string[int] test;
|
|
|
|
foreach (pair; n.mapping)
|
|
|
|
test[pair.key.as!int] = pair.value.as!string;
|
|
|
|
|
|
|
|
assert(test[1] == "foo");
|
|
|
|
assert(test[2] == "bar");
|
|
|
|
assert(test[3] == "baz");
|
2018-04-10 06:36:09 +00:00
|
|
|
|
|
|
|
int[int] constArray = [1: 2, 3: 4];
|
|
|
|
const x = Node(constArray);
|
|
|
|
foreach (pair; x.mapping)
|
|
|
|
assert(pair.value == constArray[pair.key.as!int]);
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Return a range object iterating over mapping's keys.
|
|
|
|
*
|
|
|
|
* If K is Node, simply iterate over the keys in the mapping.
|
|
|
|
* Otherwise, convert each key to T during iteration.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the nodes is not a mapping or an element
|
|
|
|
* could not be converted to specified type.
|
|
|
|
*/
|
2018-04-10 06:45:51 +00:00
|
|
|
auto mappingKeys(K = Node)() const
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
enforce(isMapping,
|
2018-04-09 22:31:21 +00:00
|
|
|
new NodeException("Trying to 'mappingKeys'-iterate over a "
|
2017-08-01 11:13:35 +00:00
|
|
|
~ nodeTypeString ~ " node", startMark_));
|
|
|
|
static if (is(Unqual!K == Node))
|
|
|
|
return mapping.map!(pair => pair.key);
|
|
|
|
else
|
|
|
|
return mapping.map!(pair => pair.key.as!K);
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
int[int] array;
|
|
|
|
Node m1 = Node(array);
|
|
|
|
m1["foo"] = 2;
|
|
|
|
m1["bar"] = 3;
|
|
|
|
|
2018-04-10 06:45:51 +00:00
|
|
|
assert(m1.mappingKeys.equal(["foo", "bar"]) || m1.mappingKeys.equal(["bar", "foo"]));
|
|
|
|
|
|
|
|
const cm1 = Node(["foo": 2, "bar": 3]);
|
|
|
|
|
|
|
|
assert(cm1.mappingKeys.equal(["foo", "bar"]) || cm1.mappingKeys.equal(["bar", "foo"]));
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Return a range object iterating over mapping's values.
|
|
|
|
*
|
|
|
|
* If V is Node, simply iterate over the values in the mapping.
|
|
|
|
* Otherwise, convert each key to V during iteration.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the nodes is not a mapping or an element
|
|
|
|
* could not be converted to specified type.
|
|
|
|
*/
|
2018-04-10 06:45:51 +00:00
|
|
|
auto mappingValues(V = Node)() const
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
enforce(isMapping,
|
2018-04-09 22:31:21 +00:00
|
|
|
new NodeException("Trying to 'mappingValues'-iterate over a "
|
2017-08-01 11:13:35 +00:00
|
|
|
~ nodeTypeString ~ " node", startMark_));
|
|
|
|
static if (is(Unqual!V == Node))
|
|
|
|
return mapping.map!(pair => pair.value);
|
|
|
|
else
|
|
|
|
return mapping.map!(pair => pair.value.as!V);
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2017-08-01 11:13:35 +00:00
|
|
|
{
|
|
|
|
int[int] array;
|
|
|
|
Node m1 = Node(array);
|
|
|
|
m1["foo"] = 2;
|
|
|
|
m1["bar"] = 3;
|
|
|
|
|
2018-04-10 06:45:51 +00:00
|
|
|
assert(m1.mappingValues.equal([2, 3]) || m1.mappingValues.equal([3, 2]));
|
|
|
|
|
|
|
|
const cm1 = Node(["foo": 2, "bar": 3]);
|
|
|
|
|
|
|
|
assert(cm1.mappingValues.equal([2, 3]) || cm1.mappingValues.equal([3, 2]));
|
2017-08-01 11:13:35 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 06:36:09 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Foreach over a sequence, getting each element as T.
|
|
|
|
*
|
|
|
|
* If T is Node, simply iterate over the nodes in the sequence.
|
|
|
|
* Otherwise, convert each node to T during iteration.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a sequence or an
|
|
|
|
* element could not be converted to specified type.
|
|
|
|
*/
|
2018-07-18 07:14:42 +00:00
|
|
|
int opApply(D)(D dg) if (isDelegate!D && (Parameters!D.length == 1))
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-07-18 07:14:42 +00:00
|
|
|
enforce(isSequence,
|
|
|
|
new NodeException("Trying to sequence-foreach over a " ~ nodeTypeString ~ " node",
|
|
|
|
startMark_));
|
2018-04-10 06:36:09 +00:00
|
|
|
|
2018-07-18 07:14:42 +00:00
|
|
|
int result;
|
|
|
|
foreach(ref node; get!(Node[]))
|
|
|
|
{
|
|
|
|
static if(is(Unqual!(Parameters!D[0]) == Node))
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-07-18 07:14:42 +00:00
|
|
|
result = dg(node);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
else
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-07-18 07:14:42 +00:00
|
|
|
Parameters!D[0] temp = node.as!(Parameters!D[0]);
|
|
|
|
result = dg(temp);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
if(result){break;}
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
/// ditto
|
|
|
|
int opApply(D)(D dg) const if (isDelegate!D && (Parameters!D.length == 1))
|
|
|
|
{
|
|
|
|
enforce(isSequence,
|
|
|
|
new NodeException("Trying to sequence-foreach over a " ~ nodeTypeString ~ " node",
|
|
|
|
startMark_));
|
|
|
|
|
|
|
|
int result;
|
|
|
|
foreach(ref node; get!(Node[]))
|
2018-04-10 06:36:09 +00:00
|
|
|
{
|
2018-07-18 07:14:42 +00:00
|
|
|
static if(is(Unqual!(Parameters!D[0]) == Node))
|
|
|
|
{
|
|
|
|
result = dg(node);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Parameters!D[0] temp = node.as!(Parameters!D[0]);
|
|
|
|
result = dg(temp);
|
|
|
|
}
|
|
|
|
if(result){break;}
|
2018-04-10 06:36:09 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
return result;
|
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
@safe unittest
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
Node n1 = Node(11);
|
|
|
|
Node n2 = Node(12);
|
|
|
|
Node n3 = Node(13);
|
|
|
|
Node n4 = Node(14);
|
2011-10-23 22:46:35 +00:00
|
|
|
Node narray = Node([n1, n2, n3, n4]);
|
2018-07-18 07:14:42 +00:00
|
|
|
const cNArray = narray;
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2018-07-18 07:14:42 +00:00
|
|
|
int[] array, array2, array3;
|
2011-08-16 12:53:13 +00:00
|
|
|
foreach(int value; narray)
|
|
|
|
{
|
|
|
|
array ~= value;
|
|
|
|
}
|
|
|
|
foreach(Node node; narray)
|
|
|
|
{
|
2011-10-22 15:06:32 +00:00
|
|
|
array2 ~= node.as!int;
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
foreach (const Node node; cNArray)
|
|
|
|
{
|
|
|
|
array3 ~= node.as!int;
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
assert(array == [11, 12, 13, 14]);
|
|
|
|
assert(array2 == [11, 12, 13, 14]);
|
2018-07-18 07:14:42 +00:00
|
|
|
assert(array3 == [11, 12, 13, 14]);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
string[] testStrs = ["1", "2", "3"];
|
|
|
|
auto node1 = Node(testStrs);
|
|
|
|
int i = 0;
|
|
|
|
foreach (string elem; node1)
|
|
|
|
{
|
|
|
|
assert(elem == testStrs[i]);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
const node2 = Node(testStrs);
|
|
|
|
i = 0;
|
|
|
|
foreach (string elem; node2)
|
|
|
|
{
|
|
|
|
assert(elem == testStrs[i]);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
immutable node3 = Node(testStrs);
|
|
|
|
i = 0;
|
|
|
|
foreach (string elem; node3)
|
|
|
|
{
|
|
|
|
assert(elem == testStrs[i]);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto node = Node(["a":1, "b":2, "c":3]);
|
|
|
|
const cNode = node;
|
|
|
|
assertThrown({foreach (Node n; node) {}}());
|
|
|
|
assertThrown({foreach (const Node n; cNode) {}}());
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Foreach over a mapping, getting each key/value as K/V.
|
|
|
|
*
|
|
|
|
* If the K and/or V is Node, simply iterate over the nodes in the mapping.
|
|
|
|
* Otherwise, convert each key/value to T during iteration.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a mapping or an
|
|
|
|
* element could not be converted to specified type.
|
|
|
|
*/
|
2018-07-18 07:14:42 +00:00
|
|
|
int opApply(DG)(DG dg) if (isDelegate!DG && (Parameters!DG.length == 2))
|
|
|
|
{
|
|
|
|
alias K = Parameters!DG[0];
|
|
|
|
alias V = Parameters!DG[1];
|
|
|
|
enforce(isMapping,
|
|
|
|
new NodeException("Trying to mapping-foreach over a " ~ nodeTypeString ~ " node",
|
|
|
|
startMark_));
|
2018-04-10 06:36:09 +00:00
|
|
|
|
2018-07-18 07:14:42 +00:00
|
|
|
int result;
|
|
|
|
foreach(ref pair; get!(Node.Pair[]))
|
|
|
|
{
|
|
|
|
static if(is(Unqual!K == Node) && is(Unqual!V == Node))
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-07-18 07:14:42 +00:00
|
|
|
result = dg(pair.key, pair.value);
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
else static if(is(Unqual!K == Node))
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2018-07-18 07:14:42 +00:00
|
|
|
V tempValue = pair.value.as!V;
|
|
|
|
result = dg(pair.key, tempValue);
|
2018-04-10 06:36:09 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
else static if(is(Unqual!V == Node))
|
|
|
|
{
|
|
|
|
K tempKey = pair.key.as!K;
|
|
|
|
result = dg(tempKey, pair.value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
K tempKey = pair.key.as!K;
|
|
|
|
V tempValue = pair.value.as!V;
|
|
|
|
result = dg(tempKey, tempValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(result){break;}
|
2018-04-10 06:36:09 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
/// ditto
|
|
|
|
int opApply(DG)(DG dg) const if (isDelegate!DG && (Parameters!DG.length == 2))
|
|
|
|
{
|
|
|
|
alias K = Parameters!DG[0];
|
|
|
|
alias V = Parameters!DG[1];
|
|
|
|
enforce(isMapping,
|
|
|
|
new NodeException("Trying to mapping-foreach over a " ~ nodeTypeString ~ " node",
|
|
|
|
startMark_));
|
|
|
|
|
|
|
|
int result;
|
|
|
|
foreach(ref pair; get!(Node.Pair[]))
|
2018-04-10 06:36:09 +00:00
|
|
|
{
|
2018-07-18 07:14:42 +00:00
|
|
|
static if(is(Unqual!K == Node) && is(Unqual!V == Node))
|
|
|
|
{
|
|
|
|
result = dg(pair.key, pair.value);
|
|
|
|
}
|
|
|
|
else static if(is(Unqual!K == Node))
|
|
|
|
{
|
|
|
|
V tempValue = pair.value.as!V;
|
|
|
|
result = dg(pair.key, tempValue);
|
|
|
|
}
|
|
|
|
else static if(is(Unqual!V == Node))
|
|
|
|
{
|
|
|
|
K tempKey = pair.key.as!K;
|
|
|
|
result = dg(tempKey, pair.value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
K tempKey = pair.key.as!K;
|
|
|
|
V tempValue = pair.value.as!V;
|
|
|
|
result = dg(tempKey, tempValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(result){break;}
|
2018-04-10 06:36:09 +00:00
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
return result;
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2011-10-23 22:46:35 +00:00
|
|
|
Node n1 = Node(cast(long)11);
|
|
|
|
Node n2 = Node(cast(long)12);
|
|
|
|
Node n3 = Node(cast(long)13);
|
|
|
|
Node n4 = Node(cast(long)14);
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2011-10-23 22:46:35 +00:00
|
|
|
Node k1 = Node("11");
|
|
|
|
Node k2 = Node("12");
|
|
|
|
Node k3 = Node("13");
|
|
|
|
Node k4 = Node("14");
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2011-10-23 22:46:35 +00:00
|
|
|
Node nmap1 = Node([Pair(k1, n1),
|
2014-05-19 17:53:18 +00:00
|
|
|
Pair(k2, n2),
|
|
|
|
Pair(k3, n3),
|
2011-10-23 22:46:35 +00:00
|
|
|
Pair(k4, n4)]);
|
2011-08-16 12:53:13 +00:00
|
|
|
|
|
|
|
int[string] expected = ["11" : 11,
|
|
|
|
"12" : 12,
|
|
|
|
"13" : 13,
|
|
|
|
"14" : 14];
|
|
|
|
int[string] array;
|
|
|
|
foreach(string key, int value; nmap1)
|
|
|
|
{
|
|
|
|
array[key] = value;
|
|
|
|
}
|
|
|
|
assert(array == expected);
|
|
|
|
|
2011-10-23 22:46:35 +00:00
|
|
|
Node nmap2 = Node([Pair(k1, Node(cast(long)5)),
|
2014-05-19 17:53:18 +00:00
|
|
|
Pair(k2, Node(true)),
|
|
|
|
Pair(k3, Node(cast(real)1.0)),
|
2011-10-23 22:46:35 +00:00
|
|
|
Pair(k4, Node("yarly"))]);
|
2011-08-16 12:53:13 +00:00
|
|
|
|
|
|
|
foreach(string key, Node value; nmap2)
|
|
|
|
{
|
|
|
|
switch(key)
|
|
|
|
{
|
2011-10-22 15:06:32 +00:00
|
|
|
case "11": assert(value.as!int == 5 ); break;
|
|
|
|
case "12": assert(value.as!bool == true ); break;
|
|
|
|
case "13": assert(value.as!float == 1.0 ); break;
|
|
|
|
case "14": assert(value.as!string == "yarly"); break;
|
2011-08-16 12:53:13 +00:00
|
|
|
default: assert(false);
|
|
|
|
}
|
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
const nmap3 = nmap2;
|
|
|
|
|
|
|
|
foreach(const Node key, const Node value; nmap3)
|
|
|
|
{
|
|
|
|
switch(key.as!string)
|
|
|
|
{
|
|
|
|
case "11": assert(value.as!int == 5 ); break;
|
|
|
|
case "12": assert(value.as!bool == true ); break;
|
|
|
|
case "13": assert(value.as!float == 1.0 ); break;
|
|
|
|
case "14": assert(value.as!string == "yarly"); break;
|
|
|
|
default: assert(false);
|
|
|
|
}
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
string[int] testStrs = [0: "1", 1: "2", 2: "3"];
|
|
|
|
auto node1 = Node(testStrs);
|
|
|
|
foreach (const int i, string elem; node1)
|
|
|
|
{
|
|
|
|
assert(elem == testStrs[i]);
|
|
|
|
}
|
|
|
|
const node2 = Node(testStrs);
|
|
|
|
foreach (const int i, string elem; node2)
|
|
|
|
{
|
|
|
|
assert(elem == testStrs[i]);
|
|
|
|
}
|
|
|
|
immutable node3 = Node(testStrs);
|
|
|
|
foreach (const int i, string elem; node3)
|
|
|
|
{
|
|
|
|
assert(elem == testStrs[i]);
|
|
|
|
}
|
|
|
|
}
|
2018-07-18 07:14:42 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto node = Node(["a", "b", "c"]);
|
|
|
|
const cNode = node;
|
|
|
|
assertThrown({foreach (Node a, Node b; node) {}}());
|
|
|
|
assertThrown({foreach (const Node a, const Node b; cNode) {}}());
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Add an element to a sequence.
|
|
|
|
*
|
|
|
|
* This method can only be called on sequence nodes.
|
|
|
|
*
|
|
|
|
* If value is a node, it is copied to the sequence directly. Otherwise
|
|
|
|
* value is converted to a node and then stored in the sequence.
|
|
|
|
*
|
|
|
|
* $(P When emitting, all values in the sequence will be emitted. When
|
|
|
|
* using the !!set tag, the user needs to ensure that all elements in
|
|
|
|
* the sequence are unique, otherwise $(B invalid) YAML code will be
|
|
|
|
* emitted.)
|
|
|
|
*
|
|
|
|
* Params: value = Value to _add to the sequence.
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
void add(T)(T value)
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2018-06-10 03:49:15 +00:00
|
|
|
if (!isValid)
|
|
|
|
{
|
|
|
|
setValue(Node[].init);
|
|
|
|
}
|
2014-05-19 17:53:18 +00:00
|
|
|
enforce(isSequence(),
|
2018-04-09 22:31:21 +00:00
|
|
|
new NodeException("Trying to add an element to a " ~ nodeTypeString ~ " node", startMark_));
|
2011-08-21 21:53:19 +00:00
|
|
|
|
|
|
|
auto nodes = get!(Node[])();
|
2011-10-28 22:31:14 +00:00
|
|
|
static if(is(Unqual!T == Node)){nodes ~= value;}
|
2012-09-13 23:21:01 +00:00
|
|
|
else {nodes ~= Node(value);}
|
2018-04-10 06:36:09 +00:00
|
|
|
setValue(nodes);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
with(Node([1, 2, 3, 4]))
|
|
|
|
{
|
|
|
|
add(5.0f);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex(4).as!float == 5.0f);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-06-10 03:49:15 +00:00
|
|
|
with(Node())
|
|
|
|
{
|
|
|
|
add(5.0f);
|
|
|
|
assert(opIndex(0).as!float == 5.0f);
|
|
|
|
}
|
|
|
|
with(Node(5.0f))
|
|
|
|
{
|
|
|
|
assertThrown!NodeException(add(5.0f));
|
|
|
|
}
|
|
|
|
with(Node([5.0f : true]))
|
|
|
|
{
|
|
|
|
assertThrown!NodeException(add(5.0f));
|
|
|
|
}
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Add a key-value pair to a mapping.
|
|
|
|
*
|
|
|
|
* This method can only be called on mapping nodes.
|
|
|
|
*
|
|
|
|
* If key and/or value is a node, it is copied to the mapping directly.
|
|
|
|
* Otherwise it is converted to a node and then stored in the mapping.
|
|
|
|
*
|
|
|
|
* $(P It is possible for the same key to be present more than once in a
|
|
|
|
* mapping. When emitting, all key-value pairs will be emitted.
|
|
|
|
* This is useful with the "!!pairs" tag, but will result in
|
|
|
|
* $(B invalid) YAML with "!!map" and "!!omap" tags.)
|
|
|
|
*
|
|
|
|
* Params: key = Key to _add.
|
|
|
|
* value = Value to _add.
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
void add(K, V)(K key, V value)
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2018-06-10 03:49:15 +00:00
|
|
|
if (!isValid)
|
|
|
|
{
|
|
|
|
setValue(Node.Pair[].init);
|
|
|
|
}
|
2014-05-19 17:53:18 +00:00
|
|
|
enforce(isMapping(),
|
2018-04-09 22:31:21 +00:00
|
|
|
new NodeException("Trying to add a key-value pair to a " ~
|
2014-05-19 17:53:18 +00:00
|
|
|
nodeTypeString ~ " node",
|
2011-10-18 14:12:22 +00:00
|
|
|
startMark_));
|
2011-08-21 21:53:19 +00:00
|
|
|
|
|
|
|
auto pairs = get!(Node.Pair[])();
|
|
|
|
pairs ~= Pair(key, value);
|
2018-04-10 06:36:09 +00:00
|
|
|
setValue(pairs);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
with(Node([1, 2], [3, 4]))
|
|
|
|
{
|
|
|
|
add(5, "6");
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex(5).as!string == "6");
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-06-10 03:49:15 +00:00
|
|
|
with(Node())
|
|
|
|
{
|
|
|
|
add(5, "6");
|
|
|
|
assert(opIndex(5).as!string == "6");
|
|
|
|
}
|
|
|
|
with(Node(5.0f))
|
|
|
|
{
|
|
|
|
assertThrown!NodeException(add(5, "6"));
|
|
|
|
}
|
|
|
|
with(Node([5.0f]))
|
|
|
|
{
|
|
|
|
assertThrown!NodeException(add(5, "6"));
|
|
|
|
}
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Determine whether a key is in a mapping, and access its value.
|
|
|
|
*
|
|
|
|
* This method can only be called on mapping nodes.
|
|
|
|
*
|
|
|
|
* Params: key = Key to search for.
|
|
|
|
*
|
|
|
|
* Returns: A pointer to the value (as a Node) corresponding to key,
|
|
|
|
* or null if not found.
|
|
|
|
*
|
|
|
|
* Note: Any modification to the node can invalidate the returned
|
|
|
|
* pointer.
|
|
|
|
*
|
|
|
|
* See_Also: contains
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
inout(Node*) opBinaryRight(string op, K)(K key) inout
|
2014-08-01 00:51:35 +00:00
|
|
|
if (op == "in")
|
2013-09-09 19:27:49 +00:00
|
|
|
{
|
2018-04-09 22:31:21 +00:00
|
|
|
enforce(isMapping, new NodeException("Trying to use 'in' on a " ~
|
2013-09-09 20:39:46 +00:00
|
|
|
nodeTypeString ~ " node", startMark_));
|
2013-09-09 19:27:49 +00:00
|
|
|
|
|
|
|
auto idx = findPair(key);
|
2013-09-09 20:39:46 +00:00
|
|
|
if(idx < 0)
|
|
|
|
{
|
2013-09-09 19:27:49 +00:00
|
|
|
return null;
|
2013-09-09 20:39:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return &(get!(Node.Pair[])[idx].value);
|
2013-09-09 19:27:49 +00:00
|
|
|
}
|
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2013-09-09 19:27:49 +00:00
|
|
|
{
|
2013-09-09 20:39:46 +00:00
|
|
|
auto mapping = Node(["foo", "baz"], ["bar", "qux"]);
|
|
|
|
assert("bad" !in mapping && ("bad" in mapping) is null);
|
|
|
|
Node* foo = "foo" in mapping;
|
2013-09-09 19:27:49 +00:00
|
|
|
assert(foo !is null);
|
|
|
|
assert(*foo == Node("bar"));
|
2013-09-09 20:39:46 +00:00
|
|
|
assert(foo.get!string == "bar");
|
2013-09-09 19:27:49 +00:00
|
|
|
*foo = Node("newfoo");
|
2013-09-09 20:39:46 +00:00
|
|
|
assert(mapping["foo"] == Node("newfoo"));
|
2013-09-09 19:27:49 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto mNode = Node(["a": 2]);
|
|
|
|
assert("a" in mNode);
|
|
|
|
const cNode = Node(["a": 2]);
|
|
|
|
assert("a" in cNode);
|
|
|
|
immutable iNode = Node(["a": 2]);
|
|
|
|
assert("a" in iNode);
|
|
|
|
}
|
2013-09-09 19:27:49 +00:00
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Remove first (if any) occurence of a value in a collection.
|
|
|
|
*
|
|
|
|
* This method can only be called on collection nodes.
|
|
|
|
*
|
|
|
|
* If the node is a sequence, the first node matching value is removed.
|
|
|
|
* If the node is a mapping, the first key-value pair where _value
|
|
|
|
* matches specified value is removed.
|
|
|
|
*
|
|
|
|
* Params: rhs = Value to _remove.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a collection.
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
void remove(T)(T rhs)
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2012-03-01 16:43:42 +00:00
|
|
|
remove_!(T, No.key, "remove")(rhs);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
with(Node([1, 2, 3, 4, 3]))
|
|
|
|
{
|
|
|
|
remove(3);
|
|
|
|
assert(length == 4);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex(2).as!int == 4);
|
|
|
|
assert(opIndex(3).as!int == 3);
|
2012-03-01 10:44:05 +00:00
|
|
|
|
|
|
|
add(YAMLNull());
|
|
|
|
assert(length == 5);
|
|
|
|
remove(YAMLNull());
|
|
|
|
assert(length == 4);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
with(Node(["1", "2", "3"], [4, 5, 6]))
|
|
|
|
{
|
|
|
|
remove(4);
|
|
|
|
assert(length == 2);
|
2012-03-01 10:44:05 +00:00
|
|
|
add("nullkey", YAMLNull());
|
|
|
|
assert(length == 3);
|
|
|
|
remove(YAMLNull());
|
|
|
|
assert(length == 2);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-01 00:51:35 +00:00
|
|
|
/** Remove element at the specified index of a collection.
|
|
|
|
*
|
|
|
|
* This method can only be called on collection nodes.
|
|
|
|
*
|
|
|
|
* If the node is a sequence, index must be integral.
|
|
|
|
*
|
|
|
|
* If the node is a mapping, remove the first key-value pair where
|
|
|
|
* key matches index.
|
|
|
|
*
|
|
|
|
* If the node is a mapping and no key matches index, nothing is removed
|
|
|
|
* and no exception is thrown. This ensures behavior siilar to D arrays
|
|
|
|
* and associative arrays.
|
|
|
|
*
|
|
|
|
* Params: index = Index to remove at.
|
|
|
|
*
|
|
|
|
* Throws: NodeException if the node is not a collection, index is out
|
|
|
|
* of range or if a non-integral index is used on a sequence node.
|
|
|
|
*/
|
2018-04-10 06:36:09 +00:00
|
|
|
void removeAt(T)(T index)
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2012-03-01 16:43:42 +00:00
|
|
|
remove_!(T, Yes.key, "removeAt")(index);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
2018-03-23 21:35:16 +00:00
|
|
|
@safe unittest
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
|
|
|
with(Node([1, 2, 3, 4, 3]))
|
|
|
|
{
|
|
|
|
removeAt(3);
|
2012-03-01 10:44:05 +00:00
|
|
|
assertThrown!NodeException(removeAt("3"));
|
2011-08-21 21:53:19 +00:00
|
|
|
assert(length == 4);
|
2011-10-22 15:06:32 +00:00
|
|
|
assert(opIndex(3).as!int == 3);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
with(Node(["1", "2", "3"], [4, 5, 6]))
|
|
|
|
{
|
2014-07-18 23:58:24 +00:00
|
|
|
// no integer 2 key, so don't remove anything
|
2012-03-01 10:44:05 +00:00
|
|
|
removeAt(2);
|
|
|
|
assert(length == 3);
|
2011-08-21 21:53:19 +00:00
|
|
|
removeAt("2");
|
|
|
|
assert(length == 2);
|
2012-03-01 10:44:05 +00:00
|
|
|
add(YAMLNull(), "nullval");
|
|
|
|
assert(length == 3);
|
|
|
|
removeAt(YAMLNull());
|
|
|
|
assert(length == 2);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
/// Compare with another _node.
|
2019-01-20 02:42:59 +00:00
|
|
|
int opCmp(const ref Node rhs) const @safe
|
2012-01-23 14:57:26 +00:00
|
|
|
{
|
2014-07-18 23:58:24 +00:00
|
|
|
// Compare tags - if equal or both null, we need to compare further.
|
2019-01-20 02:42:59 +00:00
|
|
|
const tagCmp = (tag_ is null) ? (rhs.tag_ is null) ? 0 : -1
|
|
|
|
: (rhs.tag_ is null) ? 1 : std.algorithm.comparison.cmp(tag_, rhs.tag_);
|
|
|
|
if(tagCmp != 0){return tagCmp;}
|
2012-01-23 14:57:26 +00:00
|
|
|
|
|
|
|
static int cmp(T1, T2)(T1 a, T2 b)
|
|
|
|
{
|
|
|
|
return a > b ? 1 :
|
|
|
|
a < b ? -1 :
|
|
|
|
0;
|
|
|
|
}
|
2011-10-30 17:12:02 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Compare validity: if both valid, we have to compare further.
|
2012-01-23 14:57:26 +00:00
|
|
|
const v1 = isValid;
|
|
|
|
const v2 = rhs.isValid;
|
|
|
|
if(!v1){return v2 ? -1 : 0;}
|
|
|
|
if(!v2){return 1;}
|
2014-05-19 17:53:18 +00:00
|
|
|
|
2019-01-20 02:42:59 +00:00
|
|
|
const typeCmp = cmp(newType, rhs.newType);
|
2012-01-23 14:57:26 +00:00
|
|
|
if(typeCmp != 0){return typeCmp;}
|
|
|
|
|
|
|
|
static int compareCollections(T)(const ref Node lhs, const ref Node rhs)
|
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
const c1 = lhs.getValue!T;
|
|
|
|
const c2 = rhs.getValue!T;
|
2012-01-23 14:57:26 +00:00
|
|
|
if(c1 is c2){return 0;}
|
|
|
|
if(c1.length != c2.length)
|
2011-08-20 20:15:20 +00:00
|
|
|
{
|
2012-01-23 14:57:26 +00:00
|
|
|
return cmp(c1.length, c2.length);
|
2011-08-20 20:15:20 +00:00
|
|
|
}
|
2014-07-18 23:58:24 +00:00
|
|
|
// Equal lengths, compare items.
|
2012-01-23 14:57:26 +00:00
|
|
|
foreach(i; 0 .. c1.length)
|
2011-08-20 20:15:20 +00:00
|
|
|
{
|
2019-01-20 02:42:59 +00:00
|
|
|
const itemCmp = c1[i].opCmp(c2[i]);
|
2012-01-23 14:57:26 +00:00
|
|
|
if(itemCmp != 0){return itemCmp;}
|
2011-08-20 20:15:20 +00:00
|
|
|
}
|
2012-01-23 14:57:26 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2011-10-30 17:12:02 +00:00
|
|
|
|
2012-01-23 14:57:26 +00:00
|
|
|
if(isSequence){return compareCollections!(Node[])(this, rhs);}
|
|
|
|
if(isMapping) {return compareCollections!(Pair[])(this, rhs);}
|
2014-05-19 17:53:18 +00:00
|
|
|
if(isString)
|
2012-01-23 14:57:26 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
return std.algorithm.cmp(getValue!string,
|
|
|
|
rhs.getValue!string);
|
2012-01-23 14:57:26 +00:00
|
|
|
}
|
2014-05-19 17:53:18 +00:00
|
|
|
if(isInt)
|
2012-01-23 14:57:26 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
return cmp(getValue!long, rhs.getValue!long);
|
2012-01-23 14:57:26 +00:00
|
|
|
}
|
2014-05-19 17:53:18 +00:00
|
|
|
if(isBool)
|
2012-01-23 14:57:26 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
const b1 = getValue!bool;
|
|
|
|
const b2 = rhs.getValue!bool;
|
2012-01-23 14:57:26 +00:00
|
|
|
return b1 ? b2 ? 0 : 1
|
|
|
|
: b2 ? -1 : 0;
|
|
|
|
}
|
2014-05-19 17:53:18 +00:00
|
|
|
if(isBinary)
|
2012-01-23 14:57:26 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
const b1 = getValue!(ubyte[]);
|
|
|
|
const b2 = rhs.getValue!(ubyte[]);
|
2012-01-23 14:57:26 +00:00
|
|
|
return std.algorithm.cmp(b1, b2);
|
|
|
|
}
|
2014-05-19 17:53:18 +00:00
|
|
|
if(isNull)
|
2012-01-23 14:57:26 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
2014-07-18 23:58:24 +00:00
|
|
|
// Floats need special handling for NaNs .
|
|
|
|
// We consider NaN to be lower than any float.
|
2012-01-23 14:57:26 +00:00
|
|
|
if(isFloat)
|
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
const r1 = getValue!real;
|
|
|
|
const r2 = rhs.getValue!real;
|
2012-01-23 14:57:26 +00:00
|
|
|
if(isNaN(r1))
|
2011-08-20 20:15:20 +00:00
|
|
|
{
|
2012-01-23 14:57:26 +00:00
|
|
|
return isNaN(r2) ? 0 : -1;
|
2011-10-30 17:12:02 +00:00
|
|
|
}
|
2012-01-23 14:57:26 +00:00
|
|
|
if(isNaN(r2))
|
2011-10-30 17:12:02 +00:00
|
|
|
{
|
2012-01-23 14:57:26 +00:00
|
|
|
return 1;
|
2011-08-20 20:15:20 +00:00
|
|
|
}
|
2014-07-18 23:58:24 +00:00
|
|
|
// Fuzzy equality.
|
2012-01-23 14:57:26 +00:00
|
|
|
if(r1 <= r2 + real.epsilon && r1 >= r2 - real.epsilon)
|
2011-10-30 17:12:02 +00:00
|
|
|
{
|
2012-01-23 14:57:26 +00:00
|
|
|
return 0;
|
2011-10-30 17:12:02 +00:00
|
|
|
}
|
2012-01-23 14:57:26 +00:00
|
|
|
return cmp(r1, r2);
|
2011-08-20 20:15:20 +00:00
|
|
|
}
|
2012-01-23 14:57:26 +00:00
|
|
|
else if(isTime)
|
2011-08-20 20:15:20 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
const t1 = getValue!SysTime;
|
|
|
|
const t2 = rhs.getValue!SysTime;
|
2012-01-23 14:57:26 +00:00
|
|
|
return cmp(t1, t2);
|
|
|
|
}
|
2013-04-23 23:32:16 +00:00
|
|
|
assert(false, "Unknown type of node for comparison : " ~ type.toString());
|
2011-08-20 20:15:20 +00:00
|
|
|
}
|
|
|
|
|
2019-01-20 02:42:59 +00:00
|
|
|
// Ensure opCmp is symmetric for collections
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
auto node1 = Node(
|
|
|
|
[
|
|
|
|
Node("New York Yankees", "tag:yaml.org,2002:str"),
|
|
|
|
Node("Atlanta Braves", "tag:yaml.org,2002:str")
|
|
|
|
], "tag:yaml.org,2002:seq"
|
|
|
|
);
|
|
|
|
auto node2 = Node(
|
|
|
|
[
|
|
|
|
Node("Detroit Tigers", "tag:yaml.org,2002:str"),
|
|
|
|
Node("Chicago cubs", "tag:yaml.org,2002:str")
|
|
|
|
], "tag:yaml.org,2002:seq"
|
|
|
|
);
|
|
|
|
assert(node1 > node2);
|
|
|
|
assert(node2 < node1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute hash of the node.
|
|
|
|
hash_t toHash() nothrow const @trusted
|
|
|
|
{
|
|
|
|
const valueHash = value_.toHash();
|
|
|
|
|
|
|
|
return tag_ is null ? valueHash : tag_.hashOf(valueHash);
|
|
|
|
}
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
assert(Node(42).toHash() != Node(41).toHash());
|
|
|
|
assert(Node(42).toHash() != Node(42, "some-tag").toHash());
|
|
|
|
}
|
|
|
|
|
|
|
|
package:
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Get a string representation of the node tree. Used for debugging.
|
|
|
|
//
|
|
|
|
// Params: level = Level of the node in the tree.
|
|
|
|
//
|
|
|
|
// Returns: String representing the node tree.
|
2018-04-10 06:36:09 +00:00
|
|
|
@property string debugString(uint level = 0) const @safe
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
|
|
|
string indent;
|
|
|
|
foreach(i; 0 .. level){indent ~= " ";}
|
|
|
|
|
|
|
|
if(!isValid){return indent ~ "invalid";}
|
|
|
|
|
|
|
|
if(isSequence)
|
|
|
|
{
|
|
|
|
string result = indent ~ "sequence:\n";
|
|
|
|
foreach(ref node; get!(Node[]))
|
|
|
|
{
|
|
|
|
result ~= node.debugString(level + 1);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
if(isMapping)
|
|
|
|
{
|
|
|
|
string result = indent ~ "mapping:\n";
|
|
|
|
foreach(ref pair; get!(Node.Pair[]))
|
|
|
|
{
|
|
|
|
result ~= indent ~ " pair\n";
|
|
|
|
result ~= pair.key.debugString(level + 2);
|
|
|
|
result ~= pair.value.debugString(level + 2);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
if(isScalar)
|
|
|
|
{
|
2014-05-19 17:53:18 +00:00
|
|
|
return indent ~ "scalar(" ~
|
2013-04-23 23:32:16 +00:00
|
|
|
(convertsTo!string ? get!string : type.toString()) ~ ")\n";
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
|
2019-01-15 07:37:50 +00:00
|
|
|
// Get type of the node value.
|
2018-03-23 21:35:16 +00:00
|
|
|
@property TypeInfo type() const @safe nothrow
|
2013-10-16 21:14:37 +00:00
|
|
|
{
|
2018-03-23 21:35:16 +00:00
|
|
|
return value_.type;
|
2013-10-16 21:14:37 +00:00
|
|
|
}
|
2011-08-20 20:15:20 +00:00
|
|
|
|
2019-01-15 07:37:50 +00:00
|
|
|
// Get type of the node value.
|
|
|
|
@property NodeType newType() const @safe nothrow
|
|
|
|
{
|
|
|
|
if (value_.type is typeid(bool))
|
|
|
|
{
|
|
|
|
return NodeType.boolean;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(long))
|
|
|
|
{
|
|
|
|
return NodeType.integer;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(Node[]))
|
|
|
|
{
|
|
|
|
return NodeType.sequence;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(ubyte[]))
|
|
|
|
{
|
|
|
|
return NodeType.binary;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(string))
|
|
|
|
{
|
|
|
|
return NodeType.string;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(Node.Pair[]))
|
|
|
|
{
|
|
|
|
return NodeType.mapping;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(SysTime))
|
|
|
|
{
|
|
|
|
return NodeType.timestamp;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(YAMLNull))
|
|
|
|
{
|
|
|
|
return NodeType.null_;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(YAMLMerge))
|
|
|
|
{
|
|
|
|
return NodeType.merge;
|
|
|
|
}
|
|
|
|
else if (value_.type is typeid(real))
|
|
|
|
{
|
|
|
|
return NodeType.decimal;
|
|
|
|
}
|
|
|
|
else assert(0, text(value_.type));
|
|
|
|
}
|
|
|
|
|
2016-12-08 16:59:22 +00:00
|
|
|
public:
|
2014-07-18 23:58:24 +00:00
|
|
|
// Determine if the value stored by the node is of specified type.
|
|
|
|
//
|
|
|
|
// This only works for default YAML types, not for user defined types.
|
2018-03-23 21:35:16 +00:00
|
|
|
@property bool isType(T)() const
|
2013-10-16 21:14:37 +00:00
|
|
|
{
|
|
|
|
return this.type is typeid(Unqual!T);
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Is the value a bool?
|
2018-06-13 08:17:20 +00:00
|
|
|
alias isBool = isType!bool;
|
2011-10-30 17:12:02 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Is the value a raw binary buffer?
|
2018-06-13 08:17:20 +00:00
|
|
|
alias isBinary = isType!(ubyte[]);
|
2011-10-30 17:12:02 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Is the value an integer?
|
2018-06-13 08:17:20 +00:00
|
|
|
alias isInt = isType!long;
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Is the value a floating point number?
|
2018-06-13 08:17:20 +00:00
|
|
|
alias isFloat = isType!real;
|
2011-08-16 12:53:13 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Is the value a string?
|
2018-06-13 08:17:20 +00:00
|
|
|
alias isString = isType!string;
|
2011-10-11 13:58:23 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Is the value a timestamp?
|
2018-06-13 08:17:20 +00:00
|
|
|
alias isTime = isType!SysTime;
|
2011-10-30 17:12:02 +00:00
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Does given node have the same type as this node?
|
2012-09-08 23:42:13 +00:00
|
|
|
bool hasEqualType(const ref Node node) const @safe
|
2012-12-27 19:11:26 +00:00
|
|
|
{
|
2013-10-16 21:15:23 +00:00
|
|
|
return this.type is node.type;
|
2011-08-20 20:15:20 +00:00
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Return a string describing node type (sequence, mapping or scalar)
|
2013-10-16 21:15:23 +00:00
|
|
|
@property string nodeTypeString() const @safe nothrow
|
2012-03-01 12:17:32 +00:00
|
|
|
{
|
|
|
|
assert(isScalar || isSequence || isMapping, "Unknown node type");
|
|
|
|
return isScalar ? "scalar" :
|
|
|
|
isSequence ? "sequence" :
|
|
|
|
isMapping ? "mapping" : "";
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Determine if the value can be converted to specified type.
|
2018-03-23 21:35:16 +00:00
|
|
|
@property bool convertsTo(T)() const
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
|
|
|
if(isType!T){return true;}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Every type allowed in Value should be convertible to string.
|
2011-10-30 09:52:40 +00:00
|
|
|
static if(isSomeString!T) {return true;}
|
2011-08-16 12:53:13 +00:00
|
|
|
else static if(isFloatingPoint!T){return isInt() || isFloat();}
|
|
|
|
else static if(isIntegral!T) {return isInt();}
|
2017-07-26 21:47:53 +00:00
|
|
|
else static if(is(Unqual!T==bool)){return isBool();}
|
2011-08-16 12:53:13 +00:00
|
|
|
else {return false;}
|
|
|
|
}
|
2018-06-10 06:52:12 +00:00
|
|
|
/**
|
|
|
|
* Sets the style of this node when dumped.
|
|
|
|
*
|
|
|
|
* Params: style = Any valid style.
|
|
|
|
*/
|
|
|
|
void setStyle(CollectionStyle style) @safe
|
|
|
|
{
|
|
|
|
enforce(!isValid || isSequence || isMapping, new NodeException(
|
|
|
|
"Cannot set collection style for non-collection nodes", startMark_));
|
|
|
|
collectionStyle = style;
|
|
|
|
}
|
|
|
|
/// Ditto
|
|
|
|
void setStyle(ScalarStyle style) @safe
|
|
|
|
{
|
|
|
|
enforce(!isValid || (!isSequence && !isMapping), new NodeException(
|
|
|
|
"Cannot set scalar style for non-scalar nodes", startMark_));
|
|
|
|
scalarStyle = style;
|
|
|
|
}
|
|
|
|
///
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import dyaml.dumper;
|
2018-06-22 03:59:10 +00:00
|
|
|
auto stream = new Appender!string();
|
2018-06-10 06:52:12 +00:00
|
|
|
auto node = Node([1, 2, 3, 4, 5]);
|
2018-08-27 00:49:14 +00:00
|
|
|
node.setStyle(CollectionStyle.block);
|
2018-06-10 06:52:12 +00:00
|
|
|
|
2018-06-22 03:59:10 +00:00
|
|
|
auto dumper = dumper(stream);
|
2018-06-10 06:52:12 +00:00
|
|
|
dumper.dump(node);
|
|
|
|
}
|
|
|
|
///
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import dyaml.dumper;
|
2018-06-22 03:59:10 +00:00
|
|
|
auto stream = new Appender!string();
|
2018-06-10 06:52:12 +00:00
|
|
|
auto node = Node(4);
|
2018-08-27 00:49:14 +00:00
|
|
|
node.setStyle(ScalarStyle.literal);
|
2018-06-10 06:52:12 +00:00
|
|
|
|
2018-06-22 03:59:10 +00:00
|
|
|
auto dumper = dumper(stream);
|
2018-06-10 06:52:12 +00:00
|
|
|
dumper.dump(node);
|
|
|
|
}
|
|
|
|
@safe unittest
|
|
|
|
{
|
2018-08-27 00:49:14 +00:00
|
|
|
assertThrown!NodeException(Node(4).setStyle(CollectionStyle.block));
|
|
|
|
assertThrown!NodeException(Node([4]).setStyle(ScalarStyle.literal));
|
2018-06-10 06:52:12 +00:00
|
|
|
}
|
|
|
|
@safe unittest
|
|
|
|
{
|
|
|
|
import dyaml.dumper;
|
|
|
|
{
|
2018-06-22 03:59:10 +00:00
|
|
|
auto stream = new Appender!string();
|
2018-06-10 06:52:12 +00:00
|
|
|
auto node = Node([1, 2, 3, 4, 5]);
|
2018-08-27 00:49:14 +00:00
|
|
|
node.setStyle(CollectionStyle.block);
|
2018-06-22 03:59:10 +00:00
|
|
|
auto dumper = dumper(stream);
|
2018-06-10 06:52:12 +00:00
|
|
|
dumper.explicitEnd = false;
|
|
|
|
dumper.explicitStart = false;
|
|
|
|
dumper.YAMLVersion = null;
|
|
|
|
dumper.dump(node);
|
|
|
|
|
|
|
|
//Block style should start with a hyphen.
|
|
|
|
assert(stream.data[0] == '-');
|
|
|
|
}
|
|
|
|
{
|
2018-06-22 03:59:10 +00:00
|
|
|
auto stream = new Appender!string();
|
2018-06-10 06:52:12 +00:00
|
|
|
auto node = Node([1, 2, 3, 4, 5]);
|
2018-08-27 00:49:14 +00:00
|
|
|
node.setStyle(CollectionStyle.flow);
|
2018-06-22 03:59:10 +00:00
|
|
|
auto dumper = dumper(stream);
|
2018-06-10 06:52:12 +00:00
|
|
|
dumper.explicitEnd = false;
|
|
|
|
dumper.explicitStart = false;
|
|
|
|
dumper.YAMLVersion = null;
|
|
|
|
dumper.dump(node);
|
|
|
|
|
|
|
|
//Flow style should start with a bracket.
|
|
|
|
assert(stream.data[0] == '[');
|
|
|
|
}
|
|
|
|
{
|
2018-06-22 03:59:10 +00:00
|
|
|
auto stream = new Appender!string();
|
2018-06-10 06:52:12 +00:00
|
|
|
auto node = Node(1);
|
2018-08-27 00:49:14 +00:00
|
|
|
node.setStyle(ScalarStyle.singleQuoted);
|
2018-06-22 03:59:10 +00:00
|
|
|
auto dumper = dumper(stream);
|
2018-06-10 06:52:12 +00:00
|
|
|
dumper.explicitEnd = false;
|
|
|
|
dumper.explicitStart = false;
|
|
|
|
dumper.YAMLVersion = null;
|
|
|
|
dumper.dump(node);
|
|
|
|
|
|
|
|
assert(stream.data == "!!int '1'\n");
|
|
|
|
}
|
|
|
|
{
|
2018-06-22 03:59:10 +00:00
|
|
|
auto stream = new Appender!string();
|
2018-06-10 06:52:12 +00:00
|
|
|
auto node = Node(1);
|
2018-08-27 00:49:14 +00:00
|
|
|
node.setStyle(ScalarStyle.doubleQuoted);
|
2018-06-22 03:59:10 +00:00
|
|
|
auto dumper = dumper(stream);
|
2018-06-10 06:52:12 +00:00
|
|
|
dumper.explicitEnd = false;
|
|
|
|
dumper.explicitStart = false;
|
|
|
|
dumper.YAMLVersion = null;
|
|
|
|
dumper.dump(node);
|
|
|
|
|
|
|
|
assert(stream.data == "!!int \"1\"\n");
|
|
|
|
}
|
|
|
|
}
|
2011-08-21 21:53:19 +00:00
|
|
|
|
2016-12-08 16:59:22 +00:00
|
|
|
private:
|
2014-07-18 23:58:24 +00:00
|
|
|
// Implementation of contains() and containsKey().
|
2018-04-10 06:36:09 +00:00
|
|
|
bool contains_(T, Flag!"key" key, string func)(T rhs) const
|
2012-03-01 12:17:32 +00:00
|
|
|
{
|
|
|
|
static if(!key) if(isSequence)
|
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
foreach(ref node; getValue!(Node[]))
|
2012-03-01 12:17:32 +00:00
|
|
|
{
|
|
|
|
if(node == rhs){return true;}
|
2014-05-19 17:53:18 +00:00
|
|
|
}
|
2012-03-01 12:17:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(isMapping)
|
|
|
|
{
|
2012-09-13 23:21:01 +00:00
|
|
|
return findPair!(T, key)(rhs) >= 0;
|
2012-03-01 12:17:32 +00:00
|
|
|
}
|
|
|
|
|
2018-04-09 22:31:21 +00:00
|
|
|
throw new NodeException("Trying to use " ~ func ~ "() on a " ~ nodeTypeString ~ " node",
|
2012-03-01 12:17:32 +00:00
|
|
|
startMark_);
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Implementation of remove() and removeAt()
|
2018-03-23 21:35:16 +00:00
|
|
|
void remove_(T, Flag!"key" key, string func)(T rhs)
|
2012-03-01 16:43:42 +00:00
|
|
|
{
|
|
|
|
enforce(isSequence || isMapping,
|
2018-04-09 22:31:21 +00:00
|
|
|
new NodeException("Trying to " ~ func ~ "() from a " ~ nodeTypeString ~ " node",
|
2012-03-01 16:43:42 +00:00
|
|
|
startMark_));
|
|
|
|
|
|
|
|
static void removeElem(E, I)(ref Node node, I index)
|
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
auto elems = node.getValue!(E[]);
|
2012-03-19 13:31:11 +00:00
|
|
|
moveAll(elems[cast(size_t)index + 1 .. $], elems[cast(size_t)index .. $ - 1]);
|
2012-03-01 16:43:42 +00:00
|
|
|
elems.length = elems.length - 1;
|
2018-04-10 06:36:09 +00:00
|
|
|
node.setValue(elems);
|
2012-03-01 16:43:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(isSequence())
|
|
|
|
{
|
|
|
|
static long getIndex(ref Node node, ref T rhs)
|
|
|
|
{
|
2013-05-23 12:33:34 +00:00
|
|
|
foreach(idx, ref elem; node.get!(Node[]))
|
2012-03-01 16:43:42 +00:00
|
|
|
{
|
2013-05-23 12:33:34 +00:00
|
|
|
if(elem.convertsTo!T && elem.as!(T, No.stringConversion) == rhs)
|
2012-03-01 16:43:42 +00:00
|
|
|
{
|
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const index = select!key(rhs, getIndex(this, rhs));
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// This throws if the index is not integral.
|
2012-03-01 16:43:42 +00:00
|
|
|
checkSequenceIndex(index);
|
|
|
|
|
|
|
|
static if(isIntegral!(typeof(index))){removeElem!Node(this, index);}
|
|
|
|
else {assert(false, "Non-integral sequence index");}
|
|
|
|
}
|
|
|
|
else if(isMapping())
|
|
|
|
{
|
2012-09-13 23:21:01 +00:00
|
|
|
const index = findPair!(T, key)(rhs);
|
2012-03-01 16:43:42 +00:00
|
|
|
if(index >= 0){removeElem!Pair(this, index);}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Get index of pair with key (or value, if key is false) matching index.
|
2018-04-24 16:51:29 +00:00
|
|
|
// Cannot be inferred @safe due to https://issues.dlang.org/show_bug.cgi?id=16528
|
|
|
|
sizediff_t findPair(T, Flag!"key" key = Yes.key)(const ref T index) const @safe
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
const pairs = getValue!(Pair[])();
|
2011-10-30 17:12:02 +00:00
|
|
|
const(Node)* node;
|
|
|
|
foreach(idx, ref const(Pair) pair; pairs)
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2012-09-13 23:21:01 +00:00
|
|
|
static if(key){node = &pair.key;}
|
|
|
|
else {node = &pair.value;}
|
2011-08-21 21:53:19 +00:00
|
|
|
|
2012-03-01 10:44:05 +00:00
|
|
|
|
2018-06-20 09:51:23 +00:00
|
|
|
const bool typeMatch = (isFloatingPoint!T && (node.isInt || node.isFloat)) ||
|
2012-03-01 10:44:05 +00:00
|
|
|
(isIntegral!T && node.isInt) ||
|
2017-07-26 21:47:53 +00:00
|
|
|
(is(Unqual!T==bool) && node.isBool) ||
|
2012-03-01 10:44:05 +00:00
|
|
|
(isSomeString!T && node.isString) ||
|
|
|
|
(node.isType!T);
|
|
|
|
if(typeMatch && *node == index)
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2012-03-01 10:44:05 +00:00
|
|
|
return idx;
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-07-18 23:58:24 +00:00
|
|
|
// Check if index is integral and in range.
|
2018-04-10 06:36:09 +00:00
|
|
|
void checkSequenceIndex(T)(T index) const
|
2011-08-21 21:53:19 +00:00
|
|
|
{
|
2014-05-19 17:53:18 +00:00
|
|
|
assert(isSequence,
|
2012-03-01 14:23:53 +00:00
|
|
|
"checkSequenceIndex() called on a " ~ nodeTypeString ~ " node");
|
|
|
|
|
2011-08-21 21:53:19 +00:00
|
|
|
static if(!isIntegral!T)
|
|
|
|
{
|
2018-04-09 22:31:21 +00:00
|
|
|
throw new NodeException("Indexing a sequence with a non-integral type.", startMark_);
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
enforce(index >= 0 && index < getValue!(Node[]).length,
|
2018-04-09 22:31:21 +00:00
|
|
|
new NodeException("Sequence index out of range: " ~ to!string(index),
|
2011-10-18 14:12:22 +00:00
|
|
|
startMark_));
|
2011-08-21 21:53:19 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
// Safe wrapper for getting a value out of the variant.
|
|
|
|
inout(T) getValue(T)() @trusted inout
|
2011-10-30 17:12:02 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
return value_.get!T;
|
|
|
|
}
|
|
|
|
// Safe wrapper for coercing a value out of the variant.
|
|
|
|
inout(T) coerceValue(T)() @trusted inout
|
|
|
|
{
|
|
|
|
return (cast(Value)value_).coerce!T;
|
|
|
|
}
|
|
|
|
// Safe wrapper for setting a value for the variant.
|
|
|
|
void setValue(T)(T value) @trusted
|
|
|
|
{
|
|
|
|
static if (allowed!T)
|
2011-10-30 17:12:02 +00:00
|
|
|
{
|
2018-04-10 06:36:09 +00:00
|
|
|
value_ = value;
|
2011-10-30 17:12:02 +00:00
|
|
|
}
|
2018-04-10 06:36:09 +00:00
|
|
|
else
|
2011-10-30 17:12:02 +00:00
|
|
|
{
|
2019-01-20 02:42:59 +00:00
|
|
|
auto tmpNode = cast(Node)value;
|
|
|
|
tag_ = tmpNode.tag;
|
|
|
|
scalarStyle = tmpNode.scalarStyle;
|
|
|
|
collectionStyle = tmpNode.collectionStyle;
|
|
|
|
value_ = tmpNode.value_;
|
2011-10-30 17:12:02 +00:00
|
|
|
}
|
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
package:
|
2014-07-18 23:58:24 +00:00
|
|
|
// Merge pairs into an array of pairs based on merge rules in the YAML spec.
|
|
|
|
//
|
|
|
|
// Any new pair will only be added if there is not already a pair
|
|
|
|
// with the same key.
|
|
|
|
//
|
|
|
|
// Params: pairs = Appender managing the array of pairs to merge into.
|
|
|
|
// toMerge = Pairs to merge.
|
2018-03-23 21:35:16 +00:00
|
|
|
void merge(ref Appender!(Node.Pair[]) pairs, Node.Pair[] toMerge) @safe
|
2011-08-16 12:53:13 +00:00
|
|
|
{
|
2011-10-23 18:17:37 +00:00
|
|
|
bool eq(ref Node.Pair a, ref Node.Pair b){return a.key == b.key;}
|
|
|
|
|
2012-12-29 23:25:23 +00:00
|
|
|
foreach(ref pair; toMerge) if(!canFind!eq(pairs.data, pair))
|
2011-10-23 18:17:37 +00:00
|
|
|
{
|
2012-12-29 23:25:23 +00:00
|
|
|
pairs.put(pair);
|
2011-10-23 18:17:37 +00:00
|
|
|
}
|
2011-08-16 12:53:13 +00:00
|
|
|
}
|
2019-01-15 07:37:50 +00:00
|
|
|
|
|
|
|
enum hasNodeConstructor(T) = hasSimpleNodeConstructor!T || hasExpandedNodeConstructor!T;
|
|
|
|
template hasSimpleNodeConstructor(T)
|
|
|
|
{
|
|
|
|
static if (is(T == struct))
|
|
|
|
{
|
|
|
|
enum hasSimpleNodeConstructor = is(typeof(T(Node.init)));
|
|
|
|
}
|
|
|
|
else static if (is(T == class))
|
|
|
|
{
|
|
|
|
enum hasSimpleNodeConstructor = is(typeof(new inout T(Node.init)));
|
|
|
|
}
|
|
|
|
else enum hasSimpleNodeConstructor = false;
|
|
|
|
}
|
|
|
|
template hasExpandedNodeConstructor(T)
|
|
|
|
{
|
|
|
|
static if (is(T == struct))
|
|
|
|
{
|
|
|
|
enum hasExpandedNodeConstructor = is(typeof(T(Node.init, "")));
|
|
|
|
}
|
|
|
|
else static if (is(T == class))
|
|
|
|
{
|
|
|
|
enum hasExpandedNodeConstructor = is(typeof(new inout T(Node.init, "")));
|
|
|
|
}
|
|
|
|
else enum hasExpandedNodeConstructor = false;
|
|
|
|
}
|
|
|
|
enum castableToNode(T) = (is(T == struct) || is(T == class)) && is(typeof(T.opCast!Node()) : Node);
|