Skip to content

Commit 5d2dfe8

Browse files
committed
Add support for merge keys
Merge keys are specified here[1] for YAML 1.1. While not part of the YAML 1.2 specification, they're very useful and are supported in other implementations[2][3][4] that target 1.2. Support for merge keys is optional and disabled by default. It can be enabled by defining YAML_CPP_SUPPORT_MERGE_KEYS, either directly or by setting the CMake option YAML_CPP_SUPPORT_MERGE_KEYS=ON. [1]: http://yaml.org/type/merge.html [2]: https://github.com/go-yaml/yaml [3]: https://github.com/ruby/psych [4]: https://bitbucket.org/ruamel/yaml
1 parent 1698b47 commit 5d2dfe8

4 files changed

Lines changed: 81 additions & 2 deletions

File tree

.travis.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ os:
55
compiler:
66
- clang
77
- gcc
8+
env:
9+
- YAML_CPP_SUPPORT_MERGE_KEYS=ON
10+
- YAML_CPP_SUPPORT_MERGE_KEYS=OFF
811
before_install:
912
- |
1013
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
@@ -17,7 +20,7 @@ before_install:
1720
before_script:
1821
- mkdir build
1922
- cd build
20-
- cmake ..
23+
- cmake .. -DYAML_CPP_SUPPORT_MERGE_KEYS=$YAML_CPP_SUPPORT_MERGE_KEYS
2124
script:
2225
- make
2326
- test/run-tests

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ enable_testing()
4141
option(YAML_CPP_BUILD_TESTS "Enable testing" ON)
4242
option(YAML_CPP_BUILD_TOOLS "Enable parse tools" ON)
4343
option(YAML_CPP_BUILD_CONTRIB "Enable contrib stuff in library" ON)
44+
option(YAML_CPP_SUPPORT_MERGE_KEYS "Support YAML merge keys ('<<') in yaml-cpp's executable targets. Use '#define YAML_CPP_SUPPORT_MERGE_KEYS' instead when linking from another project." OFF)
4445

4546
## Build options
4647
# --> General
@@ -97,6 +98,10 @@ else()
9798
add_definitions(-DYAML_CPP_NO_CONTRIB)
9899
endif()
99100

101+
if (YAML_CPP_SUPPORT_MERGE_KEYS)
102+
add_definitions(-DYAML_CPP_SUPPORT_MERGE_KEYS)
103+
endif()
104+
100105
set(library_sources
101106
${sources}
102107
${public_headers}

include/yaml-cpp/node/detail/node.h

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,23 @@ class node {
122122
// NOTE: this returns a non-const node so that the top-level Node can wrap
123123
// it, and returns a pointer so that it can be NULL (if there is no such
124124
// key).
125-
return static_cast<const node_ref&>(*m_pRef).get(key, pMemory);
125+
node* value = static_cast<const node_ref&>(*m_pRef).get(key, pMemory);
126+
#ifdef YAML_CPP_SUPPORT_MERGE_KEYS
127+
if (!value || value->type() == NodeType::Undefined) {
128+
return get_value_from_merge_key(key, value, pMemory);
129+
}
130+
#endif
131+
return value;
126132
}
127133
template <typename Key>
128134
node& get(const Key& key, shared_memory_holder pMemory) {
129135
node& value = m_pRef->get(key, pMemory);
130136
value.add_dependency(*this);
137+
#ifdef YAML_CPP_SUPPORT_MERGE_KEYS
138+
if (value.type() == NodeType::Undefined) {
139+
return *get_value_from_merge_key(key, &value, pMemory);
140+
}
141+
#endif
131142
return value;
132143
}
133144
template <typename Key>
@@ -159,6 +170,33 @@ class node {
159170
}
160171

161172
private:
173+
#ifdef YAML_CPP_SUPPORT_MERGE_KEYS
174+
template <typename Key>
175+
inline node* get_value_from_merge_key(const Key& key, node* currentValue,
176+
shared_memory_holder pMemory) const {
177+
node* mergeValue =
178+
static_cast<const node_ref&>(*m_pRef).get(std::string("<<"), pMemory);
179+
if (!mergeValue) {
180+
return currentValue;
181+
}
182+
if (mergeValue->type() == NodeType::Map) {
183+
return &mergeValue->get(key, pMemory);
184+
}
185+
if (mergeValue->type() == NodeType::Sequence) {
186+
for (const_node_iterator it = mergeValue->begin();
187+
it != mergeValue->end(); ++it) {
188+
if (it->pNode && it->pNode->type() == NodeType::Map) {
189+
node* value = it->pNode->get(key, pMemory);
190+
if (value && value->type() != NodeType::Undefined) {
191+
return value;
192+
}
193+
}
194+
}
195+
}
196+
return currentValue;
197+
}
198+
#endif
199+
162200
shared_node_ref m_pRef;
163201
typedef std::set<node*> nodes;
164202
nodes m_dependencies;

test/integration/load_node_test.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,39 @@ TEST(LoadNodeTest, DereferenceIteratorError) {
185185
EXPECT_THROW(node.begin()->begin()->Type(), InvalidNode);
186186
}
187187

188+
#ifdef YAML_CPP_SUPPORT_MERGE_KEYS
189+
TEST(NodeTest, MergeKeyScalarSupport) {
190+
Node node = Load("{<<: {a: 1}}");
191+
ASSERT_FALSE(!node["a"]);
192+
EXPECT_EQ(1, node["a"].as<int>());
193+
}
194+
195+
TEST(NodeTest, MergeKeyExistingKey) {
196+
Node node = Load("{a: 1, <<: {a: 2}}");
197+
ASSERT_FALSE(!node["a"]);
198+
EXPECT_EQ(1, node["a"].as<int>());
199+
}
200+
201+
TEST(NodeTest, MergeKeySequenceSupport) {
202+
Node node = Load("<<: [{a: 1}, {a: 2, b: 3}]");
203+
ASSERT_FALSE(!node["a"]);
204+
ASSERT_FALSE(!node["b"]);
205+
EXPECT_EQ(1, node["a"].as<int>());
206+
EXPECT_EQ(3, node["b"].as<int>());
207+
}
208+
209+
TEST(NodeTest, NestedMergeKeys) {
210+
Node node = Load("{<<: {<<: {a: 1}}}");
211+
ASSERT_FALSE(!node["a"]);
212+
EXPECT_EQ(1, node["a"].as<int>());
213+
}
214+
#else
215+
TEST(NodeTest, MergeKeySupport) {
216+
Node node = Load("{<<: {a: 1}}");
217+
ASSERT_FALSE(node["a"]);
218+
}
219+
#endif
220+
188221
TEST(NodeTest, EmitEmptyNode) {
189222
Node node;
190223
Emitter emitter;

0 commit comments

Comments
 (0)