dyaml/source/dyaml/representer.d
Cameron Ross b63ea1aaae make node types into enums and clean up code using them (#225)
* make node types into enums and clean up code using them

* add some tests for anchorable
2019-01-28 02:56:00 +01:00

518 lines
17 KiB
D

// 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)
/**
* YAML node _representer. Prepares YAML nodes for output. A tutorial can be
* found $(LINK2 ../tutorials/custom_types.html, here).
*
* Code based on $(LINK2 http://www.pyyaml.org, PyYAML).
*/
module dyaml.representer;
import std.algorithm;
import std.array;
import std.base64;
import std.container;
import std.conv;
import std.datetime;
import std.exception;
import std.format;
import std.math;
import std.typecons;
import std.string;
import dyaml.exception;
import dyaml.node;
import dyaml.serializer;
import dyaml.style;
package:
///Exception thrown on Representer errors.
class RepresenterException : YAMLException
{
mixin ExceptionCtors;
}
/**
* Represents YAML nodes as scalar, sequence and mapping nodes ready for output.
*/
Node representData(const Node data, ScalarStyle defaultScalarStyle, CollectionStyle defaultCollectionStyle) @safe
{
Node result;
final switch(data.type)
{
case NodeType.null_:
result = representNull();
break;
case NodeType.merge:
break;
case NodeType.boolean:
result = representBool(data);
break;
case NodeType.integer:
result = representLong(data);
break;
case NodeType.decimal:
result = representReal(data);
break;
case NodeType.binary:
result = representBytes(data);
break;
case NodeType.timestamp:
result = representSysTime(data);
break;
case NodeType.string:
result = representString(data);
break;
case NodeType.mapping:
result = representPairs(data, defaultScalarStyle, defaultCollectionStyle);
break;
case NodeType.sequence:
result = representNodes(data, defaultScalarStyle, defaultCollectionStyle);
break;
case NodeType.invalid:
assert(0);
}
final switch (result.nodeID)
{
case NodeID.scalar:
if (result.scalarStyle == ScalarStyle.invalid)
{
result.scalarStyle = defaultScalarStyle;
}
break;
case NodeID.sequence, NodeID.mapping:
if (defaultCollectionStyle != CollectionStyle.invalid)
{
result.collectionStyle = defaultCollectionStyle;
}
case NodeID.invalid:
}
//Override tag if specified.
if(data.tag_ !is null){result.tag_ = data.tag_;}
//Remember style if this was loaded before.
if(data.scalarStyle != ScalarStyle.invalid)
{
result.scalarStyle = data.scalarStyle;
}
if(data.collectionStyle != CollectionStyle.invalid)
{
result.collectionStyle = data.collectionStyle;
}
return result;
}
@safe unittest
{
// We don't emit yaml merge nodes.
assert(representData(Node(YAMLMerge()), ScalarStyle.invalid, CollectionStyle.invalid) == Node.init);
}
@safe unittest
{
assert(representData(Node(YAMLNull()), ScalarStyle.invalid, CollectionStyle.invalid) == Node("null", "tag:yaml.org,2002:null"));
}
@safe unittest
{
assert(representData(Node(cast(string)null), ScalarStyle.invalid, CollectionStyle.invalid) == Node("null", "tag:yaml.org,2002:null"));
assert(representData(Node("Hello world!"), ScalarStyle.invalid, CollectionStyle.invalid) == Node("Hello world!", "tag:yaml.org,2002:str"));
}
@safe unittest
{
assert(representData(Node(64), ScalarStyle.invalid, CollectionStyle.invalid) == Node("64", "tag:yaml.org,2002:int"));
}
@safe unittest
{
assert(representData(Node(true), ScalarStyle.invalid, CollectionStyle.invalid) == Node("true", "tag:yaml.org,2002:bool"));
assert(representData(Node(false), ScalarStyle.invalid, CollectionStyle.invalid) == Node("false", "tag:yaml.org,2002:bool"));
}
@safe unittest
{
// Float comparison is pretty unreliable...
auto result = representData(Node(1.0), ScalarStyle.invalid, CollectionStyle.invalid);
assert(approxEqual(result.as!string.to!real, 1.0));
assert(result.tag == "tag:yaml.org,2002:float");
assert(representData(Node(real.nan), ScalarStyle.invalid, CollectionStyle.invalid) == Node(".nan", "tag:yaml.org,2002:float"));
assert(representData(Node(real.infinity), ScalarStyle.invalid, CollectionStyle.invalid) == Node(".inf", "tag:yaml.org,2002:float"));
assert(representData(Node(-real.infinity), ScalarStyle.invalid, CollectionStyle.invalid) == Node("-.inf", "tag:yaml.org,2002:float"));
}
@safe unittest
{
assert(representData(Node(SysTime(DateTime(2000, 03, 14, 12, 34, 56), UTC())), ScalarStyle.invalid, CollectionStyle.invalid) == Node("2000-03-14T12:34:56Z", "tag:yaml.org,2002:timestamp"));
}
@safe unittest
{
assert(representData(Node(Node[].init, "tag:yaml.org,2002:set"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node.Pair[].init, "tag:yaml.org,2002:set"));
assert(representData(Node(Node[].init, "tag:yaml.org,2002:seq"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:seq"));
{
auto nodes = [
Node("a"),
Node("b"),
Node("c"),
];
assert(representData(Node(nodes, "tag:yaml.org,2002:set"), ScalarStyle.invalid, CollectionStyle.invalid) ==
Node([
Node.Pair(
Node("a", "tag:yaml.org,2002:str"),
Node("null", "tag:yaml.org,2002:null")
),
Node.Pair(
Node("b", "tag:yaml.org,2002:str"),
Node("null", "tag:yaml.org,2002:null")
),
Node.Pair(
Node("c", "tag:yaml.org,2002:str"),
Node("null", "tag:yaml.org,2002:null")
)
], "tag:yaml.org,2002:set"));
}
{
auto nodes = [
Node("a"),
Node("b"),
Node("c"),
];
assert(representData(Node(nodes, "tag:yaml.org,2002:seq"), ScalarStyle.invalid, CollectionStyle.invalid) ==
Node([
Node("a", "tag:yaml.org,2002:str"),
Node("b", "tag:yaml.org,2002:str"),
Node("c", "tag:yaml.org,2002:str")
], "tag:yaml.org,2002:seq"));
}
}
@safe unittest
{
assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:omap"));
assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:pairs"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node[].init, "tag:yaml.org,2002:pairs"));
assert(representData(Node(Node.Pair[].init, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid) == Node(Node.Pair[].init, "tag:yaml.org,2002:map"));
{
auto nodes = [
Node.Pair("a", "b"),
Node.Pair("a", "c")
];
assertThrown(representData(Node(nodes, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid));
}
// Yeah, this gets ugly really fast.
{
auto nodes = [
Node.Pair("a", "b"),
Node.Pair("a", "c")
];
assert(representData(Node(nodes, "tag:yaml.org,2002:pairs"), ScalarStyle.invalid, CollectionStyle.invalid) ==
Node([
Node(
[Node.Pair(
Node("a", "tag:yaml.org,2002:str"),
Node("b", "tag:yaml.org,2002:str")
)],
"tag:yaml.org,2002:map"),
Node(
[Node.Pair(
Node("a", "tag:yaml.org,2002:str"),
Node("c", "tag:yaml.org,2002:str")
)],
"tag:yaml.org,2002:map"),
], "tag:yaml.org,2002:pairs"));
}
{
auto nodes = [
Node.Pair("a", "b"),
Node.Pair("a", "c")
];
assertThrown(representData(Node(nodes, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid));
}
{
auto nodes = [
Node.Pair("a", "b"),
Node.Pair("c", "d")
];
assert(representData(Node(nodes, "tag:yaml.org,2002:omap"), ScalarStyle.invalid, CollectionStyle.invalid) ==
Node([
Node([
Node.Pair(
Node("a", "tag:yaml.org,2002:str"),
Node("b", "tag:yaml.org,2002:str")
)
], "tag:yaml.org,2002:map"),
Node([
Node.Pair(
Node("c", "tag:yaml.org,2002:str"),
Node("d", "tag:yaml.org,2002:str")
)
], "tag:yaml.org,2002:map"
)], "tag:yaml.org,2002:omap"));
}
{
auto nodes = [
Node.Pair("a", "b"),
Node.Pair("c", "d")
];
assert(representData(Node(nodes, "tag:yaml.org,2002:map"), ScalarStyle.invalid, CollectionStyle.invalid) ==
Node([
Node.Pair(
Node("a", "tag:yaml.org,2002:str"),
Node("b", "tag:yaml.org,2002:str")
),
Node.Pair(
Node("c", "tag:yaml.org,2002:str"),
Node("d", "tag:yaml.org,2002:str")
),
], "tag:yaml.org,2002:map"));
}
}
private:
//Represent a _null _node as a _null YAML value.
Node representNull() @safe
{
return Node("null", "tag:yaml.org,2002:null");
}
//Represent a string _node as a string scalar.
Node representString(const Node node) @safe
{
string value = node.as!string;
return value is null
? Node("null", "tag:yaml.org,2002:null")
: Node(value, "tag:yaml.org,2002:str");
}
//Represent a bytes _node as a binary scalar.
Node representBytes(const Node node) @safe
{
const ubyte[] value = node.as!(ubyte[]);
if(value is null){return Node("null", "tag:yaml.org,2002:null");}
auto newNode = Node(Base64.encode(value).idup, "tag:yaml.org,2002:binary");
newNode.scalarStyle = ScalarStyle.literal;
return newNode;
}
//Represent a bool _node as a bool scalar.
Node representBool(const Node node) @safe
{
return Node(node.as!bool ? "true" : "false", "tag:yaml.org,2002:bool");
}
//Represent a long _node as an integer scalar.
Node representLong(const Node node) @safe
{
return Node(node.as!long.to!string, "tag:yaml.org,2002:int");
}
//Represent a real _node as a floating point scalar.
Node representReal(const Node node) @safe
{
real f = node.as!real;
string value = isNaN(f) ? ".nan":
f == real.infinity ? ".inf":
f == -1.0 * real.infinity ? "-.inf":
{auto a = appender!string();
formattedWrite(a, "%12f", f);
return a.data.strip();}();
return Node(value, "tag:yaml.org,2002:float");
}
//Represent a SysTime _node as a timestamp.
Node representSysTime(const Node node) @safe
{
return Node(node.as!SysTime.toISOExtString(), "tag:yaml.org,2002:timestamp");
}
//Represent a sequence _node as sequence/set.
Node representNodes(const Node node, ScalarStyle defaultScalarStyle, CollectionStyle defaultCollectionStyle) @safe
{
auto nodes = node.as!(Node[]);
if(node.tag_ == "tag:yaml.org,2002:set")
{
//YAML sets are mapping with null values.
Node.Pair[] pairs;
pairs.length = nodes.length;
foreach(idx, key; nodes)
{
pairs[idx] = Node.Pair(key, Node("null", "tag:yaml.org,2002:null"));
}
Node.Pair[] value;
value.length = pairs.length;
auto bestStyle = CollectionStyle.flow;
foreach(idx, pair; pairs)
{
value[idx] = Node.Pair(representData(pair.key, defaultScalarStyle, defaultCollectionStyle), representData(pair.value, defaultScalarStyle, defaultCollectionStyle));
if(value[idx].shouldUseBlockStyle)
{
bestStyle = CollectionStyle.block;
}
}
auto newNode = Node(value, node.tag_);
newNode.collectionStyle = bestStyle;
return newNode;
}
else
{
Node[] value;
value.length = nodes.length;
auto bestStyle = CollectionStyle.flow;
foreach(idx, item; nodes)
{
value[idx] = representData(item, defaultScalarStyle, defaultCollectionStyle);
const isScalar = value[idx].nodeID == NodeID.scalar;
const s = value[idx].scalarStyle;
if(!isScalar || (s != ScalarStyle.invalid && s != ScalarStyle.plain))
{
bestStyle = CollectionStyle.block;
}
}
auto newNode = Node(value, "tag:yaml.org,2002:seq");
newNode.collectionStyle = bestStyle;
return newNode;
}
}
bool shouldUseBlockStyle(const Node value) @safe
{
const isScalar = value.nodeID == NodeID.scalar;
const s = value.scalarStyle;
return (!isScalar || (s != ScalarStyle.invalid && s != ScalarStyle.plain));
}
bool shouldUseBlockStyle(const Node.Pair value) @safe
{
const keyScalar = value.key.nodeID == NodeID.scalar;
const valScalar = value.value.nodeID == NodeID.scalar;
const keyStyle = value.key.scalarStyle;
const valStyle = value.value.scalarStyle;
if(!keyScalar ||
(keyStyle != ScalarStyle.invalid && keyStyle != ScalarStyle.plain))
{
return true;
}
if(!valScalar ||
(valStyle != ScalarStyle.invalid && valStyle != ScalarStyle.plain))
{
return true;
}
return false;
}
//Represent a mapping _node as map/ordered map/pairs.
Node representPairs(const Node node, ScalarStyle defaultScalarStyle, CollectionStyle defaultCollectionStyle) @safe
{
auto pairs = node.as!(Node.Pair[]);
bool hasDuplicates(const Node.Pair[] pairs) @safe
{
//TODO this should be replaced by something with deterministic memory allocation.
auto keys = redBlackTree!Node();
foreach(pair; pairs)
{
if(pair.key in keys){return true;}
keys.insert(pair.key);
}
return false;
}
Node[] mapToSequence(const Node.Pair[] pairs) @safe
{
Node[] nodes;
nodes.length = pairs.length;
foreach(idx, pair; pairs)
{
Node.Pair value;
auto bestStyle = value.shouldUseBlockStyle ? CollectionStyle.block : CollectionStyle.flow;
value = Node.Pair(representData(pair.key, defaultScalarStyle, defaultCollectionStyle), representData(pair.value, defaultScalarStyle, defaultCollectionStyle));
auto newNode = Node([value], "tag:yaml.org,2002:map");
newNode.collectionStyle = bestStyle;
nodes[idx] = newNode;
}
return nodes;
}
if(node.tag_ == "tag:yaml.org,2002:omap")
{
enforce(!hasDuplicates(pairs),
new RepresenterException("Duplicate entry in an ordered map"));
auto sequence = mapToSequence(pairs);
Node[] value;
value.length = sequence.length;
auto bestStyle = CollectionStyle.flow;
foreach(idx, item; sequence)
{
value[idx] = representData(item, defaultScalarStyle, defaultCollectionStyle);
if(value[idx].shouldUseBlockStyle)
{
bestStyle = CollectionStyle.block;
}
}
auto newNode = Node(value, node.tag_);
newNode.collectionStyle = bestStyle;
return newNode;
}
else if(node.tag_ == "tag:yaml.org,2002:pairs")
{
auto sequence = mapToSequence(pairs);
Node[] value;
value.length = sequence.length;
auto bestStyle = CollectionStyle.flow;
foreach(idx, item; sequence)
{
value[idx] = representData(item, defaultScalarStyle, defaultCollectionStyle);
if(value[idx].shouldUseBlockStyle)
{
bestStyle = CollectionStyle.block;
}
}
auto newNode = Node(value, node.tag_);
newNode.collectionStyle = bestStyle;
return newNode;
}
else
{
enforce(!hasDuplicates(pairs),
new RepresenterException("Duplicate entry in an unordered map"));
Node.Pair[] value;
value.length = pairs.length;
auto bestStyle = CollectionStyle.flow;
foreach(idx, pair; pairs)
{
value[idx] = Node.Pair(representData(pair.key, defaultScalarStyle, defaultCollectionStyle), representData(pair.value, defaultScalarStyle, defaultCollectionStyle));
if(value[idx].shouldUseBlockStyle)
{
bestStyle = CollectionStyle.block;
}
}
auto newNode = Node(value, "tag:yaml.org,2002:map");
newNode.collectionStyle = bestStyle;
return newNode;
}
}