Skip to content

Commit bad9c7b

Browse files
committed
feat: Add YAML-to-struct conversion utilities and improve logging setup
- Added YAML converters for `ModelSettings`, `PopulationDemographic`, `TransmissionSettings`, and `date::year_month_day` structs. - Introduced `ConfigData` struct to group configuration data sections. - Updated `Config` class to load, reload, and notify observers for configuration changes using the new `ConfigData` structure. - Integrated `sol2` and `Lua` for dynamic configuration validation. - Added `Logger` class for initializing loggers using `spdlog`, with a dedicated logger for `ConfigValidator` and network operations. - Updated CMakeLists to include dependencies: Sol2, Lua, spdlog, and date libraries. - Added unit tests for YAML converters, including date and configuration-related structs, using Google Test. - Removed example tests and added relevant configuration tests. - Added prompts for generating GTest test cases and YAML struct converters for `ModelSettings`.
1 parent 545698a commit bad9c7b

23 files changed

+940
-84
lines changed

not_used/ConfigValidator.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#include "ConfigValidator.h"
2+
3+
#include <iostream>
4+
5+
#include "Config.h"
6+
7+
bool ConfigValidator::Validate(const ConfigData &config) {
8+
try {
9+
// Add more validations as needed
10+
return true;
11+
} catch (const std::exception &e) {
12+
std::cerr << "Validation failed: " << e.what() << std::endl;
13+
return false;
14+
}
15+
}
16+
17+
bool ConfigValidator::ValidateAgainstSchema(const ConfigData &config,
18+
const YAML::Node &schema) {
19+
// Implement dynamic validation logic here (e.g., using Lua or schema-based
20+
// validation)
21+
return true; // Placeholder
22+
}
23+
24+
// Recursive function to convert YAML node to Lua table using Sol2
25+
sol::table ConfigValidator::PushYamlToLua(sol::state &lua,
26+
const YAML::Node &node) {
27+
sol::table lua_table = lua.create_table();
28+
29+
for (auto it = node.begin(); it != node.end(); ++it) {
30+
std::string key;
31+
try {
32+
key = it->first.as<std::string>();
33+
} catch (const YAML::BadConversion &e) {
34+
// Handle invalid key conversion
35+
key = "invalid_key"; // Or handle appropriately
36+
}
37+
38+
const YAML::Node &value = it->second;
39+
40+
try {
41+
if (value.IsScalar()) {
42+
if (value.Tag() == "tag:yaml.org,2002:int") {
43+
lua_table[key] = value.as<int>();
44+
} else if (value.Tag() == "tag:yaml.org,2002:float") {
45+
lua_table[key] = value.as<double>();
46+
} else if (value.Tag() == "tag:yaml.org,2002:bool") {
47+
lua_table[key] = value.as<bool>();
48+
} else if (value.IsNull()) {
49+
lua_table[key] = sol::lua_nil;
50+
} else {
51+
lua_table[key] = value.as<std::string>();
52+
}
53+
} else if (value.IsMap()) {
54+
lua_table[key] = PushYamlToLua(lua, value);
55+
} else if (value.IsSequence()) {
56+
sol::table array_table = lua.create_table();
57+
int index = 1;
58+
for (const auto &element : value) {
59+
if (element.IsScalar()) {
60+
if (element.Tag() == "tag:yaml.org,2002:int") {
61+
array_table[index++] = element.as<int>();
62+
} else if (element.Tag() == "tag:yaml.org,2002:float") {
63+
array_table[index++] = element.as<double>();
64+
} else if (element.Tag() == "tag:yaml.org,2002:bool") {
65+
array_table[index++] = element.as<bool>();
66+
} else if (element.IsNull()) {
67+
array_table[index++] = sol::lua_nil;
68+
} else {
69+
array_table[index++] = element.as<std::string>();
70+
}
71+
} else {
72+
array_table[index++] = PushYamlToLua(lua, element);
73+
}
74+
}
75+
lua_table[key] = array_table;
76+
}
77+
} catch (const YAML::BadConversion &e) {
78+
// Handle conversion error, possibly logging and setting Lua to nil or a
79+
// default value
80+
lua_table[key] = sol::lua_nil;
81+
}
82+
}
83+
84+
return lua_table;
85+
}
86+
87+
// Load YAML configuration into Lua
88+
void ConfigValidator::LoadConfigToLua(sol::state &lua,
89+
const YAML::Node &config) {
90+
sol::table lua_config = PushYamlToLua(lua, config);
91+
lua["config"] = lua_config;
92+
// Debugging: Print Lua table contents
93+
std::cout << "Lua 'config' table contents:\n";
94+
for (const auto &pair : lua_config) {
95+
std::string key = pair.first.as<std::string>();
96+
sol::object value = pair.second;
97+
std::cout << key << " = ";
98+
switch (value.get_type()) {
99+
case sol::type::lua_nil:
100+
std::cout << "nil";
101+
break;
102+
case sol::type::boolean:
103+
std::cout << (value.as<bool>() ? "true" : "false");
104+
break;
105+
case sol::type::number:
106+
std::cout << value.as<double>();
107+
break;
108+
case sol::type::string:
109+
std::cout << "\"" << value.as<std::string>() << "\"";
110+
break;
111+
case sol::type::table:
112+
std::cout << "table";
113+
break;
114+
default:
115+
std::cout << "other";
116+
break;
117+
}
118+
std::cout << "\n";
119+
}
120+
}
121+
122+
// Example validation function using Lua for dynamic rules
123+
bool ConfigValidator::ValidateAgainstLua(const ConfigData &config,
124+
const YAML::Node &schema,
125+
sol::state &lua) {
126+
LoadConfigToLua(lua, schema);
127+
128+
// You can define or load Lua conditions here based on the schema
129+
std::string condition = R"(
130+
if physics.gravity > 9.8 and simulation.duration > 1000 then
131+
return false
132+
else
133+
return true
134+
end
135+
)";
136+
137+
sol::protected_function_result result =
138+
lua.safe_script(condition, sol::script_pass_on_error);
139+
if (!result.valid()) {
140+
sol::error err = result;
141+
std::cerr << "Lua validation error: " << err.what() << std::endl;
142+
return false;
143+
}
144+
145+
return result;
146+
}
147+

