diff --git a/source/dyaml/node.d b/source/dyaml/node.d index 9d1a2db..368cfe9 100644 --- a/source/dyaml/node.d +++ b/source/dyaml/node.d @@ -1041,6 +1041,241 @@ struct Node } } + /** Return a range object iterating 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. + */ + auto sequence(T = Node)() @trusted + { + enforce(isSequence, + new Error("Trying to 'sequence'-iterate over a " ~ nodeTypeString ~ " node", + startMark_)); + struct Range + { + Node[] subnodes; + size_t position; + + this(Node[] nodes) + { + subnodes = nodes; + position = 0; + } + + /* Input range functionality. */ + bool empty() @property { return position >= subnodes.length; } + + void popFront() + { + enforce(!empty, "Attempted to popFront an empty sequence"); + position++; + } + + T front() @property + { + enforce(!empty, "Attempted to take the front of an empty sequence"); + 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. */ + void popBack() + { + enforce(!empty, "Attempted to popBack an empty sequence"); + subnodes = subnodes[0 .. $ - 1]; + } + + T back() + { + enforce(!empty, "Attempted to take the back of an empty sequence"); + static if (is(Unqual!T == Node)) + return subnodes[$ - 1]; + else + return subnodes[$ - 1].as!T; + } + + /* Random-access range functionality. */ + size_t length() const @property { return subnodes.length; } + T opIndex(size_t index) + { + static if (is(Unqual!T == Node)) + return subnodes[index]; + else + return subnodes[index].as!T; + } + + static assert(isInputRange!Range); + static assert(isForwardRange!Range); + static assert(isBidirectionalRange!Range); + static assert(isRandomAccessRange!Range); + } + return Range(get!(Node[])); + } + unittest + { + writeln("D:YAML Node sequence unittest"); + + Node n1 = Node([1, 2, 3, 4]); + int[int] array; + Node n2 = Node(array); + + auto r = n1.sequence!int.map!(x => x * 10); + assert(r.equal([10, 20, 30, 40])); + + assertThrown(n2.sequence); + } + + /** Return a range object iterating over mapping's pairs. + * + * Throws: NodeException if the node is not a mapping. + * + */ + auto mapping() @trusted + { + enforce(isMapping, + new Error("Trying to 'mapping'-iterate over a " + ~ nodeTypeString ~ " node", startMark_)); + struct Range + { + Node.Pair[] pairs; + size_t position; + + this(Node.Pair[] pairs) + { + this.pairs = pairs; + position = 0; + } + + /* Input range functionality. */ + bool empty() { return position >= pairs.length; } + + void popFront() + { + enforce(!empty, "Attempted to popFront an empty mapping"); + position++; + } + + Pair front() + { + enforce(!empty, "Attempted to take the front of an empty mapping"); + return pairs[position]; + } + + /* Forward range functionality. */ + Range save() { return this; } + + /* Bidirectional range functionality. */ + void popBack() + { + enforce(!empty, "Attempted to popBack an empty mapping"); + pairs = pairs[0 .. $ - 1]; + } + + Pair back() + { + enforce(!empty, "Attempted to take the back of an empty mapping"); + return pairs[$ - 1]; + } + + /* Random-access range functionality. */ + size_t length() const @property { return pairs.length; } + Pair opIndex(size_t index) { return pairs[index]; } + + static assert(isInputRange!Range); + static assert(isForwardRange!Range); + static assert(isBidirectionalRange!Range); + static assert(isRandomAccessRange!Range); + } + return Range(get!(Node.Pair[])); + } + unittest + { + writeln("D:YAML Node mapping unittest"); + + 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"); + } + + /** 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. + */ + auto mappingKeys(K = Node)() @trusted + { + enforce(isMapping, + new Error("Trying to 'mappingKeys'-iterate over a " + ~ nodeTypeString ~ " node", startMark_)); + static if (is(Unqual!K == Node)) + return mapping.map!(pair => pair.key); + else + return mapping.map!(pair => pair.key.as!K); + } + unittest + { + writeln("D:YAML Node mappingKeys unittest"); + + int[int] array; + Node m1 = Node(array); + m1["foo"] = 2; + m1["bar"] = 3; + + assert(m1.mappingKeys.equal(["foo", "bar"])); + } + + /** 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. + */ + auto mappingValues(V = Node)() @trusted + { + enforce(isMapping, + new Error("Trying to 'mappingValues'-iterate over a " + ~ nodeTypeString ~ " node", startMark_)); + static if (is(Unqual!V == Node)) + return mapping.map!(pair => pair.value); + else + return mapping.map!(pair => pair.value.as!V); + } + unittest + { + writeln("D:YAML Node mappingValues unittest"); + + int[int] array; + Node m1 = Node(array); + m1["foo"] = 2; + m1["bar"] = 3; + + assert(m1.mappingValues.equal([2, 3])); + } + /** Foreach over a sequence, getting each element as T. * * If T is Node, simply iterate over the nodes in the sequence.