Adding support for handling YAML Merge Key (#41)
Support for YAML Merge keys ( <<: [*dict1, *dict2] ) is added. The merge key is a specific scalar with value << (and tag !!merge) that implies that during node construction, the map (or sequence of maps) are merged into the current map. The priority rules are that each key from maps within the value associated with << are added iff the key is not yet present in the current map (and first map gets higher priority). Test cases have been added accordingly.
This commit is contained in:
parent
51adc5f739
commit
6bbc603b22
@ -87,6 +87,8 @@ const char* const INVALID_ANCHOR = "invalid anchor";
|
||||
const char* const INVALID_ALIAS = "invalid alias";
|
||||
const char* const INVALID_TAG = "invalid tag";
|
||||
const char* const BAD_FILE = "bad file";
|
||||
const char* const MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS =
|
||||
"merge key needs either single map or sequence of maps";
|
||||
|
||||
template <typename T>
|
||||
inline const std::string KEY_NOT_FOUND_WITH_KEY(
|
||||
|
||||
@ -15,6 +15,7 @@ NodeBuilder::NodeBuilder()
|
||||
m_stack{},
|
||||
m_anchors{},
|
||||
m_keys{},
|
||||
m_mergeDicts{},
|
||||
m_mapDepth(0) {
|
||||
m_anchors.push_back(nullptr); // since the anchors start at 1
|
||||
}
|
||||
@ -71,8 +72,24 @@ void NodeBuilder::OnMapStart(const Mark& mark, const std::string& tag,
|
||||
m_mapDepth++;
|
||||
}
|
||||
|
||||
void MergeMapCollection(detail::node& map_to, detail::node& map_from,
|
||||
detail::shared_memory_holder& pMemory) {
|
||||
const detail::node& const_map_to = map_to;
|
||||
for (auto j = map_from.begin(); j != map_from.end(); j++) {
|
||||
detail::node* s = const_map_to.get(*j->first, pMemory);
|
||||
if (s == nullptr) {
|
||||
map_to.insert(*j->first, *j->second, pMemory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeBuilder::OnMapEnd() {
|
||||
assert(m_mapDepth > 0);
|
||||
detail::node& collection = *m_stack.back();
|
||||
for (detail::node* n : m_mergeDicts) {
|
||||
MergeMapCollection(collection, *n, m_pMemory);
|
||||
}
|
||||
m_mergeDicts.clear();
|
||||
m_mapDepth--;
|
||||
Pop();
|
||||
}
|
||||
@ -107,15 +124,40 @@ void NodeBuilder::Pop() {
|
||||
m_stack.pop_back();
|
||||
|
||||
detail::node& collection = *m_stack.back();
|
||||
|
||||
if (collection.type() == NodeType::Sequence) {
|
||||
collection.push_back(node, m_pMemory);
|
||||
} else if (collection.type() == NodeType::Map) {
|
||||
assert(!m_keys.empty());
|
||||
PushedKey& key = m_keys.back();
|
||||
if (key.second) {
|
||||
collection.insert(*key.first, node, m_pMemory);
|
||||
m_keys.pop_back();
|
||||
detail::node& nk = *key.first;
|
||||
if (nk.type() == NodeType::Scalar &&
|
||||
((nk.tag() == "tag:yaml.org,2002:merge" && nk.scalar() == "<<") ||
|
||||
(nk.tag() == "?" && nk.scalar() == "<<"))) {
|
||||
if (node.type() == NodeType::Map) {
|
||||
m_mergeDicts.emplace_back(&node);
|
||||
m_keys.pop_back();
|
||||
} else if (node.type() == NodeType::Sequence) {
|
||||
for (auto i = node.begin(); i != node.end(); i++) {
|
||||
auto v = *i;
|
||||
if ((*v).type() == NodeType::Map) {
|
||||
m_mergeDicts.emplace_back(&(*v));
|
||||
} else {
|
||||
throw ParserException(
|
||||
node.mark(),
|
||||
ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS);
|
||||
}
|
||||
}
|
||||
m_keys.pop_back();
|
||||
} else {
|
||||
throw ParserException(
|
||||
node.mark(),
|
||||
ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS);
|
||||
}
|
||||
} else {
|
||||
collection.insert(*key.first, node, m_pMemory);
|
||||
m_keys.pop_back();
|
||||
}
|
||||
} else {
|
||||
key.second = true;
|
||||
}
|
||||
|
||||
@ -67,6 +67,7 @@ class NodeBuilder : public EventHandler {
|
||||
|
||||
using PushedKey = std::pair<detail::node*, bool>;
|
||||
std::vector<PushedKey> m_keys;
|
||||
Nodes m_mergeDicts;
|
||||
std::size_t m_mapDepth;
|
||||
};
|
||||
} // namespace YAML
|
||||
|
||||
@ -173,6 +173,43 @@ TEST(LoadNodeTest, CloneAlias) {
|
||||
EXPECT_EQ(clone[0], clone);
|
||||
}
|
||||
|
||||
TEST(LoadNodeTest, MergeKeyA) {
|
||||
Node node = Load(
|
||||
"{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: "
|
||||
"&stuff { << : *foo, b : 3} }");
|
||||
EXPECT_EQ(NodeType::Map, node["z"].Type());
|
||||
EXPECT_FALSE(node["z"]["<<"]);
|
||||
EXPECT_EQ(1, node["z"]["a"].as<int>());
|
||||
EXPECT_EQ(3, node["z"]["b"].as<int>());
|
||||
EXPECT_EQ(1, node["z"]["c"].as<int>());
|
||||
}
|
||||
|
||||
TEST(LoadNodeTest, MergeKeyB) {
|
||||
Node node = Load(
|
||||
"{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: "
|
||||
"&stuff { << : *foo, b : 3}, w: { << : [*stuff, *bar], c: 4 }, v: { '<<' "
|
||||
": *foo } , u : {!!merge << : *bar} }");
|
||||
EXPECT_EQ(NodeType::Map, node["z"].Type());
|
||||
EXPECT_EQ(NodeType::Map, node["w"].Type());
|
||||
EXPECT_FALSE(node["z"]["<<"]);
|
||||
EXPECT_EQ(1, node["z"]["a"].as<int>());
|
||||
EXPECT_EQ(3, node["z"]["b"].as<int>());
|
||||
EXPECT_EQ(1, node["z"]["c"].as<int>());
|
||||
|
||||
EXPECT_EQ(1, node["w"]["a"].as<int>());
|
||||
EXPECT_EQ(3, node["w"]["b"].as<int>());
|
||||
EXPECT_EQ(4, node["w"]["c"].as<int>());
|
||||
EXPECT_EQ(2, node["w"]["d"].as<int>());
|
||||
EXPECT_EQ(2, node["w"]["e"].as<int>());
|
||||
EXPECT_EQ(2, node["w"]["f"].as<int>());
|
||||
|
||||
EXPECT_TRUE(node["v"]["<<"]);
|
||||
EXPECT_EQ(1, node["v"]["<<"]["a"].as<int>());
|
||||
|
||||
EXPECT_FALSE(node["u"]["<<"]);
|
||||
EXPECT_EQ(2, node["u"]["d"].as<int>());
|
||||
}
|
||||
|
||||
TEST(LoadNodeTest, ForceInsertIntoMap) {
|
||||
Node node;
|
||||
node["a"] = "b";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user