not_used/ConfigValidator.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// ConfigValidator.h
2+
#ifndef CONFIGVALIDATOR_H
3+
#define CONFIGVALIDATOR_H
4+
5+
#include <yaml-cpp/yaml.h>
6+
7+
#include <sol/sol.hpp>
8+
9+
class ConfigData;
10+
11+
class ConfigValidator {
12+
public:
13+
ConfigValidator() = default;
14+
~ConfigValidator() = default;
15+
16+
// Prevent copying and moving
17+
ConfigValidator(const ConfigValidator &) = delete;
18+
ConfigValidator(ConfigValidator &&) = delete;
19+
ConfigValidator &operator=(const ConfigValidator &) = delete;
20+
ConfigValidator &operator=(ConfigValidator &&) = delete;
21+
22+
// Validate the config data
23+
bool Validate(const ConfigData &config);
24+
25+
bool ValidateAgainstLua(const ConfigData &config, const YAML::Node &schema,
26+
sol::state &lua);
27+
28+
// Optionally, validate against YAML schema rules
29+
bool ValidateAgainstSchema(const ConfigData &config,
30+
const YAML::Node &schema);
31+
32+
// Helper method to load entire config into Lua
33+
void LoadConfigToLua(sol::state &lua, const YAML::Node &config);
34+
35+
// Helper method to convert YAML to Lua tables
36+
sol::table PushYamlToLua(sol::state &lua, const YAML::Node &node);
37+
38+
private:
39+
// Helper functions for different validation rules
40+
// void ValidateTimestep(double timestep);
41+
// void ValidateGravity(double gravity);
42+
// Other validation logic for fields
43+
};
44+
45+
#endif // CONFIGVALIDATOR_H
46+

not_used/ConfigValidator_test.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include "Configuration/ConfigValidator.h"
2+
3+
#include <gtest/gtest.h>
4+
#include <spdlog/spdlog.h>
5+
6+
class ConfigValidatorTest : public ::testing::Test {
7+
protected:
8+
ConfigValidator validator;
9+
10+
void SetUp() override {
11+
// Optional: Initialize any shared resources
12+
}
13+
void TearDown() override {
14+
// Optional: Clean up any shared resources
15+
}
16+
};
17+
18+
TEST_F(ConfigValidatorTest, Validate) {
19+
YAML::Node node = YAML::Load("{ settings: { pi: 3.14159}, alpha: 0.5 }");
20+
// YAML::Emitter out;
21+
// out << node;
22+
// spdlog::info("node: {}", out.c_str());
23+
24+
// Create a Lua state and load Sol2's standard libraries
25+
sol::state lua;
26+
lua.open_libraries(sol::lib::base, sol::lib::package);
27+
28+
validator.LoadConfigToLua(lua, node);
29+
30+
sol::table config = lua["config"];
31+
32+
sol::table settings = config["settings"];
33+
34+
EXPECT_DOUBLE_EQ(settings.get<double>("pi"), 3.14159);
35+
36+
EXPECT_DOUBLE_EQ(config["alpha"], 0.5);
37+
}

promts/generate_gtest.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
```
2+
As an expert in C++ programming, would you mind help me to write a gtest for the following function.
3+
Suggest me a test file name and using Test Fixture class.
4+
5+
6+
```

