From 5a312d5bf974d5d5e0d4ccb56da5e1696163f19f Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Tue, 31 Mar 2015 12:32:09 +0100 Subject: [PATCH] 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][5][6][7][8][9][10] 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://pypi.org/project/ruamel.yaml/ [5]: https://github.com/pantoniou/libfyaml [6]: https://github.com/aaubry/YamlDotNet [7]: https://github.com/goccy/go-yaml [8]: https://github.com/nodeca/js-yaml [9]: https://github.com/eemeli/yaml [10]: https://symfony.com/doc/current/components/yaml.html --- CMakeLists.txt | 6 ++-- include/yaml-cpp/node/detail/node.h | 40 +++++++++++++++++++++- test/CMakeLists.txt | 3 ++ test/integration/load_node_test.cpp | 51 +++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ae92e2..befc5f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,13 +26,14 @@ option(YAML_CPP_BUILD_TOOLS "Enable parse tools" ON) option(YAML_BUILD_SHARED_LIBS "Build yaml-cpp shared library" ${BUILD_SHARED_LIBS}) option(YAML_CPP_INSTALL "Enable generation of yaml-cpp install targets" ${YAML_CPP_MAIN_PROJECT}) option(YAML_CPP_FORMAT_SOURCE "Format source" 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) cmake_dependent_option(YAML_CPP_BUILD_TESTS "Enable yaml-cpp tests" OFF "BUILD_TESTING;YAML_CPP_MAIN_PROJECT" OFF) cmake_dependent_option(YAML_MSVC_SHARED_RT "MSVC: Build yaml-cpp with shared runtime libs (/MD)" ON "CMAKE_SYSTEM_NAME MATCHES Windows" OFF) - + if (YAML_CPP_FORMAT_SOURCE) find_program(YAML_CPP_CLANG_FORMAT_EXE NAMES clang-format) endif() @@ -127,7 +128,8 @@ target_compile_definitions(yaml-cpp $<$>:YAML_CPP_STATIC_DEFINE> PRIVATE $<${build-windows-dll}:${PROJECT_NAME}_DLL> - $<$>:YAML_CPP_NO_CONTRIB>) + $<$>:YAML_CPP_NO_CONTRIB> + $<$:YAML_CPP_SUPPORT_MERGE_KEYS>) target_sources(yaml-cpp PRIVATE diff --git a/include/yaml-cpp/node/detail/node.h b/include/yaml-cpp/node/detail/node.h index acf60ff..eecdbd6 100644 --- a/include/yaml-cpp/node/detail/node.h +++ b/include/yaml-cpp/node/detail/node.h @@ -128,12 +128,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 nullptr (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 @@ -165,6 +176,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; using nodes = std::set; nodes m_dependencies; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c9e7f04..87797d2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,6 +34,9 @@ target_compile_options(yaml-cpp-tests PRIVATE $<$:-Wno-c99-extensions -Wno-variadic-macros -Wno-sign-compare> $<$:-Wno-variadic-macros -Wno-sign-compare -Wno-narrowing>) +target_compile_definitions(yaml-cpp-tests + PRIVATE + $<$:YAML_CPP_SUPPORT_MERGE_KEYS>) target_link_libraries(yaml-cpp-tests PRIVATE Threads::Threads diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index 9d0c790..8200d38 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -224,6 +224,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;