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
This commit is contained in:
Oliver Hamlet
2023-09-30 14:16:31 +01:00
committed by Oliver Hamlet
parent 37f1b8b8c9
commit 5a312d5bf9
4 changed files with 97 additions and 3 deletions
+4 -2
View File
@@ -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
$<$<NOT:$<BOOL:${YAML_BUILD_SHARED_LIBS}>>:YAML_CPP_STATIC_DEFINE>
PRIVATE
$<${build-windows-dll}:${PROJECT_NAME}_DLL>
$<$<NOT:$<BOOL:${YAML_CPP_BUILD_CONTRIB}>>:YAML_CPP_NO_CONTRIB>)
$<$<NOT:$<BOOL:${YAML_CPP_BUILD_CONTRIB}>>:YAML_CPP_NO_CONTRIB>
$<$<BOOL:${YAML_CPP_SUPPORT_MERGE_KEYS}>:YAML_CPP_SUPPORT_MERGE_KEYS>)
target_sources(yaml-cpp
PRIVATE
+39 -1
View File
@@ -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<const node_ref&>(*m_pRef).get(key, pMemory);
node* value = static_cast<const node_ref&>(*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 <typename Key>
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 <typename Key>
@@ -165,6 +176,33 @@ class node {
}
private:
#ifdef YAML_CPP_SUPPORT_MERGE_KEYS
template <typename Key>
inline node* get_value_from_merge_key(const Key& key, node* currentValue,
shared_memory_holder pMemory) const {
node* mergeValue =
static_cast<const node_ref&>(*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<node*, less>;
nodes m_dependencies;
+3
View File
@@ -34,6 +34,9 @@ target_compile_options(yaml-cpp-tests
PRIVATE
$<$<CXX_COMPILER_ID:Clang>:-Wno-c99-extensions -Wno-variadic-macros -Wno-sign-compare>
$<$<CXX_COMPILER_ID:GNU>:-Wno-variadic-macros -Wno-sign-compare -Wno-narrowing>)
target_compile_definitions(yaml-cpp-tests
PRIVATE
$<$<BOOL:${YAML_CPP_SUPPORT_MERGE_KEYS}>:YAML_CPP_SUPPORT_MERGE_KEYS>)
target_link_libraries(yaml-cpp-tests
PRIVATE
Threads::Threads
+51
View File
@@ -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<int>());
}
TEST(NodeTest, MergeKeyExistingKey) {
Node node = Load("{a: 1, <<: {a: 2}}");
ASSERT_FALSE(!node["a"]);
EXPECT_EQ(1, node["a"].as<int>());
}
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<int>());
EXPECT_EQ(3, node["b"].as<int>());
}
TEST(NodeTest, NestedMergeKeys) {
Node node = Load("{<<: {<<: {a: 1}}}");
ASSERT_FALSE(!node["a"]);
EXPECT_EQ(1, node["a"].as<int>());
}
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<std::string>());
EXPECT_EQ("value2", node["a_root"]["key2"].as<std::string>());
EXPECT_EQ("value1", node["b_child"]["key1"].as<std::string>());
EXPECT_EQ("value2_override", node["b_child"]["key2"].as<std::string>());
}
#else
TEST(NodeTest, MergeKeySupport) {
Node node = Load("{<<: {a: 1}}");
ASSERT_FALSE(node["a"]);
}
#endif
TEST(NodeTest, EmitEmptyNode) {
Node node;
Emitter emitter;