Skip to content

Commit

Permalink
add yaml-cpp for file config
Browse files Browse the repository at this point in the history
  • Loading branch information
yhmtsai committed Sep 11, 2024
1 parent 6db98d6 commit 6befbd3
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ IncludeCategories:
- Regex: '^<(omp|cu|hip|oneapi|thrust|CL/|cooperative|mpi|nvToolsExt|Kokkos).*'
Priority: 2
SortPriority: 3
- Regex: '^<(nlohmann|gflags|gtest|sde_lib|papi).*'
- Regex: '^<(yaml-cpp|nlohmann|gflags|gtest|sde_lib|papi).*'
Priority: 4
- Regex: '<ginkgo/ginkgo.hpp>'
Priority: 6
Expand Down
14 changes: 14 additions & 0 deletions extensions/test/config/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
find_package(yaml-cpp 0.8.0 QUIET)
if(NOT yaml-cpp_FOUND)
message(STATUS "Fetching external yaml-cpp")
FetchContent_Declare(
yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
GIT_TAG 0.8.0
)
FetchContent_MakeAvailable(yaml-cpp)
endif()
ginkgo_create_test(json_config ADDITIONAL_LIBRARIES nlohmann_json::nlohmann_json)
ginkgo_create_test(yaml_config ADDITIONAL_LIBRARIES yaml-cpp::yaml-cpp)

# prepare the testing file and generate location
configure_file("${Ginkgo_SOURCE_DIR}/extensions/test/config/file_location.hpp.in"
"${Ginkgo_BINARY_DIR}/extensions/test/config/file_location.hpp" @ONLY)
configure_file(test.json "${Ginkgo_BINARY_DIR}/extensions/test/config/test.json")
configure_file(test.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/test.yaml")
configure_file(alias.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/alias.yaml")
configure_file(nested_alias.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/nested_alias.yaml")
7 changes: 7 additions & 0 deletions extensions/test/config/alias.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
base: &base_config
key1: 123
base2: &base_config2
key2: test
test:
<<: [*base_config, *base_config2]
key3: true
7 changes: 7 additions & 0 deletions extensions/test/config/file_location.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ namespace config {

const char* location_test_json =
"@Ginkgo_BINARY_DIR@/extensions/test/config/test.json";
const char* location_test_yaml =
"@Ginkgo_BINARY_DIR@/extensions/test/config/test.yaml";
const char* location_alias_yaml =
"@Ginkgo_BINARY_DIR@/extensions/test/config/alias.yaml";
const char* location_nested_alias_yaml =
"@Ginkgo_BINARY_DIR@/extensions/test/config/nested_alias.yaml";



} // namespace config
Expand Down
9 changes: 9 additions & 0 deletions extensions/test/config/nested_alias.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
base: &base_config
key1: 123
base2: &base_config2
<<: *base_config
key2: test
test:
<<: *base_config2
key2: override
key3: true
6 changes: 6 additions & 0 deletions extensions/test/config/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
item: 4
array:
- 3.0
- 4.5
map:
bool: false
127 changes: 127 additions & 0 deletions extensions/test/config/yaml_config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: 2017 - 2024 The Ginkgo authors
//
// SPDX-License-Identifier: BSD-3-Clause

#include <ostream>
#include <stdexcept>
#include <string>

#include <gtest/gtest.h>
#include <yaml-cpp/yaml.h>

#include <ginkgo/core/config/property_tree.hpp>
#include <ginkgo/extensions/config/yaml_config.hpp>

#include "core/test/utils.hpp"
#include "extensions/test/config/file_location.hpp"


TEST(YamlConfig, ThrowIfInvalid)
{
const char yaml[] = "test: null";
auto d = YAML::Load(yaml);

ASSERT_THROW(gko::ext::config::parse_yaml(d), std::runtime_error);
}


TEST(YamlConfig, ReadMap)
{
const char yaml[] = R"(
test: A
bool: true
)";
auto d = YAML::Load(yaml);

auto ptree = gko::ext::config::parse_yaml(d);

ASSERT_EQ(ptree.get_map().size(), 2);
ASSERT_EQ(ptree.get("test").get_string(), "A");
ASSERT_EQ(ptree.get("bool").get_boolean(), true);
}


TEST(YamlConfig, ReadArray)
{
const char yaml[] = R"(
- A
- B
- C
)";
auto d = YAML::Load(yaml);

auto ptree = gko::ext::config::parse_yaml(d);

ASSERT_EQ(ptree.get_array().size(), 3);
ASSERT_EQ(ptree.get(0).get_string(), "A");
ASSERT_EQ(ptree.get(1).get_string(), "B");
ASSERT_EQ(ptree.get(2).get_string(), "C");
}


TEST(YamlConfig, ReadInput)
{
const char yaml[] = R"(
item: 4
array:
- 3.0
- 4.5
map:
bool: false)";
auto d = YAML::Load(yaml);

auto ptree = gko::ext::config::parse_yaml(d);

auto& child_array = ptree.get("array").get_array();
auto& child_map = ptree.get("map").get_map();
ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get("item").get_integer(), 4);
ASSERT_EQ(child_array.size(), 2);
ASSERT_EQ(child_array.at(0).get_real(), 3.0);
ASSERT_EQ(child_array.at(1).get_real(), 4.5);
ASSERT_EQ(child_map.size(), 1);
ASSERT_EQ(child_map.at("bool").get_boolean(), false);
}