promts/struct_to_yaml_converter.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
```
2+
Here is the example of using yaml-cpp to convert yaml to Vec3 class
3+
4+
namespace YAML {
5+
template<>
6+
struct convert<Vec3> {
7+
static Node encode(const Vec3& rhs) {
8+
Node node;
9+
node.push_back(rhs.x);
10+
node.push_back(rhs.y);
11+
node.push_back(rhs.z);
12+
return node;
13+
}
14+
15+
static bool decode(const Node& node, Vec3& rhs) {
16+
if(!node.IsSequence() || node.size() != 3) {
17+
return false;
18+
}
19+
20+
rhs.x = node[0].as<double>();
21+
rhs.y = node[1].as<double>();
22+
rhs.z = node[2].as<double>();
23+
return true;
24+
}
25+
};
26+
}
27+
Then you could use Vec3 wherever you could use any other type:
28+
29+
YAML::Node node = YAML::Load("start: [1, 3, 0]");
30+
Vec3 v = node["start"].as<Vec3>();
31+
node["end"] = Vec3(2, -1, 0);
32+
33+
34+
As an expert in C++ developer, would you mind make the similar convert function for the following struct:
35+
36+
struct ModelSettings {
37+
int days_between_stdout_output; // Frequency of stdout output, in days
38+
int initial_seed_number; // Seed for random number generator
39+
bool record_genome_db; // Flag to record genomic data
40+
date::year_month_day starting_date; // Simulation start date (YYYY/MM/DD)
41+
date::year_month_day
42+
start_of_comparison_period; // Start of comparison period (YYYY/MM/DD)
43+
date::year_month_day ending_date; // Simulation end date (YYYY/MM/DD)
44+
int start_collect_data_day; // Day to start collecting data
45+
};
46+
```

promts/yaml_to_struct.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```
2+
As an expret in C++ programming, would you mind help me to convert the yaml which is from the input file to C++ struct.
3+
4+
For date type, you can use the date library from HowardHinnant.
5+
6+
7+
```

src/CMakeLists.txt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
find_package(fmt CONFIG REQUIRED)
22
find_package(GSL REQUIRED)
33
find_package(yaml-cpp CONFIG REQUIRED)
4-
5-
include_directories(${PROJECT_SOURCE_DIR}/src)
4+
find_package(sol2 CONFIG REQUIRED)
5+
find_package(Lua REQUIRED)
6+
find_package(spdlog REQUIRED)
7+
find_package(date CONFIG REQUIRED)
8+
9+
include_directories(
10+
${PROJECT_SOURCE_DIR}/src
11+
${LUA_INCLUDE_DIR}
12+
)
613

714
# craete source files
815
# Add source files for the core library
@@ -23,6 +30,10 @@ target_link_libraries(MalaSimCore PUBLIC
2330
fmt::fmt
2431
GSL::gsl GSL::gslcblas
2532
yaml-cpp::yaml-cpp
33+
${LUA_LIBRARIES}
34+
sol2
35+
spdlog::spdlog
36+
date::date date::date-tz
2637
)
2738

2839
set_property(TARGET MalaSimCore PROPERTY CXX_STANDARD 20)

src/Configuration/Config.cpp

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,30 @@
1+
12
#include "Config.h"
23

3-
#include <iostream>
4-
5-
bool Config::ValidateNode(const YAML::Node &node, const YAML::Node &schema) {
6-
for (auto it = schema.begin(); it != schema.end(); ++it) {
7-
std::string key = it->first.as<std::string>();
8-
const YAML::Node &schema_field = it->second;
9-
10-
// Check if the field is required and present
11-
if (schema_field["required"] && schema_field["required"].as<bool>()
12-
&& !node[key]) {
13-
std::cerr << "Missing required field: " << key << std::endl;
14-
return false;
15-
}
16-
17-
// If the field exists, check the type
18-
if (node[key]) {
19-
std::string expected_type = schema_field["type"].as<std::string>();
20-
if (expected_type == "double" && !node[key].IsScalar()) {
21-
std::cerr << "Invalid type for field: " << key << " (expected double)"
22-
<< std::endl;
23-
return false;
24-
}
25-
if (expected_type == "string" && !node[key].IsScalar()) {
26-
std::cerr << "Invalid type for field: " << key << " (expected string)"
27-
<< std::endl;
28-
return false;
29-
}
30-
31-
// Additional checks like min, max can be added
32-
if (expected_type == "double" && schema_field["min"]) {
33-
double value = node[key].as<double>();
34-
if (value < schema_field["min"].as<double>()) {
35-
std::cerr << "Value for " << key
36-
<< " is less than the minimum allowed: "
37-
<< schema_field["min"].as<double>() << std::endl;
38-
return false;
39-
}
40-
}
41-
}
42-
}
43-
return true;
4+
#include <yaml-cpp/yaml.h>
5+
6+
#include "YAMLConverters.h"
7+
8+
void Config::Load(const std::string &filename) {
9+
std::shared_lock lock(mutex_);
10+
config_file_path_ = filename;
11+
YAML::Node config = YAML::LoadFile(filename);
12+
config_data_.model_settings = config["ModelSettings"].as<ModelSettings>();
13+
config_data_.transmission_settings =
14+
config["TransmissionSettings"].as<TransmissionSettings>();
15+
config_data_.population_demographic =
16+
config["PopulationDemographic"].as<PopulationDemographic>();
17+
NotifyObservers();
4418
}
19+
20+
void Config::Reload() { Load(config_file_path_); }
21+
22+
void Config::RegisterObserver(ConfigObserver observer) {
23+
std::unique_lock lock(mutex_);
24+
observers_.push_back(observer);
25+
}
26+
27+
void Config::NotifyObservers() {
28+
for (const auto &observer : observers_) { observer(config_data_); }
29+
}
30+

0 commit comments

Comments
 (0)