From f4f6ea7c81dd41e74199f2ff0d8decb06263613d Mon Sep 17 00:00:00 2001 From: S David <2100425+s-daveb@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:48:20 -0400 Subject: [PATCH] Revamp of the TOML serialization code. This is a combination of 4 commits. --- This is the 1st commit message: Final attempt to revamp this cursed API This is the commit message #2: --- Simplify util/macros.hpp and rewrite util/toml from scratch This is the commit message #3: --- Very nearly done refactoring TOML serializer; still failing 2-3 unit tests This is the commit message #4: --- Finalize the toml serialization API refactor. Additionally: Standardize the data structures used in Toml unit tests --- include/TomlTable.hpp | 15 +- include/util/macros.hpp | 70 ++++------ include/util/toml.hpp | 209 ++++++++++++++-------------- include/util/toml.old.hpp | 155 +++++++++++++++++++++ src/CMakeLists.txt | 2 +- src/TomlConfigFile.cpp | 1 + tests/CMakeLists.txt | 8 +- tests/TomlConfigFile.test.cpp | 48 +++---- tests/TomlTable.test.cpp | 77 ++++------- tests/Util.macros.test.cpp | 13 +- tests/Util.toml.test.cpp | 213 +++++++++++------------------ tests/test-utils/serialization.hpp | 88 ++++++++++++ 12 files changed, 512 insertions(+), 387 deletions(-) create mode 100644 include/util/toml.old.hpp create mode 100644 tests/test-utils/serialization.hpp diff --git a/include/TomlTable.hpp b/include/TomlTable.hpp index 5b9c496..33cd832 100644 --- a/include/TomlTable.hpp +++ b/include/TomlTable.hpp @@ -17,8 +17,6 @@ namespace IOCore { struct TomlTable : public toml::table { private: - using TomlSerializer = IOCore::TOML::Serializer; - public: TomlTable() = default; TomlTable(const toml::table& tbl) : toml::table(tbl) {} @@ -26,8 +24,7 @@ struct TomlTable : public toml::table { TomlTable(TomlTable&& tbl) noexcept : toml::table(std::move(tbl)) {} template - TomlTable(const T& obj) - : toml::table(TomlSerializer::to_table>(obj)) + TomlTable(const T& obj) : toml::table(create_toml>(obj)) { } @@ -36,7 +33,7 @@ struct TomlTable : public toml::table { auto operator=(const T& obj) -> TomlTable& { using value_t = std::decay_t; - auto value = TomlSerializer::to_table(obj); + auto value = create_toml(obj); toml::table::operator=(value); return *this; } @@ -46,16 +43,16 @@ struct TomlTable : public toml::table { auto operator=(T&& obj) -> TomlTable& { using value_t = std::decay_t; - auto value = TomlSerializer::to_table(obj); - toml::table::operator=(std::move(value)); + to_toml(*this, obj); return *this; } template auto get() const -> T { - T retval; - TomlSerializer::from_table(*this, retval); + using value_t = std::decay_t; + value_t retval; + from_toml(*this, retval); return retval; } diff --git a/include/util/macros.hpp b/include/util/macros.hpp index 9cf1ff2..9fa295a 100644 --- a/include/util/macros.hpp +++ b/include/util/macros.hpp @@ -10,58 +10,34 @@ // #pragma once -#define TOML_EXPAND(x) x -#define TOML_ENUM_FIELD(field) { field, #field } +#define MACRO_EXPAND(x) x +#define NAMED_PAIR(field) { #field, field } #define FOREACH_PARAM_1(func, param) func(param) -#define FOREACH_PARAM_2(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_1(func, __VA_ARGS__)) -#define FOREACH_PARAM_3(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_2(func, __VA_ARGS__)) -#define FOREACH_PARAM_4(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_3(func, __VA_ARGS__)) -#define FOREACH_PARAM_5(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_4(func, __VA_ARGS__)) -#define FOREACH_PARAM_6(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_5(func, __VA_ARGS__)) -#define FOREACH_PARAM_7(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_6(func, __VA_ARGS__)) -#define FOREACH_PARAM_8(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_7(func, __VA_ARGS__)) -#define FOREACH_PARAM_9(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_8(func, __VA_ARGS__)) -#define FOREACH_PARAM_10(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_9(func, __VA_ARGS__)) -#define FOREACH_PARAM_11(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_10(func, __VA_ARGS__)) -#define FOREACH_PARAM_12(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_11(func, __VA_ARGS__)) -#define FOREACH_PARAM_13(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_12(func, __VA_ARGS__)) -#define FOREACH_PARAM_14(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_13(func, __VA_ARGS__)) -#define FOREACH_PARAM_15(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_14(func, __VA_ARGS__)) -#define FOREACH_PARAM_16(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_15(func, __VA_ARGS__)) -#define FOREACH_PARAM_17(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_16(func, __VA_ARGS__)) -#define FOREACH_PARAM_18(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_17(func, __VA_ARGS__)) -#define FOREACH_PARAM_19(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_18(func, __VA_ARGS__)) -#define FOREACH_PARAM_20(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_19(func, __VA_ARGS__)) -#define FOREACH_PARAM_21(func, param, ...) func(param); TOML_EXPAND(FOREACH_PARAM_20(func, __VA_ARGS__)) - - -#define FOREACH_ENUM_PARAM_1(func, param) func(param) -#define FOREACH_ENUM_PARAM_2(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_1(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_3(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_2(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_4(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_3(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_5(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_4(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_6(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_5(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_7(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_6(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_8(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_7(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_9(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_8(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_10(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_9(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_11(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_10(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_12(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_11(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_13(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_12(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_14(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_13(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_15(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_14(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_16(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_15(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_17(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_16(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_18(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_17(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_19(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_18(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_20(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_19(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM_21(func, param, ...) func(param), TOML_EXPAND(FOREACH_ENUM_PARAM_20(func, __VA_ARGS__)) +#define FOREACH_PARAM_2(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_1(func, __VA_ARGS__)) +#define FOREACH_PARAM_3(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_2(func, __VA_ARGS__)) +#define FOREACH_PARAM_4(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_3(func, __VA_ARGS__)) +#define FOREACH_PARAM_5(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_4(func, __VA_ARGS__)) +#define FOREACH_PARAM_6(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_5(func, __VA_ARGS__)) +#define FOREACH_PARAM_7(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_6(func, __VA_ARGS__)) +#define FOREACH_PARAM_8(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_7(func, __VA_ARGS__)) +#define FOREACH_PARAM_9(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_8(func, __VA_ARGS__)) +#define FOREACH_PARAM_10(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_9(func, __VA_ARGS__)) +#define FOREACH_PARAM_11(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_10(func, __VA_ARGS__)) +#define FOREACH_PARAM_12(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_11(func, __VA_ARGS__)) +#define FOREACH_PARAM_13(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_12(func, __VA_ARGS__)) +#define FOREACH_PARAM_14(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_13(func, __VA_ARGS__)) +#define FOREACH_PARAM_15(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_14(func, __VA_ARGS__)) +#define FOREACH_PARAM_16(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_15(func, __VA_ARGS__)) +#define FOREACH_PARAM_17(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_16(func, __VA_ARGS__)) +#define FOREACH_PARAM_18(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_17(func, __VA_ARGS__)) +#define FOREACH_PARAM_19(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_18(func, __VA_ARGS__)) +#define FOREACH_PARAM_20(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_19(func, __VA_ARGS__)) +#define FOREACH_PARAM_21(func, param, ...) func(param) MACRO_EXPAND(FOREACH_PARAM_20(func, __VA_ARGS__)) #define GET_FOREACH_MACRO(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,NAME,...) NAME -#define FOREACH_PARAM(func, ...) TOML_EXPAND(GET_FOREACH_MACRO(__VA_ARGS__, FOREACH_PARAM_21, FOREACH_PARAM_20, FOREACH_PARAM_19, FOREACH_PARAM_18, FOREACH_PARAM_17, FOREACH_PARAM_16, FOREACH_PARAM_15, FOREACH_PARAM_14, FOREACH_PARAM_13, FOREACH_PARAM_12, FOREACH_PARAM_11, FOREACH_PARAM_10, FOREACH_PARAM_9, FOREACH_PARAM_8, FOREACH_PARAM_7, FOREACH_PARAM_6, FOREACH_PARAM_5, FOREACH_PARAM_4, FOREACH_PARAM_3, FOREACH_PARAM_2, FOREACH_PARAM_1)(func, __VA_ARGS__)) -#define FOREACH_ENUM_PARAM(func, ...) TOML_EXPAND(GET_FOREACH_MACRO(__VA_ARGS__, FOREACH_ENUM_PARAM_21, FOREACH_ENUM_PARAM_20, FOREACH_ENUM_PARAM_19, FOREACH_ENUM_PARAM_18, FOREACH_ENUM_PARAM_17, FOREACH_ENUM_PARAM_16, FOREACH_ENUM_PARAM_15, FOREACH_ENUM_PARAM_14, FOREACH_ENUM_PARAM_13, FOREACH_ENUM_PARAM_12, FOREACH_ENUM_PARAM_11, FOREACH_ENUM_PARAM_10, FOREACH_ENUM_PARAM_9, FOREACH_ENUM_PARAM_8, FOREACH_ENUM_PARAM_7, FOREACH_ENUM_PARAM_6, FOREACH_ENUM_PARAM_5, FOREACH_ENUM_PARAM_4, FOREACH_ENUM_PARAM_3, FOREACH_ENUM_PARAM_2, FOREACH_ENUM_PARAM_1)(func, __VA_ARGS__)) +#define FOREACH_PARAM(func, ...) MACRO_EXPAND(GET_FOREACH_MACRO(__VA_ARGS__, FOREACH_PARAM_21, FOREACH_PARAM_20, FOREACH_PARAM_19, FOREACH_PARAM_18, FOREACH_PARAM_17, FOREACH_PARAM_16, FOREACH_PARAM_15, FOREACH_PARAM_14, FOREACH_PARAM_13, FOREACH_PARAM_12, FOREACH_PARAM_11, FOREACH_PARAM_10, FOREACH_PARAM_9, FOREACH_PARAM_8, FOREACH_PARAM_7, FOREACH_PARAM_6, FOREACH_PARAM_5, FOREACH_PARAM_4, FOREACH_PARAM_3, FOREACH_PARAM_2, FOREACH_PARAM_1)(func, __VA_ARGS__)) // clang-format off // vim: set foldmethod=syntax foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen : diff --git a/include/util/toml.hpp b/include/util/toml.hpp index e6a82a0..a6ba897 100644 --- a/include/util/toml.hpp +++ b/include/util/toml.hpp @@ -1,6 +1,6 @@ -/* toml.hpp +/* util/toml.hpp * Copyright © 2024 Saul D. Beniquez - * License: Mozilla Public License v. 2.0 + * License: Mozilla Public License v2.0 (MPL2) * * This Source Code Form is subject to the terms of the Mozilla Public License, * v.2.0. If a copy of the MPL was not distributed with this file, You can @@ -9,152 +9,151 @@ #pragma once -#include -#include -#include -#include -#include - #include #include "../Exception.hpp" -#include "./macros.hpp" +#include "macros.hpp" -template -constexpr bool is_toml_type_t = toml::impl::can_partially_represent_native; +inline namespace TOML { -namespace IOCore { -struct TomlException : public Exception { +struct TomlException : public IOCore::Exception { TomlException() - : Exception("TOML Serialization/Deseralization Exception") + : IOCore::Exception("TOML Serialization/Deseralization Exception") { } - explicit TomlException(const std::string& message) : Exception(message) + explicit TomlException(const std::string& message) + : IOCore::Exception(message) { } ~TomlException() override = default; }; + +template +constexpr bool is_toml_type_t = toml::impl::can_partially_represent_native; + +template +void to_toml(toml::table&, const T&); + +template +void from_toml(const toml::table&, T&); + +template +auto create_toml(const T& obj) -> toml::table +{ + toml::table tbl; + to_toml(tbl, obj); + return tbl; } -namespace IOCore::TOML { - -struct Serializer { - template - static auto to_table(const TClass& obj) -> toml::table // NOLINT - { - using class_t = std::decay_t; - auto result = class_t::to_toml(obj); - return result; - } - template - static void from_table(const toml::table& tbl, TClass& result) // NOLINT - { - using class_t = std::decay_t; - class_t::from_toml(tbl, result); - } - - template - static void insert_field( // NOLINT - toml::table& tbl, const char* fieldName, const TField& obj - ) - { - using value_type = std::decay_t; +template +inline void +add_toml_field(const TField& obj, const char* fieldName, toml::table& tbl) +{ + using value_type = std::decay_t; + if constexpr (std::is_enum_v) { + add_toml_enum_field(obj, fieldName, tbl); + } else if constexpr (std::is_same_v) { + tbl.insert_or_assign(fieldName, static_cast(obj)); + return; + } else { if constexpr (is_toml_type_t) { tbl.insert_or_assign(fieldName, obj); } else { - auto subtable = to_table(obj); + toml::table subtable; + to_toml(subtable, obj); tbl.insert_or_assign(fieldName, subtable); } } - template - static void extract_field( // NOLINT - const toml::table& tbl, const char* fieldName, TField& output - ) - { - using value_type = std::decay_t; +} +template +inline void +extract_toml_field(const toml::table& tbl, const char* fieldName, TField& output) +{ + using value_type = std::decay_t; - if (!tbl.contains(fieldName)) { - throw IOCore::TomlException( - "Missing field " + std::string(fieldName) - ); - } - if constexpr (is_toml_type_t) { - output = tbl[fieldName].value().value(); - } else { - auto subtable = *(tbl[fieldName].as_table()); - from_table(subtable, output); - } + if (!tbl.contains(fieldName)) { + throw TomlException("Missing field " + std::string(fieldName)); + } + + if constexpr (std::is_enum_v) { + extract_toml_enum_field(tbl, fieldName, output); + } else if constexpr (std::is_same_v) { + output = tbl[fieldName].value().value(); + } else if constexpr (is_toml_type_t) { + output = tbl[fieldName].value().value(); + } else { + auto subtable = *(tbl[fieldName].as_table()); + from_toml(subtable, output); } -}; } -#define IOCORE_TOML_FIELD(field) \ - IOCore::TOML::Serializer::insert_field( \ - tbl, #field, obj.field \ - ); -#define IOCORE_TOML_EXTRACT_FIELD(field) \ - IOCore::TOML::Serializer::extract_field( \ - tbl, #field, result.field \ - ); +} // namespace TOML -#define IOCORE_TOML_SERIALIZABLE(CLASS, ...) \ - static constexpr auto _class_name()->const char* \ +#define INSERT_FIELD(FIELD) add_toml_field(obj.FIELD, #FIELD, table); +#define EXTRACT_FIELD(FIELD) extract_toml_field(table, #FIELD, obj.FIELD); + +#define ENUM_FIELD_ENTRY(field) NAMED_PAIR(field), + +#define TOML_STRUCT(STRUCT_TYPE, ...) \ + inline void to_toml(toml::table& table, const STRUCT_TYPE& obj) \ { \ - return #CLASS; \ + FOREACH_PARAM(INSERT_FIELD, __VA_ARGS__); \ } \ \ - static auto to_toml(const CLASS& obj)->toml::table \ + inline void from_toml(const toml::table& table, STRUCT_TYPE& obj) \ { \ - toml::table tbl; \ - FOREACH_PARAM(IOCORE_TOML_FIELD, __VA_ARGS__) \ - return tbl; \ + FOREACH_PARAM(EXTRACT_FIELD, __VA_ARGS__); \ + } + +#define TOML_CLASS(CLASS_TYPE, ...) \ + inline friend void to_toml(toml::table& table, const CLASS_TYPE& obj) \ + { \ + FOREACH_PARAM(INSERT_FIELD, __VA_ARGS__); \ } \ \ - static void from_toml(const toml::table& tbl, CLASS& result) \ + inline friend void from_toml(const toml::table& table, CLASS_TYPE& obj) \ { \ - FOREACH_PARAM(IOCORE_TOML_EXTRACT_FIELD, __VA_ARGS__) \ - } \ - friend class IOCore::TOML::Serializer; + FOREACH_PARAM(EXTRACT_FIELD, __VA_ARGS__); \ + } -#define IOCORE_TOML_ENUM(ENUM_TYPE, ...) \ - template<> \ - inline void IOCore::TOML::Serializer::insert_field( \ - toml::table & tbl, const char* fieldName, const ENUM_TYPE& obj \ +#define TOML_ENUM(ENUM_TYPE, ...) \ + inline void add_toml_enum_field( \ + const ENUM_TYPE& obj, const char* fieldName, toml::table& tbl \ ) \ { \ - constexpr bool enum_check = std::is_enum::value; \ - static_assert(enum_check, #ENUM_TYPE "must be an enum!"); \ - \ - using pair_t = std::pair; \ + static_assert( \ + std::is_enum::value, \ + #ENUM_TYPE "must be an enum!" \ + ); \ + using pair_t = std::pair; \ static const pair_t _enum_to_string[] = { \ - FOREACH_ENUM_PARAM(TOML_ENUM_FIELD, __VA_ARGS__) \ + FOREACH_PARAM(ENUM_FIELD_ENTRY, __VA_ARGS__) \ }; \ auto it = std::find_if( \ std::begin(_enum_to_string), \ std::end(_enum_to_string), \ [obj](const auto& pair) -> bool { \ - return pair.first == obj; \ + return pair.second == obj; \ } \ ); \ - tbl.insert_or_assign(fieldName, it->second); \ + tbl.insert_or_assign(fieldName, it->first); \ } \ - \ - template<> \ - inline void IOCore::TOML::Serializer::extract_field( \ + inline void extract_toml_enum_field( \ const toml::table& tbl, const char* fieldName, ENUM_TYPE& obj \ ) \ { \ - constexpr bool enum_check = std::is_enum::value; \ - static_assert(enum_check, #ENUM_TYPE "must be an enum!"); \ - \ - using pair_t = std::pair; \ + static_assert( \ + std::is_enum::value, \ + #ENUM_TYPE " must be an enum!" \ + ); \ + using pair_t = std::pair; \ static const pair_t _enum_to_string[] = { \ - FOREACH_ENUM_PARAM(TOML_ENUM_FIELD, __VA_ARGS__) \ + FOREACH_PARAM(ENUM_FIELD_ENTRY, __VA_ARGS__) \ }; \ auto val = tbl[fieldName].value().value(); \ - for (const auto& [enum_val, str] : _enum_to_string) { \ + for (const auto& [str, enum_val] : _enum_to_string) { \ if (str == val) { \ obj = enum_val; \ break; \ @@ -162,16 +161,12 @@ struct Serializer { } \ } -// clang-format off -#define IOCORE_TOML_SERIALIZE_IMPL(CLASS) \ - template<> \ - auto Serializer::to_table(const CLASS& obj) \ - ->toml::table - -#define IOCORE_TOML_DESERIALIZE_IMPL(CLASS) \ - template<> \ - auto Serializer::from_table(const toml::table& tbl, CLASS& result)\ - -> void +#define TOML_SERIALIZE_IMPL(CLASS) \ + template<> \ + void to_toml(toml::table & result, const CLASS& obj) +#define TOML_DESERIALIZE_IMPL(CLASS) \ + template<> \ + void from_toml(const toml::table& tbl, CLASS& result) // clang-format off -// vim: set foldmethod=marker foldmarker=@{,@} foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen : +// vim: set foldmethod=syntax foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen : diff --git a/include/util/toml.old.hpp b/include/util/toml.old.hpp new file mode 100644 index 0000000..db092d9 --- /dev/null +++ b/include/util/toml.old.hpp @@ -0,0 +1,155 @@ +/* toml.hpp + * Copyright © 2024 Saul D. Beniquez + * License: Mozilla Public License v. 2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v.2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../Exception.hpp" +#include "./macros.hpp" + +template +constexpr bool is_toml_type_t = toml::impl::can_partially_represent_native; + +namespace IOCore { +struct TomlException : public Exception { + + TomlException() + : Exception("TOML Serialization/Deseralization Exception") + { + } + explicit TomlException(const std::string& message) : Exception(message) + { + } + ~TomlException() override = default; +}; + +namespace TOML { +template +struct AdlSerializer { + static auto to_toml(const TClass& obj) + { + using class_t = std::decay_t; + return class_t::to_toml(obj); + } + static void from_toml(const toml::table& tbl, TClass& result) + { + using class_t = std::decay_t; + class_t::from_toml(tbl, result); + } + + template + static void + insert_field(toml::table& tbl, const char* fieldName, const TField& obj) + { + using value_type = std::decay_t; + + if constexpr (is_toml_type_t) { + tbl.insert_or_assign(fieldName, obj); + } else { + if constexpr (std::is_enum_v) { + AdlSerializer::insert_enum_field() + } else { + auto subtable = AdlSerializer::to_toml(obj); + tbl.insert_or_assign(fieldName, subtable); + } + } + template + static void insert_enum_field( + toml::table& tbl, const char* fieldName, TField obj + ); + + + template + static void extract_field( + const toml::table& tbl, const char* fieldName, TField& output + ) + { + using value_type = std::decay_t; + + if (!tbl.contains(fieldName)) { + throw IOCore::TomlException( + "Missing field " + std::string(fieldName) + ); + } + if constexpr (is_toml_type_t) { + output = tbl[fieldName].value().value(); + } else { + auto subtable = *(tbl[fieldName].as_table()); + AdlSerializer::from_toml(subtable, output); + } + } +}; +} +} + +#define IOCORE_TOML_FIELD(field) \ + IOCore::TOML::AdlSerializer::insert_field(tbl, #field, obj.field); +#define IOCORE_TOML_EXTRACT_FIELD(field) \ + IOCore::TOML::AdlSerializer::extract_field(tbl, #field, result.field); + +#define IOCORE_TOML_SERIALIZABLE(CLASS, ...) \ + private: \ + static constexpr auto _class_name()->const char* \ + { \ + return #CLASS; \ + } \ + \ + static auto to_toml(const CLASS& obj)->toml::table \ + { \ + toml::table tbl; \ + FOREACH_PARAM(IOCORE_TOML_FIELD, __VA_ARGS__) \ + return tbl; \ + } \ + \ + static void from_toml(const toml::table& tbl, CLASS& result) \ + { \ + FOREACH_PARAM(IOCORE_TOML_EXTRACT_FIELD, __VA_ARGS__) \ + } \ + friend IOCore::TOML::AdlSerializer; \ + ; + +#define IOCORE_TOML_ENUM(ENUM_TYPE, ...) \ + template \ + void IOCore::TOML::AdlSerializer::insert_field( \ + toml::table& tbl, const char* fieldName, ENUM_TYPE obj \ + ) \ + { \ + ; \ + } \ + template \ + void IOCore::TOML::AdlSerializer::extract_field( \ + const toml::table& tbl, const char* fieldName, ENUM_TYPE& output \ + ) \ + { \ + ; \ + } + +#define IOCORE_TOML_SERIALIZE_IMPL(CLASS) \ + template<> \ + auto AdlSerializer::to_table(const CLASS& obj)->toml::table + +#define IOCORE_TOML_DESERIALIZE_IMPL(CLASS) \ + template<> \ + void AdlSerializer::from_table( \ + const toml::table& tbl, CLASS& result \ + ) + +// clang-format off +// vim: set foldmethod=marker foldmarker=@{,@} foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen : diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 99baeee..dd79c0a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ add_library(IOCore OBJECT Exception.cpp FileResource.cpp debuginfo.cpp - JsonConfigFile.cpp + #JsonConfigFile.cpp TomlConfigFile.cpp ) diff --git a/src/TomlConfigFile.cpp b/src/TomlConfigFile.cpp index 8251809..9c9889a 100644 --- a/src/TomlConfigFile.cpp +++ b/src/TomlConfigFile.cpp @@ -8,6 +8,7 @@ */ #include "TomlConfigFile.hpp" +#include "util/toml.hpp" #include "Exception.hpp" #include "sys/debuginfo.hpp" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 56eae3a..f668271 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,13 +1,13 @@ # Define the executable 'test-runner' add_executable(test-runner runtime-tests.cpp + Exception.test.cpp Util.macros.test.cpp Util.toml.test.cpp TomlTable.test.cpp - Application.test.cpp - FileResource.test.cpp - Exception.test.cpp - JsonConfigFile.test.cpp + #Application.test.cpp + #FileResource.test.cpp + #JsonConfigFile.test.cpp TomlConfigFile.test.cpp ) diff --git a/tests/TomlConfigFile.test.cpp b/tests/TomlConfigFile.test.cpp index ca54ca1..77de806 100644 --- a/tests/TomlConfigFile.test.cpp +++ b/tests/TomlConfigFile.test.cpp @@ -16,6 +16,7 @@ #include "IOCore/util/debug_print.hpp" #include "test-utils/common.hpp" +#include "test-utils/serialization.hpp" #include #include @@ -33,45 +34,22 @@ c::string_constant kNonExistantPath = "/hades/tmp/dne/file.toml"; using TomlTable = IOCore::TomlTable; using TomlConfigFile = IOCore::TomlConfigFile; using StringDictionary = IOCore::Dictionary; -using Serializer = IOCore::TOML::Serializer; -IOCORE_TOML_SERIALIZE_IMPL(StringDictionary) +TOML_SERIALIZE_IMPL(StringDictionary) { - toml::table result; for (const auto& [key, value] : obj) { result.insert_or_assign(key, value); } - return result; } -IOCORE_TOML_DESERIALIZE_IMPL(StringDictionary) +TOML_DESERIALIZE_IMPL(StringDictionary) { result.clear(); for (auto [key, value] : tbl) { result.emplace(key, value.ref()); } } -struct SimpleStruct { - int field1; - int field2; - - auto operator==(const SimpleStruct& other) const -> bool - { - return field1 == other.field1 && field2 == other.field2; - } - - IOCORE_TOML_SERIALIZABLE(SimpleStruct, field1, field2); -}; - -struct ComplexStruct { - SimpleStruct field1; - int field2; - - IOCORE_TOML_SERIALIZABLE(ComplexStruct, field1, field2); -}; - BEGIN_TEST_SUITE("elemental::TomlConfigFile") { - namespace { // Test fixtures struct SampleFileGenerator { SampleFileGenerator() @@ -255,12 +233,24 @@ BEGIN_TEST_SUITE("elemental::TomlConfigFile") FIXTURE_TEST("TomlConfigFile works with ComplexStruct ") { auto config_file = TomlConfigFile(kInputFilePath); - config_file.set(ComplexStruct{ { 11, 22 }, 30 }); + config_file.set(ComplexStruct{ + { 11, 'c' }, { 30, 0 }, { 1, 2, Red }, Blue, ns::Windowed }); auto toml_data = config_file.getTomlTable(); - REQUIRE(toml_data["field1"]["field1"].value() == 11); - REQUIRE(toml_data["field1"]["field2"].value() == 22); - REQUIRE(toml_data["field2"].value() == 30); + REQUIRE(toml_data["part1"]["field1"].value() == 11); + REQUIRE(toml_data["part1"]["field2"].value() == 'c'); + REQUIRE(toml_data["part2"]["field1"].value() == 30); + REQUIRE(toml_data["part2"]["field2"].value() == 0); + + REQUIRE(toml_data["part3"]["field1"].value() == 1); + REQUIRE(toml_data["part3"]["field2"].value() == 2); + REQUIRE( + toml_data["part3"]["foreground"].value() == + "Red" + ); + + REQUIRE(toml_data["background"].value() == "Blue"); + REQUIRE(toml_data["mode"].value() == "Windowed"); } } // clang-format off diff --git a/tests/TomlTable.test.cpp b/tests/TomlTable.test.cpp index eb92fa1..c01a3d9 100644 --- a/tests/TomlTable.test.cpp +++ b/tests/TomlTable.test.cpp @@ -11,81 +11,54 @@ #include "test-utils/common.hpp" -#include "../include/TomlTable.hpp" -#include "../include/util/toml.hpp" +#include "IOCore/TomlTable.hpp" +#include "IOCore/util/toml.hpp" -enum Colors { Red, Green, Blue }; -IOCORE_TOML_ENUM(Colors, Red, Green, Blue); +#include "test-utils/serialization.hpp" BEGIN_TEST_SUITE("IOCore::TomlTable") { using IOCore::TomlTable; - struct SimpleStruct { - int field1; - int field2; - - auto operator==(const SimpleStruct& other) const -> bool - { - return field1 == other.field1 && field2 == other.field2; - } - - IOCORE_TOML_SERIALIZABLE(SimpleStruct, field1, field2); - }; - struct SimpleStruct2 { - int field1; - int field2; - std::string label; - - auto operator==(const SimpleStruct2& other) const -> bool - { - return label == other.label && field1 == other.field1 && - field2 == other.field2; - } - - IOCORE_TOML_SERIALIZABLE(SimpleStruct2, field1, field2, label); - }; - - struct ComplexStruct { - SimpleStruct field1; - int field2; - Colors foreground; - - IOCORE_TOML_SERIALIZABLE( - ComplexStruct, field1, field2, foreground - ); - }; - TEST_CASE("IOCore::TomlTable class construction") { TomlTable table; } TEST_CASE("IOCore::TomlTable core operators, simple struct") { - auto data = SimpleStruct2{ 11, 22, "Hello" }; + auto data = SimpleClass{ 11, 22 }; TomlTable table = data; - CHECK(table.size() == 3); + CHECK(table.size() == 2); CHECK(table["field1"].value() == 11); CHECK(table["field2"].value() == 22); - REQUIRE(table["label"].value() == "Hello"); } TEST_CASE("IOCore::TomlTable core operators complex struct") { - auto data = ComplexStruct{ { 11, 22 }, 30, Blue }; + auto data = ComplexStruct{}; + data.part1 = { 1, 'a' }; + data.part2 = { 3, 4 }; + data.part3 = { 5, 6, Blue }; + data.background = Red; + data.mode = ns::Windowed; + TomlTable table = data; - auto newdest = table.as(); + auto newdest = table.get(); - CHECK(table.size() == 3); + REQUIRE(table.size() == 5); - CHECK(table["field1"]["field1"].value() == 11); - CHECK(table["field1"]["field2"].value() == 22); - CHECK(table["field2"].value() == 30); - CHECK(table["foreground"].value() == "Blue"); + CHECK(table["part1"]["field1"].value() == 1); + CHECK(table["part1"]["field2"].value() == 'a'); + CHECK(table["part2"]["field1"].value() == 3); + CHECK(table["part2"]["field2"].value() == 4); + CHECK(table["part3"]["field1"].value() == 5); + CHECK(table["part3"]["field2"].value() == 6); - CHECK(newdest.field1 == data.field1); - CHECK(newdest.field2 == data.field2); - CHECK(newdest.foreground == data.foreground); + CHECK( + table["part3"]["foreground"].value() == "Blue" + ); + CHECK(table["background"].value() == "Red"); + CHECK(table["mode"].value() == "Windowed"); } } diff --git a/tests/Util.macros.test.cpp b/tests/Util.macros.test.cpp index c19da73..49269ee 100644 --- a/tests/Util.macros.test.cpp +++ b/tests/Util.macros.test.cpp @@ -10,7 +10,6 @@ #include "test-utils/common.hpp" #include "../include/util/macros.hpp" -#include "../include/util/toml.hpp" #include #include @@ -20,6 +19,8 @@ enum Colors { Red, Green, Blue }; +#define NAME_ENTRY(field) NAMED_PAIR(field), + BEGIN_TEST_SUITE("Util.Macros") { TEST_CASE("FOREACH_PARAM Macro works") @@ -34,14 +35,14 @@ BEGIN_TEST_SUITE("Util.Macros") TEST_CASE("FOREACH_ENUM_PARAM Macro works") { - std::pair color_pairs[] = { - FOREACH_ENUM_PARAM(TOML_ENUM_FIELD, Red, Green, Blue) + std::pair color_pairs[] = { + FOREACH_PARAM(NAME_ENTRY, Red, Green, Blue) }; CHECK(std::size(color_pairs) == 3); - REQUIRE(color_pairs[0].second == "Red"); - REQUIRE(color_pairs[1].second == "Green"); - REQUIRE(color_pairs[2].second == "Blue"); + REQUIRE(color_pairs[0].first == "Red"); + REQUIRE(color_pairs[1].first == "Green"); + REQUIRE(color_pairs[2].first == "Blue"); } } diff --git a/tests/Util.toml.test.cpp b/tests/Util.toml.test.cpp index 403ada2..cab494d 100644 --- a/tests/Util.toml.test.cpp +++ b/tests/Util.toml.test.cpp @@ -7,160 +7,109 @@ * obtain one at https://mozilla.org/MPL/2.0/. */ +#include #include #include "test-utils/common.hpp" +#include "test-utils/serialization.hpp" -#include "../include/util/toml.hpp" - -enum Colors { Red, Green, Blue }; -IOCORE_TOML_ENUM(Colors, Red, Green, Blue); +#include "IOCore/util/toml.hpp" BEGIN_TEST_SUITE("Util.Toml") { - struct SimpleStruct { - int field1; - int field2; - - IOCORE_TOML_SERIALIZABLE(SimpleStruct, field1, field2); - }; - struct SimpleStruct2 { - int fieldA; - - IOCORE_TOML_SERIALIZABLE(SimpleStruct2, fieldA); - }; - - struct StructWithEnum { - int field1; - int field2; - Colors foreground; - - IOCORE_TOML_SERIALIZABLE( - StructWithEnum, field1, field2, foreground - ); - }; - - struct CompositeStruct { - SimpleStruct part1; - SimpleStruct2 part2; - - IOCORE_TOML_SERIALIZABLE(CompositeStruct, part1, part2); - }; - - struct ComplexStruct { - SimpleStruct part1; - SimpleStruct2 part2; - StructWithEnum part3; - Colors background; - - IOCORE_TOML_SERIALIZABLE( - ComplexStruct, part1, part2, part3, background - ); - }; - TEST_CASE("IOCORE_TOML_TO Macro works") + TEST_CASE("SimpleStruct Serializes") { - toml::table tbl; - SimpleStruct obj; + SimpleStruct data{ 1 }; + toml::table result; + SimpleStruct deserialized; - IOCORE_TOML_FIELD(field1); - IOCORE_TOML_FIELD(field2); - REQUIRE(tbl.size() == 2); - } - TEST_CASE("IOCORE_TOML_SERIALIZABLE Macro works with SimpleStruct") - { + to_toml(result, data); + from_toml(result, deserialized); - toml::table table; - SimpleStruct data = { 10, 20 }; - SimpleStruct newdest; - - table = SimpleStruct::to_toml(data); - SimpleStruct::from_toml(table, newdest); - - CHECK(table.size() == 2); - - CHECK(table["field1"].value() == 10); - CHECK(table["field2"].value() == 20); - - CHECK(newdest.field1 == data.field1); - CHECK(newdest.field2 == data.field2); - } - TEST_CASE("IOCORE_TOML_SERIALIZABLE Macro works with StructWithEnum") - { - - toml::table table; - StructWithEnum data = { 11, 22, Green }; - StructWithEnum newdest; - - table = StructWithEnum::to_toml(data); - StructWithEnum::from_toml(table, newdest); - - CHECK(table.size() == 3); - - CHECK(table["field1"].value() == 11); - CHECK(table["field2"].value() == 22); - CHECK(table["foreground"].value() == "Green"); - - CHECK(newdest.field1 == data.field1); - CHECK(newdest.field2 == data.field2); - CHECK(newdest.foreground == data.foreground); + REQUIRE(result.size() == 2); + CHECK(result.at("field1").value() == 1); + CHECK(deserialized.field1 == 1); + CHECK(deserialized.field2 == 0); } - TEST_CASE("IOCORE_TOML_SERIALIZABLE Macro works with CompositeStruct") + TEST_CASE("SimpleClass Serializes") { + SimpleClass data{ 1, 22 }; + toml::table result; + SimpleClass deserialized; - toml::table table; - CompositeStruct data = { { 10, 20 }, { 30 } }; - CompositeStruct newdest; + to_toml(result, data); + from_toml(result, deserialized); - table = CompositeStruct::to_toml(data); - CompositeStruct::from_toml(table, newdest); + REQUIRE(result.size() == 2); + CHECK(result.at("field1").value() == 1); + CHECK(result.at("field2").value() == 22); - CHECK(table.size() == 2); - - CHECK(table["part1"].as_table()->size() == 2); - CHECK(table["part2"].as_table()->size() == 1); - - CHECK(table["part1"]["field1"].value() == 10); - CHECK(table["part1"]["field2"].value() == 20); - CHECK(table["part2"]["fieldA"].value() == 30); - - CHECK(newdest.part1.field1 == data.part1.field1); - CHECK(newdest.part1.field2 == data.part1.field2); - CHECK(newdest.part2.fieldA == data.part2.fieldA); + CHECK(deserialized.field1 == 1); + CHECK(deserialized.field2 == 22); } - TEST_CASE("IOCORE_TOML_SERIALIZABLE Macro works with ComplexStruct") + TEST_CASE("StructWithEnum Serializes") { - toml::table table; - ComplexStruct data = { - { 10, 20 }, { 30 }, { 40, 50, Blue }, Red + StructWithEnum data{ 1, 2, Colors::Green }; + toml::table result; + StructWithEnum deserialized; + + to_toml(result, data); + from_toml(result, deserialized); + + REQUIRE(result.size() == 3); + CHECK(result.at("field1").value() == 1); + CHECK(result.at("field2").value() == 2); + CHECK(result.at("foreground").value() == "Green"); + + CHECK(deserialized.field1 == data.field1); + CHECK(deserialized.field2 == data.field2); + CHECK(deserialized.foreground == data.foreground); + } + TEST_CASE("CompositeStruct Serializes") + { + CompositeStruct data{ { 1, 'c' }, { 3, 4 } }; + toml::table result; + CompositeStruct deserialized; + + to_toml(result, data); + from_toml(result, deserialized); + + REQUIRE(result.size() == 2); + REQUIRE(result.at("part1").as_table()->size() == 2); + REQUIRE(result.at("part2").as_table()->size() == 2); + + CHECK(deserialized.part1.field1 == data.part1.field1); + CHECK(deserialized.part1.field2 == data.part1.field2); + CHECK(deserialized.part2.field1 == data.part2.field1); + CHECK(deserialized.part2.field2 == data.part2.field2); + } + + TEST_CASE("ComplexStruct Serializes") + { + ComplexStruct data{ + { 1, 'c' }, { 3, 4 }, { 5, 6, Colors::Red }, Colors::Blue }; - ComplexStruct newdest; + toml::table result; + ComplexStruct deserialized; - table = ComplexStruct::to_toml(data); - ComplexStruct::from_toml(table, newdest); + to_toml(result, data); + from_toml(result, deserialized); - CHECK(table.size() == 4); + REQUIRE(result.size() == 5); + REQUIRE(result.at("part1").as_table()->size() == 2); + REQUIRE(result.at("part2").as_table()->size() == 2); + REQUIRE(result.at("part3").as_table()->size() == 3); - CHECK(table["part1"].as_table()->size() == 2); - CHECK(table["part2"].as_table()->size() == 1); - CHECK(table["part3"].as_table()->size() == 3); - - CHECK(table["part1"]["field1"].value() == 10); - CHECK(table["part1"]["field2"].value() == 20); - CHECK(table["part2"]["fieldA"].value() == 30); - CHECK(table["part3"]["field1"].value() == 40); - CHECK(table["part3"]["field2"].value() == 50); - CHECK( - table["part3"]["foreground"].value() == "Blue" - ); - - CHECK(newdest.part1.field1 == data.part1.field1); - CHECK(newdest.part1.field2 == data.part1.field2); - CHECK(newdest.part2.fieldA == data.part2.fieldA); - CHECK(newdest.part3.field1 == data.part3.field1); - CHECK(newdest.part3.field2 == data.part3.field2); - CHECK(newdest.part3.foreground == data.part3.foreground); - CHECK(newdest.background == data.background); + CHECK(deserialized.part1.field1 == data.part1.field1); + CHECK(deserialized.part1.field2 == data.part1.field2); + CHECK(deserialized.part2.field1 == data.part2.field1); + CHECK(deserialized.part2.field2 == data.part2.field2); + CHECK(deserialized.part3.field1 == data.part3.field1); + CHECK(deserialized.part3.field2 == data.part3.field2); + CHECK(deserialized.part3.foreground == data.part3.foreground); + CHECK(deserialized.background == data.background); + CHECK(deserialized.mode == data.mode); } } // clang-format off diff --git a/tests/test-utils/serialization.hpp b/tests/test-utils/serialization.hpp new file mode 100644 index 0000000..4ef7552 --- /dev/null +++ b/tests/test-utils/serialization.hpp @@ -0,0 +1,88 @@ +/* test-util/serialization.hpp + * Copyright © 2024 Saul D. Beniquez + * License: Mozilla Public License v. 2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v.2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "IOCore/util/toml.hpp" + +inline namespace util { +inline namespace serialization { + +enum Colors { Red, Green, Blue }; +TOML_ENUM(Colors, Red, Green, Blue); + +namespace ns { +enum WindowMode { Windowed, Fullscreen, Borderless }; +TOML_ENUM(WindowMode, Windowed, Fullscreen, Borderless); +} // namespace ns + +struct SimpleStruct { + int field1; + char field2; + + auto operator==(const SimpleStruct& other) const -> bool + { + return field1 == other.field1 && field2 == other.field2; + } +}; +TOML_STRUCT(SimpleStruct, field1, field2); + +class SimpleClass { + public: + int field1; + int field2; + + auto operator==(const SimpleClass& other) const -> bool + { + return field1 == other.field1 && field2 == other.field2; + } + + private: + TOML_CLASS(SimpleClass, field1, field2); +}; + +struct StructWithEnum { + int field1; + int field2; + Colors foreground; + + auto operator==(const StructWithEnum& other) const -> bool + { + return field1 == other.field1 && field2 == other.field2 && + foreground == other.foreground; + } + TOML_CLASS(StructWithEnum, field1, field2, foreground); +}; + +struct CompositeStruct { + SimpleStruct part1; + SimpleClass part2; + + auto operator==(const CompositeStruct& other) const -> bool + { + return part1 == other.part1 && part2 == other.part2; + } + TOML_CLASS(CompositeStruct, part1, part2); +}; + +struct ComplexStruct { + SimpleStruct part1; + SimpleClass part2; + StructWithEnum part3; + Colors background; + ns::WindowMode mode; + + TOML_CLASS(ComplexStruct, part1, part2, part3, background, mode); +}; + +} // namespace serialization +} // namespace util + +// clang-format off +// vim: set foldmethod=syntax foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen :