From a0a0df6f198c5fcc9571a94beb4f98119130246c Mon Sep 17 00:00:00 2001 From: S David <2100425+s-daveb@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:06:31 -0400 Subject: [PATCH] Add Toml support using toml++ and custom preprocessor macros --- .gitignore | 10 +++-- .vim/local.vimrc | 3 +- include/util/macros.hpp | 43 ++++++++++++++++++ include/util/toml.hpp | 90 +++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 2 + tests/Util.macros.test.cpp | 33 ++++++++++++++ tests/Util.toml.test.cpp | 75 +++++++++++++++++++++++++++++++ tests/test-utils/common.hpp | 6 +-- 8 files changed, 254 insertions(+), 8 deletions(-) create mode 100644 include/util/macros.hpp create mode 100644 include/util/toml.hpp create mode 100644 tests/Util.macros.test.cpp create mode 100644 tests/Util.toml.test.cpp diff --git a/.gitignore b/.gitignore index 4026da9..cb8fc71 100644 --- a/.gitignore +++ b/.gitignore @@ -132,10 +132,14 @@ Thumbs.db venv/ env/ -*.bak - -.cache/ +# Build files +CMakeCache.txt compile_commands.json vim-debug +CMakeFiles/* +*.bak +.cache/ docs/ .vim/* +build/* +.cmake/* diff --git a/.vim/local.vimrc b/.vim/local.vimrc index 5e7c844..98dff90 100644 --- a/.vim/local.vimrc +++ b/.vim/local.vimrc @@ -1,4 +1,3 @@ - " editorconfig/local.vimrc " Copyright © 2023-2024 Saul D. Beniquez @{ " @@ -24,7 +23,7 @@ " ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE " POSSIBILITY OF SUCH DAMAGE. @} -let s:build_dir = 'debug' +let s:build_dir = 'build/Debug' let s:build_cores = 6 let s:make_args = '-C '. s:build_dir . ' -j ' . s:build_cores . ' all' diff --git a/include/util/macros.hpp b/include/util/macros.hpp new file mode 100644 index 0000000..01c6833 --- /dev/null +++ b/include/util/macros.hpp @@ -0,0 +1,43 @@ +/* + * 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/. + */ +// clang-format off +// +#pragma once + +#include + +#define IOCORE_EXPAND(x) x + +#define IOCORE_FOREACH_1(func, param) func(param) +#define IOCORE_FOREACH_2(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_1(func, __VA_ARGS__)) +#define IOCORE_FOREACH_3(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_2(func, __VA_ARGS__)) +#define IOCORE_FOREACH_4(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_3(func, __VA_ARGS__)) +#define IOCORE_FOREACH_5(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_4(func, __VA_ARGS__)) +#define IOCORE_FOREACH_6(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_5(func, __VA_ARGS__)) +#define IOCORE_FOREACH_7(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_6(func, __VA_ARGS__)) +#define IOCORE_FOREACH_8(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_7(func, __VA_ARGS__)) +#define IOCORE_FOREACH_9(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_8(func, __VA_ARGS__)) +#define IOCORE_FOREACH_10(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_9(func, __VA_ARGS__)) +#define IOCORE_FOREACH_11(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_10(func, __VA_ARGS__)) +#define IOCORE_FOREACH_12(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_11(func, __VA_ARGS__)) +#define IOCORE_FOREACH_13(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_12(func, __VA_ARGS__)) +#define IOCORE_FOREACH_14(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_13(func, __VA_ARGS__)) +#define IOCORE_FOREACH_15(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_14(func, __VA_ARGS__)) +#define IOCORE_FOREACH_16(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_15(func, __VA_ARGS__)) +#define IOCORE_FOREACH_17(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_16(func, __VA_ARGS__)) +#define IOCORE_FOREACH_18(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_17(func, __VA_ARGS__)) +#define IOCORE_FOREACH_19(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_18(func, __VA_ARGS__)) +#define IOCORE_FOREACH_20(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_19(func, __VA_ARGS__)) +#define IOCORE_FOREACH_21(func, param, ...) func(param); IOCORE_EXPAND(IOCORE_FOREACH_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 IOCORE_FOREACH(func, ...) IOCORE_EXPAND(GET_FOREACH_MACRO(__VA_ARGS__, IOCORE_FOREACH_21, IOCORE_FOREACH_20, IOCORE_FOREACH_19, IOCORE_FOREACH_18, IOCORE_FOREACH_17, IOCORE_FOREACH_16, IOCORE_FOREACH_15, IOCORE_FOREACH_14, IOCORE_FOREACH_13, IOCORE_FOREACH_12, IOCORE_FOREACH_11, IOCORE_FOREACH_10, IOCORE_FOREACH_9, IOCORE_FOREACH_8, IOCORE_FOREACH_7, IOCORE_FOREACH_6, IOCORE_FOREACH_5, IOCORE_FOREACH_4, IOCORE_FOREACH_3, IOCORE_FOREACH_2, IOCORE_FOREACH_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 new file mode 100644 index 0000000..9097502 --- /dev/null +++ b/include/util/toml.hpp @@ -0,0 +1,90 @@ +/* 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 "../Exception.hpp" +#include "./macros.hpp" + +#define IOCORE_TOML_TO(field) tbl.insert_or_assign(#field, obj.field); +#define IOCORE_TOML_FROM(field) \ + if (!tbl.contains(#field)) { \ + throw IOCore::Toml::TomlException("Missing field " #field); \ + } \ + \ + obj.field = tbl[#field].value().value(); + +#define IOCORE_INIT_METADATA(T) \ + auto metadata = toml::table(); \ + metadata.insert_or_assign("type", #T); \ + tbl.insert_or_assign("General", metadata); + +#define IOCORE_TOML_SERIALIZABLE(T, ...) \ + const char* _class_name = #T; \ + friend void to_toml_table(toml::table& tbl, const T& obj) \ + { \ + IOCORE_INIT_METADATA(T) \ + IOCORE_FOREACH(IOCORE_TOML_TO, __VA_ARGS__) \ + } \ + friend void from_toml_table(const toml::table& table, T& obj) \ + { \ + auto tbl = table; \ + tbl.erase("General"); \ + IOCORE_FOREACH(IOCORE_TOML_FROM, __VA_ARGS__) \ + } + +namespace IOCore::Toml { + +struct TomlException : public Exception { + + TomlException() + : Exception("TOML Serialization/Deseralization Exception") + { + } + explicit TomlException(const std::string& message) : Exception(message) + { + } + ~TomlException() override = default; +}; + +struct Table : public toml::table { + Table() = default; + Table(const toml::table& tbl) : toml::table(tbl) {} + Table(const Table& tbl) : toml::table(tbl) {} + Table(Table&& tbl) noexcept : toml::table(std::move(tbl)) {} + + template + Table(const T& obj) + { + to_toml_table(*this, obj); + } + + template + auto operator=(const T& obj) -> Table& + { + to_toml_table(*this, obj); + return *this; + } + + template + auto operator=(T&& obj) -> Table& + { + to_toml_table(*this, obj); + return *this; + } +}; +} + +// 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/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ea77b8..bf7b31e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,8 @@ add_executable(test-runner Application.test.cpp FileResource.test.cpp Exception.test.cpp + Util.macros.test.cpp + Util.toml.test.cpp ) target_include_directories(test-runner PRIVATE diff --git a/tests/Util.macros.test.cpp b/tests/Util.macros.test.cpp new file mode 100644 index 0000000..3129c66 --- /dev/null +++ b/tests/Util.macros.test.cpp @@ -0,0 +1,33 @@ +/* Util.test.cpp + * 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/. + */ + +#include "test-utils/common.hpp" + +#include "../include/util/macros.hpp" +#include "../include/util/toml.hpp" + +#include + +#define PRINT_MACRO(x) buffer << #x << ": " << x << ", " << std::flush; + +BEGIN_TEST_SUITE("Util.Macros") +{ + TEST_CASE("IOCORE_FOREACH Macro works") + { + std::stringstream buffer; + int field1 = 10; + int field2 = 20; + + IOCORE_FOREACH(PRINT_MACRO, field1, field2) + REQUIRE(buffer.str() == "field1: 10, field2: 20, "); + } +} + +// clang-format off +// vim: set foldmethod=syntax foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen : diff --git a/tests/Util.toml.test.cpp b/tests/Util.toml.test.cpp new file mode 100644 index 0000000..6724257 --- /dev/null +++ b/tests/Util.toml.test.cpp @@ -0,0 +1,75 @@ +/* Util.toml.test.cpp + * 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/. + */ + +#include "test-utils/common.hpp" + +#include "../include/util/toml.hpp" + +BEGIN_TEST_SUITE("Util.Toml") +{ + struct TestStruct { + int field1; + int field2; + + friend void + to_toml_table(toml::table& tbl, const TestStruct& obj) + { + IOCORE_TOML_TO(field1); + IOCORE_TOML_TO(field2); + } + }; + + struct SerializableStruct { + int field1; + int field2; + + IOCORE_TOML_SERIALIZABLE(SerializableStruct, field1, field2); + }; + + TEST_CASE("IOCORE_TOML_TO Macro works") + { + + toml::table table; + TestStruct data; + + to_toml_table(table, data); + REQUIRE(table.size() == 2); + } + TEST_CASE("IOCORE_TOML_SERIALIZABLE Macro works") + { + + toml::table table; + SerializableStruct data = { 11, 22 }; + SerializableStruct newdest; + + to_toml_table(table, data); + from_toml_table(table, newdest); + + REQUIRE(table.size() == 3); + + REQUIRE(table["field1"].value() == 11); + REQUIRE(table["field2"].value() == 22); + + REQUIRE(newdest.field1 == data.field1); + REQUIRE(newdest.field2 == data.field2); + } + TEST_CASE("Toml::Table class construction and basic operators") + { + IOCore::Toml::Table table = SerializableStruct{ 11, 22 }; + + REQUIRE(table.size() == 3); + REQUIRE( + table["General"]["type"].value().value() == + "SerializableStruct" + ); + } +} + +// clang-format off +// vim: set foldmethod=syntax foldminlines=10 textwidth=80 ts=8 sts=0 sw=8 noexpandtab ft=cpp.doxygen : diff --git a/tests/test-utils/common.hpp b/tests/test-utils/common.hpp index 68ead0c..6d2776d 100644 --- a/tests/test-utils/common.hpp +++ b/tests/test-utils/common.hpp @@ -14,13 +14,13 @@ #include #pragma GCC diagnostic ignored "-Wunused-variable" -#define BEGIN_TEST_SUITE(name) \ - static const char* TEST_SUITE_NAME = "[" name "]"; \ +#define BEGIN_TEST_SUITE(name) \ + static const char* TEST_SUITE_NAME = "[" name "]"; \ namespace #define TEST(testname) TEST_CASE(testname, TEST_SUITE_NAME) -#define TEST_WITH_FIXTURE(FixtureName, testname) \ +#define TEST_WITH_FIXTURE(FixtureName, testname) \ TEST_CASE_METHOD(FixtureName, testname, TEST_SUITE_NAME) #define FIXTURE_TEST(testname) TEST_WITH_FIXTURE(TestFixture, testname)