Skip to content

Commit 04cb9c8

Browse files
committed
add yaml-cpp for file config
1 parent d7c7a7c commit 04cb9c8

File tree

8 files changed

+294
-1
lines changed

8 files changed

+294
-1
lines changed

.clang-format

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ IncludeCategories:
7070
- Regex: '^<(omp|cu|hip|oneapi|thrust|CL/|cooperative|mpi|nvToolsExt|Kokkos).*'
7171
Priority: 2
7272
SortPriority: 3
73-
- Regex: '^<(nlohmann|gflags|gtest|sde_lib|papi).*'
73+
- Regex: '^<(yaml-cpp|nlohmann|gflags|gtest|sde_lib|papi).*'
7474
Priority: 4
7575
- Regex: '<ginkgo/ginkgo.hpp>'
7676
Priority: 6

extensions/test/config/CMakeLists.txt

+23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1+
find_package(yaml-cpp 0.8.0 QUIET)
2+
if(NOT yaml-cpp_FOUND)
3+
message(STATUS "Fetching external yaml-cpp")
4+
FetchContent_Declare(
5+
yaml-cpp
6+
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
7+
GIT_TAG 0.8.0
8+
)
9+
FetchContent_MakeAvailable(yaml-cpp)
10+
endif()
111
ginkgo_create_test(json_config ADDITIONAL_LIBRARIES nlohmann_json::nlohmann_json)
12+
ginkgo_create_test(yaml_config ADDITIONAL_LIBRARIES yaml-cpp::yaml-cpp)
213

