feat(reflect-cpp): Switched from glaze -> reflect cpp
A bug was discovered in glaze which prevented valid toml output. We have switched to toml++ and reflect-cpp. The interface has remained the same so this should not break any code
This commit is contained in:
@@ -1,53 +1,22 @@
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include "glaze/toml.hpp"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <format>
|
||||
|
||||
#include "fourdst/config/registry.h"
|
||||
#include "fourdst/config/exceptions/exceptions.h"
|
||||
|
||||
namespace fourdst::config::utils {
|
||||
inline std::string extract_error_key(const std::string& buffer, const glz::error_ctx& pe) {
|
||||
if (pe.location >= buffer.size()) return "unknown";
|
||||
|
||||
size_t line_start = pe.location;
|
||||
while (line_start > 0 && buffer[line_start - 1] != '\n') {
|
||||
line_start--;
|
||||
}
|
||||
|
||||
size_t line_end = pe.location;
|
||||
while (line_end < buffer.size() && buffer[line_end] != '\n' && buffer[line_end] != '\r') {
|
||||
line_end++;
|
||||
}
|
||||
|
||||
std::string line = buffer.substr(line_start, line_end - line_start);
|
||||
|
||||
const size_t separator_pos = line.find_first_of("=:");
|
||||
|
||||
if (separator_pos != std::string::npos) {
|
||||
std::string key_part = line.substr(0, separator_pos);
|
||||
|
||||
while (!key_part.empty() && std::isspace(key_part.back())) {
|
||||
key_part.pop_back();
|
||||
}
|
||||
|
||||
size_t first_char = 0;
|
||||
while (first_char < key_part.size() && std::isspace(key_part[first_char])) {
|
||||
first_char++;
|
||||
}
|
||||
|
||||
if (first_char < key_part.size()) {
|
||||
return key_part.substr(first_char);
|
||||
}
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
}
|
||||
#include "rfl.hpp"
|
||||
#include "rfl/toml.hpp"
|
||||
#include "rfl/json.hpp"
|
||||
|
||||
namespace fourdst::config {
|
||||
enum class RootNameLoadPolicy {
|
||||
FROM_FILE,
|
||||
KEEP_CURRENT
|
||||
};
|
||||
|
||||
enum class ConfigState {
|
||||
DEFAULT,
|
||||
LOADED_FROM_FILE
|
||||
@@ -56,66 +25,110 @@ namespace fourdst::config {
|
||||
template <typename T>
|
||||
class Config {
|
||||
public:
|
||||
Config() {
|
||||
(void)m_registrar;
|
||||
Config() = default;
|
||||
const T* operator->() const { return &m_content; }
|
||||
const T& main() const { return m_content; }
|
||||
|
||||
void save(std::string_view path) const {
|
||||
T default_instance{};
|
||||
std::unordered_map<std::string, T> wrapper;
|
||||
wrapper[m_root_name] = m_content;
|
||||
const std::string toml_string = rfl::toml::write(wrapper);
|
||||
|
||||
std::ofstream ofs{std::string(path)};
|
||||
if (!ofs.is_open()) {
|
||||
throw exceptions::ConfigSaveError(
|
||||
std::format("Failed to open file for writing config: {}", path)
|
||||
);
|
||||
}
|
||||
|
||||
ofs << toml_string;
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
const T* operator->() const { return &m_content.main; }
|
||||
const T& main() const { return m_content.main; }
|
||||
void set_root_name(const std::string_view name) {
|
||||
m_root_name = name;
|
||||
}
|
||||
|
||||
void save(std::optional<std::string_view> path = std::nullopt) const {
|
||||
if (!path) {
|
||||
path = std::string(glz::name_v<T>) + ".toml";
|
||||
}
|
||||
auto err = glz::write_file_toml(m_content, path.value(), std::string{});
|
||||
if (err) {
|
||||
throw exceptions::ConfigSaveError(
|
||||
std::format(
|
||||
"Config::save: Failed to save config to {} with glaze error {} ",
|
||||
std::string(path.value()),
|
||||
glz::format_error(err.ec)
|
||||
)
|
||||
);
|
||||
[[nodiscard]] std::string_view get_root_name() const {
|
||||
return m_root_name;
|
||||
}
|
||||
|
||||
void set_root_name_load_policy(const RootNameLoadPolicy policy) {
|
||||
m_root_name_load_policy = policy;
|
||||
}
|
||||
|
||||
RootNameLoadPolicy get_root_name_load_policy() const {
|
||||
return m_root_name_load_policy;
|
||||
}
|
||||
|
||||
std::string describe_root_name_load_policy() const {
|
||||
switch (m_root_name_load_policy) {
|
||||
case RootNameLoadPolicy::FROM_FILE:
|
||||
return "FROM_FILE";
|
||||
case RootNameLoadPolicy::KEEP_CURRENT:
|
||||
return "KEEP_CURRENT";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void load(const std::string_view path) {
|
||||
std::string buffer;
|
||||
const auto ec = glz::file_to_buffer(buffer, path);
|
||||
if (ec != glz::error_code::none) {
|
||||
if (m_state == ConfigState::LOADED_FROM_FILE) {
|
||||
throw exceptions::ConfigLoadError(
|
||||
"Config has already been loaded from file. Reloading is not supported.");
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
throw exceptions::ConfigLoadError(
|
||||
std::format("Config file does not exist: {}", path));
|
||||
}
|
||||
|
||||
using wrapper = std::unordered_map<std::string, T>;
|
||||
const rfl::Result<wrapper> result = rfl::toml::load<wrapper>(std::string(path));
|
||||
|
||||
if (!result) {
|
||||
throw exceptions::ConfigParseError(
|
||||
std::format("Failed to load config from file: {}", path));
|
||||
}
|
||||
|
||||
|
||||
std::string loaded_root_name = result.value().begin()->first;
|
||||
|
||||
if (m_root_name_load_policy == RootNameLoadPolicy::KEEP_CURRENT && m_root_name != loaded_root_name) {
|
||||
throw exceptions::ConfigLoadError(
|
||||
std::format(
|
||||
"Config::load: Failed to load config from {} with glaze error {} ",
|
||||
std::string(path),
|
||||
glz::format_error(ec)
|
||||
)
|
||||
);
|
||||
}
|
||||
auto err = glz::read<
|
||||
glz::opts{
|
||||
.format = glz::TOML,
|
||||
.error_on_unknown_keys = true
|
||||
}>(m_content, buffer);
|
||||
if (err) {
|
||||
throw exceptions::ConfigParseError(
|
||||
std::format(
|
||||
"Config::load: Failed to parse config from {} with glaze error {} (Key: {}) ",
|
||||
std::string(path),
|
||||
glz::format_error(err.ec),
|
||||
utils::extract_error_key(buffer, err)
|
||||
"Root name mismatch when loading config from file. Current root name is '{}', but file root name is '{}'. If you want to use the root name from the file, set the root name load policy to FROM_FILE using set_root_name_load_policy().",
|
||||
m_root_name,
|
||||
loaded_root_name
|
||||
)
|
||||
);
|
||||
}
|
||||
m_root_name = loaded_root_name;
|
||||
|
||||
m_content = result.value().at(loaded_root_name);
|
||||
|
||||
m_state = ConfigState::LOADED_FROM_FILE;
|
||||
}
|
||||
|
||||
void save_schema(const std::string_view dir) const {
|
||||
Registry::generate_named(dir, std::string(glz::name_v<T>));
|
||||
static void save_schema(const std::string& path) {
|
||||
using wrapper = std::unordered_map<std::string, T>;
|
||||
const std::string json_schema = rfl::json::to_schema<wrapper>(rfl::json::pretty);
|
||||
|
||||
std::ofstream ofs{std::string(path)};
|
||||
if (!ofs.is_open()) {
|
||||
throw exceptions::SchemaSaveError(
|
||||
std::format("Failed to open file for writing schema: {}", path)
|
||||
);
|
||||
}
|
||||
|
||||
ofs << json_schema;
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
ConfigState get_state() const { return m_state; }
|
||||
[[nodiscard]] ConfigState get_state() const { return m_state; }
|
||||
|
||||
std::string describe_state() const {
|
||||
[[nodiscard]] std::string describe_state() const {
|
||||
switch (m_state) {
|
||||
case ConfigState::DEFAULT:
|
||||
return "DEFAULT";
|
||||
@@ -127,20 +140,10 @@ namespace fourdst::config {
|
||||
}
|
||||
|
||||
private:
|
||||
struct Content {
|
||||
T main;
|
||||
};
|
||||
Content m_content;
|
||||
T m_content;
|
||||
std::string m_root_name = "main";
|
||||
ConfigState m_state = ConfigState::DEFAULT;
|
||||
|
||||
|
||||
struct Registrar {
|
||||
Registrar() {
|
||||
const auto name = std::string(glz::name_v<T>);
|
||||
Registry::register_schema<Content>(name);
|
||||
}
|
||||
};
|
||||
static inline Registrar m_registrar;
|
||||
RootNameLoadPolicy m_root_name_load_policy = RootNameLoadPolicy::KEEP_CURRENT;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,16 +154,9 @@ struct std::formatter<fourdst::config::Config<T>, CharT> {
|
||||
|
||||
auto format(const fourdst::config::Config<T>& config, auto& ctx) const {
|
||||
const T& inner_value = config.main();
|
||||
struct Content {
|
||||
T main;
|
||||
};
|
||||
Content content{inner_value};
|
||||
std::map<std::string, T> wrapper;
|
||||
wrapper[std::string(config.get_root_name())] = inner_value;
|
||||
std::string buffer;
|
||||
const glz::error_ctx ec = glz::write<glz::opts{.format=glz::TOML, .prettify = true}>(content, buffer);
|
||||
if (ec) {
|
||||
return std::format_to(ctx.out(), "Error serializing config");
|
||||
}
|
||||
|
||||
return std::format_to(ctx.out(), "{}", buffer);
|
||||
return buffer;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
namespace fourdst::config::exceptions {
|
||||
class ConfigError : public std::exception {
|
||||
public:
|
||||
ConfigError(const std::string & what): m_msg(what) {}
|
||||
explicit ConfigError(const std::string & what): m_msg(what) {}
|
||||
|
||||
const char* what() const noexcept override {
|
||||
[[nodiscard]] const char* what() const noexcept override {
|
||||
return m_msg.c_str();
|
||||
}
|
||||
private:
|
||||
@@ -27,12 +27,9 @@ namespace fourdst::config::exceptions {
|
||||
using ConfigError::ConfigError;
|
||||
};
|
||||
|
||||
class SchemaGenerationError final : public ConfigError {
|
||||
class SchemaSaveError final : public ConfigError {
|
||||
using ConfigError::ConfigError;
|
||||
};
|
||||
|
||||
class SchemaNameError final : public ConfigError {
|
||||
using ConfigError::ConfigError;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <glaze/glaze.hpp>
|
||||
#include <exception>
|
||||
#include "fourdst/config/exceptions/exceptions.h"
|
||||
|
||||
namespace fourdst::config {
|
||||
|
||||
struct Registry {
|
||||
using SchemaWriter = std::function<void(std::filesystem::path)>;
|
||||
|
||||
static std::unordered_map<std::string, SchemaWriter>& get_writers() {
|
||||
static std::unordered_map<std::string, SchemaWriter> writers;
|
||||
return writers;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void register_schema(const std::string& name) {
|
||||
auto& writers = get_writers();
|
||||
|
||||
writers.insert({name, [name](const std::filesystem::path &dir) {
|
||||
|
||||
auto schema_r = glz::write_json_schema<T>();
|
||||
if (!schema_r.has_value()) {
|
||||
throw std::runtime_error("Failed to generate schema for " + name);
|
||||
}
|
||||
std::string schema = schema_r.value();
|
||||
|
||||
const auto path = dir / (name + ".schema.json");
|
||||
const auto err = glz::buffer_to_file(schema, path.string());
|
||||
|
||||
if (err != glz::error_code::none) {
|
||||
throw exceptions::SchemaGenerationError("Failed to write schema for " + name + " to " + path.string());
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
static void generate_all(const std::filesystem::path& dir) {
|
||||
std::filesystem::create_directories(dir);
|
||||
for (const auto &writer: get_writers() | std::views::values) {
|
||||
writer(dir);
|
||||
}
|
||||
}
|
||||
|
||||
static void generate_named(const std::filesystem::path& dir, const std::string& name) {
|
||||
const auto& writers = get_writers();
|
||||
const auto it = writers.find(name);
|
||||
if (it == writers.end()) {
|
||||
throw exceptions::SchemaNameError("No schema registered with name: " + name);
|
||||
}
|
||||
it->second(dir);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/* ***********************************************************************
|
||||
//
|
||||
// Copyright (C) 2025 -- The 4D-STAR Collaboration
|
||||
// File Author: Emily Boudreaux
|
||||
// Last Modified: March 20, 2025
|
||||
//
|
||||
// 4DSSE is free software; you can use it and/or modify
|
||||
// it under the terms and restrictions the GNU General Library Public
|
||||
// License version 3 (GPLv3) as published by the Free Software Foundation.
|
||||
//
|
||||
// 4DSSE is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the GNU Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public License
|
||||
// along with this software; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// *********************************************************************** */
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "yaml-cpp/yaml.h"
|
||||
|
||||
#include "fourdst/config/config.h"
|
||||
|
||||
namespace fourdst::config {
|
||||
|
||||
Config::Config() {}
|
||||
|
||||
Config::~Config() {}
|
||||
|
||||
Config& Config::getInstance() {
|
||||
static Config instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Config::loadConfig(const std::string& configFile) {
|
||||
configFilePath = configFile;
|
||||
try {
|
||||
yamlRoot = YAML::LoadFile(configFile);
|
||||
} catch (YAML::BadFile& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
m_loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Config::isKeyInCache(const std::string &key) {
|
||||
return configMap.find(key) != configMap.end();
|
||||
}
|
||||
|
||||
void Config::addToCache(const std::string &key, const YAML::Node &node) {
|
||||
configMap[key] = node;
|
||||
}
|
||||
|
||||
void Config::registerUnknownKey(const std::string &key) {
|
||||
unknownKeys.push_back(key);
|
||||
}
|
||||
|
||||
bool Config::has(const std::string &key) {
|
||||
if (!m_loaded) {
|
||||
throw std::runtime_error("Error! Config file not loaded");
|
||||
}
|
||||
if (isKeyInCache(key)) { return true; }
|
||||
|
||||
YAML::Node node = YAML::Clone(yamlRoot);
|
||||
std::istringstream keyStream(key);
|
||||
std::string subKey;
|
||||
while (std::getline(keyStream, subKey, ':')) {
|
||||
if (!node[subKey]) {
|
||||
registerUnknownKey(key);
|
||||
return false;
|
||||
}
|
||||
node = node[subKey]; // go deeper
|
||||
}
|
||||
|
||||
// Key exists and is of the requested type
|
||||
addToCache(key, node);
|
||||
return true;
|
||||
}
|
||||
|
||||
void recurse_keys(const YAML::Node& node, std::vector<std::string>& keyList, const std::string& path = "") {
|
||||
if (node.IsMap()) {
|
||||
for (const auto& it : node) {
|
||||
auto key = it.first.as<std::string>();
|
||||
auto new_path = path.empty() ? key : path + ":" + key;
|
||||
recurse_keys(it.second, keyList, new_path);
|
||||
}
|
||||
} else {
|
||||
keyList.push_back(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::vector<std::string> Config::keys() const {
|
||||
std::vector<std::string> keyList;
|
||||
YAML::Node node = YAML::Clone(yamlRoot);
|
||||
recurse_keys(node, keyList);
|
||||
return keyList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +1,11 @@
|
||||
# Define the library
|
||||
config_sources = files(
|
||||
'lib/config.cpp',
|
||||
)
|
||||
|
||||
|
||||
# Define the libconfig library so it can be linked against by other parts of the build system
|
||||
#libconfig = library('config',
|
||||
# config_sources,
|
||||
# include_directories: include_directories('include'),
|
||||
# cpp_args: ['-fvisibility=default'],
|
||||
# dependencies: [yaml_cpp_dep, glaze_dep],
|
||||
# install : true)
|
||||
|
||||
config_dep = declare_dependency(
|
||||
include_directories: include_directories('include'),
|
||||
# link_with: libconfig,
|
||||
# sources: config_sources,
|
||||
dependencies: [glaze_dep],
|
||||
dependencies: [reflect_cpp_dep],
|
||||
)
|
||||
|
||||
# Make headers accessible
|
||||
config_headers = files(
|
||||
'include/fourdst/config/config.h',
|
||||
'include/fourdst/config/registry.h',
|
||||
'include/fourdst/config/exceptions/exceptions.h',
|
||||
'include/fourdst/config/base.h',
|
||||
)
|
||||
install_headers(config_headers, subdir : 'fourdst/fourdst/config')
|
||||
|
||||
Reference in New Issue
Block a user