Skip to content

Commit 4192215

Browse files
feat: Add initial implementation
1 parent ca61c6d commit 4192215

7 files changed

Lines changed: 503 additions & 3 deletions

File tree

src/CMakeLists.txt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
1-
add_executable(cpp-template main.cpp)
1+
add_library(CppfigLib)
22

3-
target_link_libraries(cpp-template
4-
GreeterLib)
3+
target_sources(CppfigLib
4+
PRIVATE
5+
${CMAKE_CURRENT_LIST_DIR}/Configuration.cpp
6+
PUBLIC
7+
${CMAKE_CURRENT_LIST_DIR}/Configuration.h
8+
${CMAKE_CURRENT_LIST_DIR}/IConfiguration.h
9+
)
10+
11+
target_include_directories(CppfigLib
12+
PUBLIC
13+
${CMAKE_CURRENT_LIST_DIR}
14+
)
15+
16+
target_link_libraries(CppfigLib
17+
PUBLIC
18+
# nlohmann_json::nlohmann_json
19+
# spdlog::spdlog
20+
)
21+
22+
if(STRICT_BUILD)
23+
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
24+
target_compile_options(CppfigLib PRIVATE -Wall -Wextra -Wpedantic -Werror -Wdocumentation)
25+
else()
26+
target_compile_options(CppfigLib PRIVATE -Wall -Wextra -Wpedantic -Werror)
27+
endif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
28+
endif(STRICT_BUILD)

src/Configuration.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#include "Configuration.h"
2+
3+
#include <spdlog/spdlog.h>
4+
5+
#include <fstream>
6+
#include <ostream>
7+
8+
#include "ConfigurationDefault.h"
9+
#include "ConfigurationName.h"
10+
#include "Setting.h"
11+
12+
namespace config {
13+
14+
Configuration::Configuration(std::filesystem::path filepath)
15+
: filepath_(std::move(filepath))
16+
{
17+
18+
if (!std::filesystem::exists(filepath_)) {
19+
bool creation_success = CreateDefaultConfigurationFile();
20+
if (!creation_success) {
21+
spdlog::error("Failed to create config file");
22+
}
23+
}
24+
else {
25+
bool load_success = LoadConfigurationFile();
26+
if (!load_success) {
27+
spdlog::error("Failed to load config file");
28+
}
29+
}
30+
}
31+
32+
bool Configuration::LoadConfigurationFile()
33+
{
34+
std::ifstream config_file(filepath_);
35+
config_file >> config_data_;
36+
return config_file.good();
37+
}
38+
39+
bool Configuration::WriteConfigurationFile(json& jsonfile)
40+
{
41+
if (jsonfile.empty()) {
42+
spdlog::error("JSON file is empty");
43+
return false;
44+
}
45+
46+
std::ofstream config_file;
47+
config_file.open(filepath_);
48+
49+
if (!config_file.is_open()) {
50+
int errnum = errno;
51+
spdlog::error("Failed to open file: {}", strerror(errnum));
52+
}
53+
try {
54+
config_file << std::setw(4) << jsonfile << '\n';
55+
}
56+
catch (const std::exception& e) {
57+
spdlog::error("Error writing JSON to file: {}", e.what());
58+
return false;
59+
}
60+
61+
return config_file.good();
62+
}
63+
64+
Setting Configuration::GetSetting(ConfigurationName config_name)
65+
{
66+
auto has_name = [config_name](const Setting& setting) { return setting.Name() == config_name; };
67+
68+
if (!config_data_.empty()) {
69+
if (auto iter = std::find_if(config_data_.begin(), config_data_.end(), has_name); iter != std::end(config_data_)) {
70+
return *iter;
71+
}
72+
}
73+
spdlog::info("Configuration {} not found in file, searching on default configuration.", ToString(config_name));
74+
75+
if (auto iter = std::find_if(DefaultConfig.begin(), DefaultConfig.end(), has_name); iter != std::end(DefaultConfig)) {
76+
return *iter;
77+
}
78+
79+
spdlog::warn("Configuration not found. The available configuration names are:");
80+
for (const auto& [key, value] : config_data_.items()) {
81+
spdlog::warn("{}", key);
82+
}
83+
84+
std::string error = "Configuration not found: " + ToString(config_name);
85+
throw std::runtime_error(error);
86+
}
87+
88+
bool Configuration::CreateDefaultConfigurationFile()
89+
{
90+
json default_config;
91+
92+
for (const auto& setting : DefaultConfig) {
93+
default_config += setting;
94+
}
95+
96+
bool write_success = WriteConfigurationFile(default_config);
97+
98+
return write_success;
99+
}
100+
101+
void to_json(json& j, const Setting& setting)
102+
{
103+
j = json {
104+
{ "name", ToString(setting.Name()) },
105+
{ "value", setting.Value() },
106+
{ "max_value", setting.MaxValue() },
107+
{ "min_value", setting.MinValue() },
108+
{ "unit", setting.Unit() },
109+
{ "description", setting.Description() }
110+
111+
};
112+
}
113+
114+
void from_json(const json& j, config::Setting& setting)
115+
{
116+
config::ConfigurationName name = config::FromString(j.at("name").get<std::string>());
117+
config::SettingValueType value = j.at("value").get<config::SettingValueType>();
118+
std::optional<config::SettingValueType> max_value = j.at("max_value").get<std::optional<config::SettingValueType>>();
119+
std::optional<config::SettingValueType> min_value = j.at("min_value").get<std::optional<config::SettingValueType>>();
120+
std::optional<std::string> unit = j.at("unit").get<std::optional<std::string>>();
121+
std::optional<std::string> description = j.at("description").get<std::optional<std::string>>();
122+
setting = config::Setting { name, value, max_value, min_value, unit, description };
123+
}
124+
125+
} // namespace config

