Skip to content

Commit 365663a

Browse files
committed
Merge branch 'merge-operator' into update-lib-and-merge-operator
* merge-operator: Fix merge operator support (that can be visible by iterating through the node) Fix merge-key handling in case the dictionary contains a sub-dictionary Adding support for handling YAML Merge Key (jbeder#41)
2 parents e5ba551 + da04fa3 commit 365663a

File tree

4 files changed

+116
-3
lines changed

4 files changed

+116
-3
lines changed

include/yaml-cpp/exceptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ const char* const INVALID_ANCHOR = "invalid anchor";
8787
const char* const INVALID_ALIAS = "invalid alias";
8888
const char* const INVALID_TAG = "invalid tag";
8989
const char* const BAD_FILE = "bad file";
90+
const char* const MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS =
91+
"merge key needs either single map or sequence of maps";
9092

9193
template <typename T>
9294
inline const std::string KEY_NOT_FOUND_WITH_KEY(

src/nodebuilder.cpp

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <algorithm>
12
#include <cassert>
23

34
#include "nodebuilder.h"
@@ -15,6 +16,7 @@ NodeBuilder::NodeBuilder()
1516
m_stack{},
1617
m_anchors{},
1718
m_keys{},
19+
m_mergeDicts{},
1820
m_mapDepth(0) {
1921
m_anchors.push_back(nullptr); // since the anchors start at 1
2022
}
@@ -69,11 +71,38 @@ void NodeBuilder::OnMapStart(const Mark& mark, const std::string& tag,
6971
node.set_tag(tag);
7072
node.set_style(style);
7173
m_mapDepth++;
74+
m_mergeDicts.emplace_back();
75+
}
76+
77+
void MergeMapCollection(detail::node& map_to, detail::node& map_from,
78+
detail::shared_memory_holder& pMemory) {
79+
const detail::node& const_map_to = map_to;
80+
for (auto j = map_from.begin(); j != map_from.end(); j++) {
81+
const auto & from_key = j->first;
82+
/// NOTE: const_map_to.get(*j->first) cannot be used here, since it
83+
/// compares only the shared_ptr's, while we need to compare the key
84+
/// itself.
85+
///
86+
/// NOTE: get() also iterates over elements
87+
bool found = std::any_of(const_map_to.begin(), const_map_to.end(), [&](const detail::node_iterator_value<const detail::node> & kv)
88+
{
89+
const auto & key_node = kv.first;
90+
return key_node->scalar() == from_key->scalar();
91+
});
92+
if (!found)
93+
map_to.insert(*from_key, *j->second, pMemory);
94+
}
7295
}
7396

7497
void NodeBuilder::OnMapEnd() {
7598
assert(m_mapDepth > 0);
99+
detail::node& collection = *m_stack.back();
100+
auto& toMerge = *m_mergeDicts.rbegin();
101+
for (detail::node* n : toMerge) {
102+
MergeMapCollection(collection, *n, m_pMemory);
103+
}
76104
m_mapDepth--;
105+
m_mergeDicts.pop_back();
77106
Pop();
78107
}
79108

@@ -107,15 +136,40 @@ void NodeBuilder::Pop() {
107136
m_stack.pop_back();
108137

109138
detail::node& collection = *m_stack.back();
110-
111139
if (collection.type() == NodeType::Sequence) {
112140
collection.push_back(node, m_pMemory);
113141
} else if (collection.type() == NodeType::Map) {
114142
assert(!m_keys.empty());
115143
PushedKey& key = m_keys.back();
116144
if (key.second) {
117-
collection.insert(*key.first, node, m_pMemory);
118-
m_keys.pop_back();
145+
detail::node& nk = *key.first;
146+
if (nk.type() == NodeType::Scalar &&
147+
((nk.tag() == "tag:yaml.org,2002:merge" && nk.scalar() == "<<") ||
148+
(nk.tag() == "?" && nk.scalar() == "<<"))) {
149+
if (node.type() == NodeType::Map) {
150+
m_mergeDicts.rbegin()->emplace_back(&node);
151+
m_keys.pop_back();
152+
} else if (node.type() == NodeType::Sequence) {
153+
for (auto i = node.begin(); i != node.end(); i++) {
154+
auto v = *i;
155+
if ((*v).type() == NodeType::Map) {
156+
m_mergeDicts.rbegin()->emplace_back(&(*v));
157+
} else {
158+
throw ParserException(
159+
node.mark(),
160+
ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS);
161+
}
162+
}
163+
m_keys.pop_back();
164+
} else {
165+
throw ParserException(
166+
node.mark(),
167+
ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS);
168+
}
169+
} else {
170+
collection.insert(*key.first, node, m_pMemory);
171+
m_keys.pop_back();
172+
}
119173
} else {
120174
key.second = true;
121175
}

src/nodebuilder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class NodeBuilder : public EventHandler {
6767

6868
using PushedKey = std::pair<detail::node*, bool>;
6969
std::vector<PushedKey> m_keys;
70+
std::vector<Nodes> m_mergeDicts;
7071
std::size_t m_mapDepth;
7172
};
7273
} // namespace YAML

test/integration/load_node_test.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "yaml-cpp/yaml.h" // IWYU pragma: keep
22

33
#include "gtest/gtest.h"
4+
#include <algorithm>
45

56
namespace YAML {
67
namespace {
@@ -173,6 +174,61 @@ TEST(LoadNodeTest, CloneAlias) {
173174
EXPECT_EQ(clone[0], clone);
174175
}
175176

177+
TEST(LoadNodeTest, MergeKeyA) {
178+
Node node = Load(
179+
"{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: "
180+
"&stuff { << : *foo, b : 3} }");
181+
EXPECT_EQ(NodeType::Map, node["z"].Type());
182+
EXPECT_FALSE(node["z"]["<<"]);
183+
EXPECT_EQ(1, node["z"]["a"].as<int>());
184+
EXPECT_EQ(3, node["z"]["b"].as<int>());
185+
EXPECT_EQ(1, node["z"]["c"].as<int>());
186+
}
187+
188+
TEST(LoadNodeTest, MergeKeyAIterator) {
189+
Node node = Load(
190+
"{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: "
191+
"&stuff { << : *foo, b : 3} }");
192+
EXPECT_EQ(NodeType::Map, node["z"].Type());
193+
194+
const auto& z = node["z"];
195+
size_t z_b_keys = std::count_if(z.begin(), z.end(), [&](const detail::iterator_value & kv)
196+
{
197+
return kv.first.as<std::string>() == "b";
198+
});
199+
ASSERT_EQ(z_b_keys, 1);
200+
}
201+
202+
TEST(LoadNodeTest, MergeKeyB) {
203+
Node node = Load(
204+
"{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: "
205+
"&stuff { << : *foo, b : 3}, w: { << : [*stuff, *bar], c: 4 }, v: { '<<' "
206+
": *foo } , u : {!!merge << : *bar}, t: {!!merge << : *bar, h: 3} }");
207+
EXPECT_EQ(NodeType::Map, node["z"].Type());
208+
EXPECT_EQ(NodeType::Map, node["w"].Type());
209+
EXPECT_FALSE(node["z"]["<<"]);
210+
EXPECT_EQ(1, node["z"]["a"].as<int>());
211+
EXPECT_EQ(3, node["z"]["b"].as<int>());
212+
EXPECT_EQ(1, node["z"]["c"].as<int>());
213+
214+
EXPECT_EQ(1, node["w"]["a"].as<int>());
215+
EXPECT_EQ(3, node["w"]["b"].as<int>());
216+
EXPECT_EQ(4, node["w"]["c"].as<int>());
217+
EXPECT_EQ(2, node["w"]["d"].as<int>());
218+
EXPECT_EQ(2, node["w"]["e"].as<int>());
219+
EXPECT_EQ(2, node["w"]["f"].as<int>());
220+
221+
EXPECT_TRUE(node["v"]["<<"]);
222+
EXPECT_EQ(1, node["v"]["<<"]["a"].as<int>());
223+
224+
EXPECT_FALSE(node["u"]["<<"]);
225+
EXPECT_EQ(2, node["u"]["d"].as<int>());
226+
227+
EXPECT_FALSE(node["t"]["<<"]);
228+
EXPECT_EQ(2, node["t"]["d"].as<int>());
229+
EXPECT_EQ(3, node["t"]["h"].as<int>());
230+
}
231+
176232
TEST(LoadNodeTest, ForceInsertIntoMap) {
177233
Node node;
178234
node["a"] = "b";

0 commit comments

Comments
 (0)