314
# prepare the testing file and generate location
415
configure_file(
@@ -10,3 +21,15 @@ configure_file(
1021
test.json
1122
"${Ginkgo_BINARY_DIR}/extensions/test/config/test.json"
1223
)
24+
configure_file(
25+
test.yaml
26+
"${Ginkgo_BINARY_DIR}/extensions/test/config/test.yaml"
27+
)
28+
configure_file(
29+
alias.yaml
30+
"${Ginkgo_BINARY_DIR}/extensions/test/config/alias.yaml"
31+
)
32+
configure_file(
33+
nested_alias.yaml
34+
"${Ginkgo_BINARY_DIR}/extensions/test/config/nested_alias.yaml"
35+
)

extensions/test/config/alias.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
base: &base_config
2+
key1: 123
3+
base2: &base_config2
4+
key2: test
5+
test:
6+
<<: [*base_config, *base_config2]
7+
key3: true

extensions/test/config/file_location.hpp.in

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ namespace config {
1313

1414
const char* location_test_json =
1515
"@Ginkgo_BINARY_DIR@/extensions/test/config/test.json";
16+
const char* location_test_yaml =
17+
"@Ginkgo_BINARY_DIR@/extensions/test/config/test.yaml";
18+
const char* location_alias_yaml =
19+
"@Ginkgo_BINARY_DIR@/extensions/test/config/alias.yaml";
20+
const char* location_nested_alias_yaml =
21+
"@Ginkgo_BINARY_DIR@/extensions/test/config/nested_alias.yaml";
22+
1623

1724

1825
} // namespace config
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
base: &base_config
2+
key1: 123
3+
base2: &base_config2
4+
<<: *base_config
5+
key2: test
6+
test:
7+
<<: *base_config2
8+
key2: override
9+
key3: true

extensions/test/config/test.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
item: 4
2+
array:
3+
- 3.0
4+
- 4.5
5+
map:
6+
bool: false
+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-FileCopyrightText: 2017 - 2024 The Ginkgo authors
2+
//
3+
// SPDX-License-Identifier: BSD-3-Clause
4+
5+
#include <ostream>
6+
#include <stdexcept>
7+
#include <string>
8+
9+
#include <gtest/gtest.h>
10+
#include <yaml-cpp/yaml.h>
11+
12+
#include <ginkgo/core/config/property_tree.hpp>
13+
#include <ginkgo/extensions/config/yaml_config.hpp>
14+
15+
#include "core/test/utils.hpp"
16+
#include "extensions/test/config/file_location.hpp"
17+
18+
19+
TEST(YamlConfig, ThrowIfInvalid)
20+
{
21+
const char yaml[] = "test: null";
22+
auto d = YAML::Load(yaml);
23+
24+
ASSERT_THROW(gko::ext::config::parse_yaml(d), std::runtime_error);
25+
}
26+
27+
28+
TEST(YamlConfig, ReadMap)
29+
{
30+
const char yaml[] = R"(
31+
test: A
32+
bool: true
33+
)";
34+
auto d = YAML::Load(yaml);
35+
36+
auto ptree = gko::ext::config::parse_yaml(d);
37+
38+
ASSERT_EQ(ptree.get_map().size(), 2);
39+
ASSERT_EQ(ptree.get("test").get_string(), "A");
40+
ASSERT_EQ(ptree.get("bool").get_boolean(), true);
41+
}
42+
43+
44+
TEST(YamlConfig, ReadArray)
45+
{
46+
const char yaml[] = R"(
47+
- A
48+
- B
49+
- C
50+
)";
51+
auto d = YAML::Load(yaml);
52+
53+
auto ptree = gko::ext::config::parse_yaml(d);
54+
55+
ASSERT_EQ(ptree.get_array().size(), 3);
56+
ASSERT_EQ(ptree.get(0).get_string(), "A");
57+
ASSERT_EQ(ptree.get(1).get_string(), "B");
58+
ASSERT_EQ(ptree.get(2).get_string(), "C");
59+
}
60+
61+
62+
TEST(YamlConfig, ReadInput)
63+
{
64+
const char yaml[] = R"(
65+
item: 4
66+
array:
67+
- 3.0
68+
- 4.5
69+
map:
70+
bool: false)";
71+
auto d = YAML::Load(yaml);
72+
73+
auto ptree = gko::ext::config::parse_yaml(d);
74+
75+
auto& child_array = ptree.get("array").get_array();
76+
auto& child_map = ptree.get("map").get_map();
77+
ASSERT_EQ(ptree.get_map().size(), 3);
78+
ASSERT_EQ(ptree.get("item").get_integer(), 4);
79+
ASSERT_EQ(child_array.size(), 2);
80+
ASSERT_EQ(child_array.at(0).get_real(), 3.0);
81+
ASSERT_EQ(child_array.at(1).get_real(), 4.5);
82+
ASSERT_EQ(child_map.size(), 1);
83+
ASSERT_EQ(child_map.at("bool").get_boolean(), false);
84+
}
85+
86+
87+
TEST(YamlConfig, ReadInputFromFile)
88+
{
89+
auto ptree =
90+
gko::ext::config::parse_yaml_file(gko::ext::config::location_test_yaml);
91+
92+
auto& child_array = ptree.get("array").get_array();
93+
auto& child_map = ptree.get("map").get_map();
94+
ASSERT_EQ(ptree.get_map().size(), 3);
95+
ASSERT_EQ(ptree.get("item").get_integer(), 4);
96+
ASSERT_EQ(child_array.size(), 2);
97+
ASSERT_EQ(child_array.at(0).get_real(), 3.0);
98+
ASSERT_EQ(child_array.at(1).get_real(), 4.5);
99+
ASSERT_EQ(child_map.size(), 1);
100+
ASSERT_EQ(child_map.at("bool").get_boolean(), false);
101+
}
102+
103+
104+
TEST(YamlConfig, ReadInputFromFileWithAlias)
105+
{
106+
auto yaml = YAML::LoadFile(gko::ext::config::location_alias_yaml);
107+
108+
auto ptree = gko::ext::config::parse_yaml(yaml["test"]);
109+
110+
ASSERT_EQ(ptree.get_map().size(), 3);
111+
ASSERT_EQ(ptree.get_map().at("key1").get_integer(), 123);
112+
ASSERT_EQ(ptree.get_map().at("key2").get_string(), "test");
113+
ASSERT_EQ(ptree.get_map().at("key3").get_boolean(), true);
114+
}
115+
116+
117+
TEST(YamlConfig, ReadInputFromFileWithNestedAliasAndOverwrite)
118+
{
119+
auto yaml = YAML::LoadFile(gko::ext::config::location_nested_alias_yaml);
120+
121+
auto ptree = gko::ext::config::parse_yaml(yaml["test"]);
122+
123+
ASSERT_EQ(ptree.get_map().size(), 3);
124+
ASSERT_EQ(ptree.get_map().at("key1").get_integer(), 123);
125+
ASSERT_EQ(ptree.get_map().at("key2").get_string(), "override");
126+
ASSERT_EQ(ptree.get_map().at("key3").get_boolean(), true);
127+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// SPDX-FileCopyrightText: 2017 - 2024 The Ginkgo authors
2+
//
3+
// SPDX-License-Identifier: BSD-3-Clause
4+
5+
#ifndef GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_
6+
#define GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_
7+
8+
#include <iostream>
9+
#include <stdexcept>
10+
#include <string>
11+
12+
#include <yaml-cpp/yaml.h>
13+
14+
#include <ginkgo/core/config/property_tree.hpp>
15+
16+
17+
namespace gko {
18+
namespace ext {
19+
namespace config {
20+
21+
22+
/**
23+
* parse_yaml takes the yaml-cpp node object to generate the property tree
24+
* object
25+
*/
26+
inline gko::config::pnode parse_yaml(const YAML::Node& input)
27+
{
28+
auto parse_array = [](const auto& arr) {
29+
gko::config::pnode::array_type nodes;
30+
for (const auto& it : arr) {
31+
nodes.emplace_back(parse_yaml(it));
32+
}
33+
return gko::config::pnode{nodes};
34+
};
35+
auto parse_map = [](const auto& map) {
36+
gko::config::pnode::map_type nodes;
37+
// use [] to get override behavior
38+
for (YAML::const_iterator it = map.begin(); it != map.end(); ++it) {
39+
std::string key = it->first.as<std::string>();
40+
// yaml-cpp keeps the alias without resolving it when parsing.
41+
// We resolve them here.
42+
if (key == "<<") {
43+
auto node = parse_yaml(it->second);
44+
if (node.get_tag() == gko::config::pnode::tag_t::array) {
45+
for (const auto& arr : node.get_array()) {
46+
for (const auto& item : arr.get_map()) {
47+
nodes[item.first] = item.second;
48+
}
49+
}
50+
} else if (node.get_tag() == gko::config::pnode::tag_t::map) {
51+
for (const auto& item : node.get_map()) {
52+
nodes[item.first] = item.second;
53+
}
54+
} else {
55+
std::runtime_error("can not handle this alias: " +
56+
YAML::Dump(it->second));
57+
}
58+
} else {
59+
std::string content = it->first.as<std::string>();
60+
nodes[key] = parse_yaml(it->second);
61+
}
62+
}
63+
return gko::config::pnode{nodes};
64+
};
65+
// yaml-cpp does not have type check
66+
auto parse_data = [](const auto& data) {
67+
if (std::int64_t value;
68+
YAML::convert<std::int64_t>::decode(data, value)) {
69+
return gko::config::pnode{value};
70+
}
71+
if (bool value; YAML::convert<bool>::decode(data, value)) {
72+
return gko::config::pnode{value};
73+
}
74+
if (double value; YAML::convert<double>::decode(data, value)) {
75+
return gko::config::pnode{value};
76+
}
77+
if (std::string value;
78+
YAML::convert<std::string>::decode(data, value)) {
79+
return gko::config::pnode{value};
80+
}
81+
std::string content = YAML::Dump(data);
82+
throw std::runtime_error(
83+
"property_tree can not handle the node with content: " + content);
84+
};
85+
86+
if (input.IsSequence()) {
87+
return parse_array(input);
88+
}
89+
if (input.IsMap()) {
90+
return parse_map(input);
91+
}
92+
return parse_data(input);
93+
}
94+
95+
96+
/**
97+
* parse_yaml_file takes the yaml file to generate the property tree object
98+
*
99+
* @note Because YAML always needs a entry for reusing, there will be more than
100+
* one entry when putting the anchors in the top level. This function can not
101+
* know which entry is the actual solver, so please use the parse_yaml function.
102+
*/
103+
inline gko::config::pnode parse_yaml_file(std::string filename)
104+
{
105+
return parse_yaml(YAML::LoadFile(filename));
106+
}
107+
108+
109+
} // namespace config
110+
} // namespace ext
111+
} // namespace gko
112+
113+
114+
#endif // GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_

0 commit comments

Comments
 (0)