src/Configuration.h

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#pragma once
2+
3+
#include <filesystem>
4+
#include <nlohmann/json.hpp>
5+
6+
#include "ConfigurationInterface.h"
7+
#include "ConfigurationName.h"
8+
#include "Setting.h"
9+
10+
NLOHMANN_JSON_NAMESPACE_BEGIN
11+
12+
/**
13+
* @brief Serializer. Uses Argument-dependent lookup to choose to_json/from_json functions from the types' namespaces.
14+
Overloaded to work with std::optional
15+
*
16+
* @tparam T Type
17+
*/
18+
template <typename T>
19+
struct adl_serializer<std::optional<T>> {
20+
/**
21+
* @brief Converts any value type to a JSON value
22+
*
23+
* @param json json
24+
* @param t optional value
25+
*/
26+
static void to_json(json& json, const std::optional<T>& t) // NOLINT(readability-identifier-naming)
27+
{
28+
if (t.has_value()) {
29+
json = *t;
30+
}
31+
else {
32+
33+
json = nullptr;
34+
}
35+
}
36+
/**
37+
* @brief Converts a JSON value to any value type
38+
*
39+
* @param j json
40+
* @param opt optional value
41+
*/
42+
static void from_json(const json& j, std::optional<T>& opt) // NOLINT(readability-identifier-naming)
43+
{
44+
if (j.is_null()) {
45+
opt = std::nullopt;
46+
}
47+
else {
48+
opt = j.get<T>();
49+
}
50+
}
51+
};
52+
53+
/**
54+
* @brief Helper function to convert a JSON value to a std::variant
55+
*
56+
* @tparam T Type
57+
* @tparam Ts Types
58+
* @param j json
59+
* @param data data
60+
*/
61+
template <typename T, typename... Ts>
62+
void variant_from_json(const nlohmann::json& j, std::variant<Ts...>& data) // NOLINT(readability-identifier-naming)
63+
{
64+
try {
65+
data = j.get<T>();
66+
}
67+
catch (...) {
68+
}
69+
}
70+
71+
/**
72+
* @brief Serializer. Uses Argument-dependent lookup to choose to_json/from_json functions from the types' namespaces.
73+
Overloaded to work with std::variant
74+
*
75+
* @tparam Ts Type list
76+
*/
77+
template <typename... Ts>
78+
struct adl_serializer<std::variant<Ts...>> {
79+
/**
80+
* @brief Converts any value type to a JSON value
81+
*
82+
* @param j json
83+
* @param data data
84+
*/
85+
static void to_json(nlohmann::json& j, const std::variant<Ts...>& data) // NOLINT(readability-identifier-naming)
86+
{
87+
std::visit([&j](const auto& v) { j = v; }, data);
88+
}
89+
/**
90+
* @brief Converts a JSON value to any value type
91+
*
92+
* @param j json
93+
* @param data data
94+
*/
95+
static void from_json(const nlohmann::json& j, std::variant<Ts...>& data) // NOLINT(readability-identifier-naming)
96+
{
97+
(variant_from_json<Ts>(j, data), ...);
98+
}
99+
};
100+
101+
NLOHMANN_JSON_NAMESPACE_END
102+
103+
namespace config {
104+
105+
using json = nlohmann::json;
106+
class Configuration : public IConfiguration {
107+
public:
108+
/**
109+
* @brief Configuration used to load and save configuration settings.
110+
*
111+
* @param filepath Path to the configuration file
112+
*/
113+
explicit Configuration(std::filesystem::path filepath);
114+
/**
115+
* @brief Get a setting from the configuration file.
116+
*
117+
* @param config_name Configuration name
118+
* @return Setting Setting value
119+
*/
120+
[[nodiscard]] Setting GetSetting(ConfigurationName config_name) override;
121+
122+
private:
123+
/**
124+
* @brief Load the configuration file.
125+
*
126+
* @return true if the file was loaded successfully
127+
*/
128+
[[nodiscard]] bool LoadConfigurationFile();
129+
/**
130+
* @brief Write the configuration file.
131+
*
132+
* @param jsonfile JSON file
133+
* @return true if the file was written successfully
134+
*/
135+
[[nodiscard]] bool WriteConfigurationFile(json& jsonfile);
136+
/**
137+
* @brief Create a default configuration file.
138+
*
139+
* @return true if the file was created successfully
140+
*/
141+
[[nodiscard]] bool CreateDefaultConfigurationFile();
142+
143+
std::filesystem::path filepath_;
144+
json config_data_;
145+
};
146+
147+
/**
148+
* @brief Convert a Setting to a JSON object.
149+
*
150+
* @param j JSON object
151+
* @param setting Setting object
152+
*/
153+
void to_json(json& j, const Setting& setting); // NOLINT(readability-identifier-naming)
154+
/**
155+
* @brief Convert a JSON object to a Setting.
156+
*
157+
* @param j JSON object
158+
* @param setting Setting object
159+
*/
160+
void from_json(const json& j, config::Setting& setting); // NOLINT(readability-identifier-naming)
161+
162+
} // namespace config

src/ConfigurationDefault.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <optional>
4+
#include <vector>
5+
6+
#include "Setting.h"
7+
8+
namespace config {
9+
// Default configuration settings
10+
const std::vector<Setting> DefaultConfig = {
11+
{ ConfigurationName::RoomTemperature,
12+
25.5,
13+
30,
14+
20,
15+
std::nullopt,
16+
"Room temperature" },
17+
{ ConfigurationName::PathToText,
18+
std::string("/home/user/text.txt"),
19+
std::nullopt,
20+
std::nullopt,
21+
std::nullopt,
22+
"Path to the text file" },
23+
};
24+
25+
} // namespace config

src/ConfigurationName.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <iostream>
5+
#include <ostream>
6+
7+
namespace config {
8+
9+
enum class ConfigurationName : std::uint8_t {
10+
RoomTemperature,
11+
PathToText,
12+
};
13+
14+
/**
15+
* @brief Converts the given ConfigurationName into the equivalent name
16+
*
17+
* @param config_name ConfigurationName to be converted
18+
* @return std::string name of the configuration
19+
*
20+
* @remark For logging purposes but also to overload the << operator so that GTest can print the errors correctly
21+
*/
22+
inline std::string ToString(ConfigurationName config_name)
23+
{
24+
switch (config_name) {
25+
case ConfigurationName::RoomTemperature:
26+
return "room_temperature";
27+
case ConfigurationName::PathToText:
28+
return "path_to_text";
29+
} // Don't add the default case, so that the compiler can warn you.
30+
return {};
31+
}
32+
33+
inline ConfigurationName FromString(const std::string& config_name)
34+
{
35+
if (config_name == "room_temperature") {
36+
return ConfigurationName::RoomTemperature;
37+
}
38+
if (config_name == "path_to_text") {
39+
return ConfigurationName::PathToText;
40+
}
41+
std::cerr << "Trying to parse " << config_name << " as ConfigurationName" << '\n';
42+
return {};
43+
}
44+
45+
inline std::ostream& operator<<(std::ostream& ostream, const ConfigurationName& config_name)
46+
{
47+
ostream << ToString(config_name);
48+
return ostream;
49+
}
50+
51+
} // namespace config

0 commit comments

Comments
 (0)