diff --git a/.travis.yml b/.travis.yml index d0b6a04ef..32f07973e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,9 @@ os: compiler: - clang - gcc +env: + - YAML_CPP_SUPPORT_MERGE_KEYS=ON + - YAML_CPP_SUPPORT_MERGE_KEYS=OFF before_install: - | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then @@ -17,7 +20,7 @@ before_install: before_script: - mkdir build - cd build - - cmake .. + - cmake .. -DYAML_CPP_SUPPORT_MERGE_KEYS=$YAML_CPP_SUPPORT_MERGE_KEYS script: - make - test/run-tests diff --git a/CMakeLists.txt b/CMakeLists.txt index 715c8466f..a6615d8ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ enable_testing() option(YAML_CPP_BUILD_TESTS "Enable testing" ON) option(YAML_CPP_BUILD_TOOLS "Enable parse tools" ON) option(YAML_CPP_BUILD_CONTRIB "Enable contrib stuff in library" ON) +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) ## Build options # --> General @@ -97,6 +98,10 @@ else() add_definitions(-DYAML_CPP_NO_CONTRIB) endif() +if (YAML_CPP_SUPPORT_MERGE_KEYS) + add_definitions(-DYAML_CPP_SUPPORT_MERGE_KEYS) +endif() + set(library_sources ${sources} ${public_headers} diff --git a/include/yaml-cpp/node/detail/node.h b/include/yaml-cpp/node/detail/node.h index 8a776f62a..a22f56c73 100644 --- a/include/yaml-cpp/node/detail/node.h +++ b/include/yaml-cpp/node/detail/node.h @@ -122,12 +122,23 @@ class node { // NOTE: this returns a non-const node so that the top-level Node can wrap // it, and returns a pointer so that it can be NULL (if there is no such // key). - return static_cast(*m_pRef).get(key, pMemory); + node* value = static_cast(*m_pRef).get(key, pMemory); +#ifdef YAML_CPP_SUPPORT_MERGE_KEYS + if (!value || value->type() == NodeType::Undefined) { + return get_value_from_merge_key(key, value, pMemory); + } +#endif + return value; } template node& get(const Key& key, shared_memory_holder pMemory) { node& value = m_pRef->get(key, pMemory); value.add_dependency(*this); +#ifdef YAML_CPP_SUPPORT_MERGE_KEYS + if (value.type() == NodeType::Undefined) { + return *get_value_from_merge_key(key, &value, pMemory); + } +#endif return value; } template @@ -159,6 +170,33 @@ class node { } private: +#ifdef YAML_CPP_SUPPORT_MERGE_KEYS + template + inline node* get_value_from_merge_key(const Key& key, node* currentValue, + shared_memory_holder pMemory) const { + node* mergeValue = + static_cast(*m_pRef).get(std::string("<<"), pMemory); + if (!mergeValue) { + return currentValue; + } + if (mergeValue->type() == NodeType::Map) { + return &mergeValue->get(key, pMemory); + } + if (mergeValue->type() == NodeType::Sequence) { + for (const_node_iterator it = mergeValue->begin(); + it != mergeValue->end(); ++it) { + if (it->pNode && it->pNode->type() == NodeType::Map) { + node* value = it->pNode->get(key, pMemory); + if (value && value->type() != NodeType::Undefined) { + return value; + } + } + } + } + return currentValue; + } +#endif + shared_node_ref m_pRef; typedef std::set nodes; nodes m_dependencies; diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index 02bb8fe58..b05d9174a 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -185,6 +185,57 @@ TEST(LoadNodeTest, DereferenceIteratorError) { EXPECT_THROW(node.begin()->begin()->Type(), InvalidNode); } +#ifdef YAML_CPP_SUPPORT_MERGE_KEYS +TEST(NodeTest, MergeKeyScalarSupport) { + Node node = Load("{<<: {a: 1}}"); + ASSERT_FALSE(!node["a"]); + EXPECT_EQ(1, node["a"].as()); +} + +TEST(NodeTest, MergeKeyExistingKey) { + Node node = Load("{a: 1, <<: {a: 2}}"); + ASSERT_FALSE(!node["a"]); + EXPECT_EQ(1, node["a"].as()); +} + +TEST(NodeTest, MergeKeySequenceSupport) { + Node node = Load("<<: [{a: 1}, {a: 2, b: 3}]"); + ASSERT_FALSE(!node["a"]); + ASSERT_FALSE(!node["b"]); + EXPECT_EQ(1, node["a"].as()); + EXPECT_EQ(3, node["b"].as()); +} + +TEST(NodeTest, NestedMergeKeys) { + Node node = Load("{<<: {<<: {a: 1}}}"); + ASSERT_FALSE(!node["a"]); + EXPECT_EQ(1, node["a"].as()); +} + +TEST(NodeTest, AnchorAndMergeKey) { + Node node = YAML::Load(R"( + a_root: &root_anchor + key1: value1 + key2: value2 + b_child: + <<: *root_anchor + key2: value2_override + )"); + + ASSERT_FALSE(!node["a_root"]); + ASSERT_FALSE(!node["b_child"]); + EXPECT_EQ("value1", node["a_root"]["key1"].as()); + EXPECT_EQ("value2", node["a_root"]["key2"].as()); + EXPECT_EQ("value1", node["b_child"]["key1"].as()); + EXPECT_EQ("value2_override", node["b_child"]["key2"].as()); +} +#else +TEST(NodeTest, MergeKeySupport) { + Node node = Load("{<<: {a: 1}}"); + ASSERT_FALSE(node["a"]); +} +#endif + TEST(NodeTest, EmitEmptyNode) { Node node; Emitter emitter;