TEST(YamlConfig, ReadInputFromFile)
{
auto ptree =
gko::ext::config::parse_yaml_file(gko::ext::config::location_test_yaml);

auto& child_array = ptree.get("array").get_array();
auto& child_map = ptree.get("map").get_map();
ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get("item").get_integer(), 4);
ASSERT_EQ(child_array.size(), 2);
ASSERT_EQ(child_array.at(0).get_real(), 3.0);
ASSERT_EQ(child_array.at(1).get_real(), 4.5);
ASSERT_EQ(child_map.size(), 1);
ASSERT_EQ(child_map.at("bool").get_boolean(), false);
}


TEST(YamlConfig, ReadInputFromFileWithAlias)
{
auto yaml = YAML::LoadFile(gko::ext::config::location_alias_yaml);

auto ptree = gko::ext::config::parse_yaml(yaml["test"]);

ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get_map().at("key1").get_integer(), 123);
ASSERT_EQ(ptree.get_map().at("key2").get_string(), "test");
ASSERT_EQ(ptree.get_map().at("key3").get_boolean(), true);
}


TEST(YamlConfig, ReadInputFromFileWithNestedAliasAndOverwrite)
{
auto yaml = YAML::LoadFile(gko::ext::config::location_nested_alias_yaml);

auto ptree = gko::ext::config::parse_yaml(yaml["test"]);

ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get_map().at("key1").get_integer(), 123);
ASSERT_EQ(ptree.get_map().at("key2").get_string(), "override");
ASSERT_EQ(ptree.get_map().at("key3").get_boolean(), true);
}
114 changes: 114 additions & 0 deletions include/ginkgo/extensions/config/yaml_config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: 2017 - 2024 The Ginkgo authors
//
// SPDX-License-Identifier: BSD-3-Clause

#ifndef GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_
#define GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_

#include <iostream>
#include <stdexcept>
#include <string>

#include <yaml-cpp/yaml.h>

#include <ginkgo/core/config/property_tree.hpp>


namespace gko {
namespace ext {
namespace config {


/**
* parse_yaml takes the yaml-cpp node object to generate the property tree
* object
*/
inline gko::config::pnode parse_yaml(const YAML::Node& input)
{
auto parse_array = [](const auto& arr) {
gko::config::pnode::array_type nodes;
for (const auto& it : arr) {
nodes.emplace_back(parse_yaml(it));
}
return gko::config::pnode{nodes};
};
auto parse_map = [](const auto& map) {
gko::config::pnode::map_type nodes;
// use [] to get override behavior
for (YAML::const_iterator it = map.begin(); it != map.end(); ++it) {
std::string key = it->first.as<std::string>();
// yaml-cpp keeps the alias without resolving it when parsing.
// We resolve them here.
if (key == "<<") {
auto node = parse_yaml(it->second);
if (node.get_tag() == gko::config::pnode::tag_t::array) {
for (const auto& arr : node.get_array()) {
for (const auto& item : arr.get_map()) {
nodes[item.first] = item.second;
}
}
} else if (node.get_tag() == gko::config::pnode::tag_t::map) {
for (const auto& item : node.get_map()) {
nodes[item.first] = item.second;
}
} else {
std::runtime_error("can not handle this alias: " +
YAML::Dump(it->second));
}
} else {
std::string content = it->first.as<std::string>();
nodes[key] = parse_yaml(it->second);
}
}
return gko::config::pnode{nodes};
};
// yaml-cpp does not have type check
auto parse_data = [](const auto& data) {
if (std::int64_t value;
YAML::convert<std::int64_t>::decode(data, value)) {
return gko::config::pnode{value};
}
if (bool value; YAML::convert<bool>::decode(data, value)) {
return gko::config::pnode{value};
}
if (double value; YAML::convert<double>::decode(data, value)) {
return gko::config::pnode{value};
}
if (std::string value;
YAML::convert<std::string>::decode(data, value)) {
return gko::config::pnode{value};
}
std::string content = YAML::Dump(data);
throw std::runtime_error(
"property_tree can not handle the node with content: " + content);
};

if (input.IsSequence()) {
return parse_array(input);
}
if (input.IsMap()) {
return parse_map(input);
}
return parse_data(input);
}


/**
* parse_yaml_file takes the yaml file to generate the property tree object
*
* @note Because YAML always needs a entry for reusing, there will be more than
* one entry when putting the anchors in the top level. This function can not
* know which entry is the actual solver, so please use the parse_yaml function.
*/
inline gko::config::pnode parse_yaml_file(std::string filename)
{
return parse_yaml(YAML::LoadFile(filename));
}


} // namespace config
} // namespace ext
} // namespace gko


#endif // GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_

0 comments on commit 6befbd3

Please sign in to comment.