├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── README_CN.md ├── include └── config-loader │ ├── ConfigLoader.h │ ├── ConfigLoaderNS.h │ ├── Result.h │ ├── core │ ├── DefineSchema.h │ ├── ForEachField.h │ └── ReflectedTraits.h │ ├── deserialize │ ├── CompoundDeserializeTraits.h │ ├── DeserializeTraits.h │ ├── DeserializeTraitsDecl.h │ ├── Deserializer.h │ └── PrimitiveDeserializeTraits.h │ ├── parsers │ ├── JsonCppParser.h │ ├── TinyXML2Parser.h │ ├── UnsupportedParser.h │ └── YamlCppParser.h │ ├── serialize │ ├── CompoundSerializeTraits.h │ ├── PrimitiveSerializeTraits.h │ ├── SerializeTraits.h │ ├── SerializeTraitsDecl.h │ ├── Serializer.h │ └── TypeSerializer.h │ └── utils │ ├── Assertion.h │ ├── CommonTraits.h │ ├── ConstexprStringUtils.h │ ├── Log.h │ └── RepeatMacro.h ├── src └── utils │ └── GetFileContent.cpp ├── test └── ut │ ├── CMakeLists.txt │ ├── ConstexprStringTest.cpp │ ├── DeserializeConfig.h │ ├── DeserializerTest.cpp │ ├── JsonCppDeserializeTest.cpp │ ├── JsonCppTest.cpp │ ├── ReflectMacroTest.cpp │ ├── ResultChecking.cpp │ ├── SerializerTest.cpp │ ├── TinyXML2DeserializeTest.cpp │ ├── TinyXML2Test.cpp │ ├── TypeSerializerTest.cpp │ ├── UTSchema.h │ ├── UtilsTest.cpp │ ├── YamlCppDeserializeTest.cpp │ ├── YamlCppTest.cpp │ └── configs │ ├── json │ ├── Point.json │ ├── Rect.json │ ├── STLObj.json │ ├── SomeOfPoints.json │ └── Tree.json │ ├── xml │ ├── Point.xml │ ├── Rect.xml │ ├── STLObj.xml │ ├── SomeOfPoints.xml │ └── Tree.xml │ └── yml │ ├── Point.yml │ ├── Rect.yml │ ├── STLObj.yml │ ├── SomeOfPoints.yml │ └── Tree.yml └── third-party └── ThirdParty.cmake /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=cpp 2 | *.xml linguist-vendored -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: config-loader ci 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | gcc-build-and-test: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | with: 10 | submodules: recursive 11 | - uses: egor-tensin/setup-gcc@v1 12 | with: 13 | version: 11 14 | 15 | - name: build 16 | run: | 17 | mkdir build 18 | CXX=g++ cmake -H. -Bbuild 19 | cmake --build build -j 20 | - name: test 21 | run: | 22 | cd build/bin 23 | ./config_loader_test 24 | 25 | clang-build-and-test: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@master 29 | with: 30 | submodules: recursive 31 | - uses: egor-tensin/setup-clang@v1 32 | with: 33 | version: 13 34 | 35 | - name: build 36 | run: | 37 | mkdir build 38 | CXX=clang++ cmake -H. -Bbuild 39 | cmake --build build -j 40 | - name: test 41 | run: | 42 | cd build/bin 43 | ./config_loader_test 44 | 45 | msvc-build-and-test: 46 | runs-on: windows-latest 47 | steps: 48 | - uses: actions/checkout@master 49 | with: 50 | submodules: recursive 51 | - uses: ilammy/msvc-dev-cmd@v1 52 | 53 | - name: build 54 | run: | 55 | mkdir build 56 | cd build 57 | cmake -G Ninja .. 58 | ninja 59 | - name: test 60 | run: | 61 | cd build/bin 62 | .\config_loader_test.exe 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /cmake-build-*/ 2 | /.idea/ 3 | /build/ 4 | /.cache/ 5 | compile_commands.json 6 | /.vs 7 | /CMakeSettings.json 8 | /out 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third-party/tinyxml2"] 2 | path = third-party/tinyxml2 3 | url = https://github.com/leethomason/tinyxml2.git 4 | [submodule "third-party/Catch2"] 5 | path = third-party/Catch2 6 | url = https://github.com/catchorg/Catch2.git 7 | [submodule "third-party/jsoncpp"] 8 | path = third-party/jsoncpp 9 | url = https://github.com/open-source-parsers/jsoncpp.git 10 | [submodule "third-party/yaml-cpp"] 11 | path = third-party/yaml-cpp 12 | url = https://github.com/jbeder/yaml-cpp.git 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(config-loader) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | message(CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) 7 | 8 | option(${PROJECT_NAME}_BUILD_TESTS OFF) 9 | set(BUILD_TESTS ${PROJECT_NAME}_BUILD_TESTS) 10 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 11 | set (BUILD_TESTS ON) 12 | endif() 13 | 14 | option(${PROJECT_NAME}_ASAN OFF) 15 | set(USE_ASAN ${PROJECT_NAME}_ASAN) 16 | if (${USE_ASAN} AND (NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")) 17 | set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") 18 | set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") 19 | endif() 20 | 21 | ############################################################################### 22 | # thirdparty option 23 | option(HAS_TINYXML2 "enable tinyxml2" ON) 24 | option(HAS_JSONCPP "enable jsoncpp" ON) 25 | option(HAS_YAMLCPP "enable yamlcpp" ON) 26 | 27 | message(STATUS "enable tinyxml2: ${HAS_TINYXML2}") 28 | message(STATUS "enable jsoncpp: ${HAS_JSONCPP}") 29 | message(STATUS "enable yamlcpp: ${HAS_YAMLCPP}") 30 | 31 | ############################################################################### 32 | 33 | set(CONFIG_LOADER_INC 34 | include/config-loader/utils/RepeatMacro.h 35 | include/config-loader/core/DefineSchema.h 36 | include/config-loader/core/ReflectedTraits.h 37 | include/config-loader/ConfigLoaderNS.h 38 | include/config-loader/core/ForEachField.h 39 | include/config-loader/ConfigLoader.h 40 | include/config-loader/Result.h 41 | include/config-loader/deserialize/DeserializeTraitsDecl.h 42 | include/config-loader/deserialize/DeserializeTraits.h 43 | include/config-loader/deserialize/CompoundDeserializeTraits.h 44 | include/config-loader/deserialize/PrimitiveDeserializeTraits.h 45 | include/config-loader/deserialize/Deserializer.h 46 | include/config-loader/utils/Log.h 47 | include/config-loader/utils/Assertion.h 48 | include/config-loader/parsers/TinyXML2Parser.h 49 | include/config-loader/parsers/JsonCppParser.h 50 | include/config-loader/parsers/YamlCppParser.h 51 | include/config-loader/parsers/UnsupportedParser.h 52 | include/config-loader/serialize/Serializer.h 53 | include/config-loader/serialize/SerializeTraits.h 54 | include/config-loader/serialize/SerializeTraitsDecl.h 55 | include/config-loader/utils/CommonTraits.h 56 | include/config-loader/serialize/PrimitiveSerializeTraits.h 57 | include/config-loader/serialize/CompoundSerializeTraits.h 58 | include/config-loader/serialize/TypeSerializer.h 59 | include/config-loader/utils/ConstexprStringUtils.h) 60 | 61 | add_library(${PROJECT_NAME} STATIC 62 | ${CONFIG_LOADER_INC} 63 | src/utils/GetFileContent.cpp) 64 | 65 | include(third-party/ThirdParty.cmake) 66 | 67 | target_include_directories(${PROJECT_NAME} 68 | PUBLIC ${PROJECT_SOURCE_DIR}/include) 69 | 70 | target_link_libraries(${PROJECT_NAME} PUBLIC ${THIRD_PARTY_LIBRARIES}) 71 | 72 | install(TARGETS ${PROJECT_NAME} DESTINATION lib) 73 | install(DIRECTORY include/config-loader DESTINATION include) 74 | 75 | 76 | if (${BUILD_TESTS}) 77 | add_subdirectory(test/ut) 78 | endif() 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Netcan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # config-loader [中文版](README_CN.md) 2 | `config-loader` is a static reflection framework written in C++17 from **parse configuration file** to **native data structure**. It has the following characteristics: 3 | 4 | - Simple interface, users need to **define data structure** and provide corresponding **configuration file**, the framework uses meta-programming technology to generate **load** interface 5 | - The design conforms to the opening and closing principle, extends the data structure without modifying the framework 6 | - Currently supports XML, JSON and YAML format configuration files, a variety of methods can be **flexibly composed** 7 | - Lightweight, easy to integrate, less than ~1000 lines of code 8 | - Support nested data structure, STL container 9 | - Complete test cases 10 | - Support from native data structure to config file, stringify data structure 11 | 12 | Future plans: 13 | 14 | - Provide additional C++20 version 15 | 16 | ## Build & Run 17 | Build 18 | 19 | ```shell 20 | $ git clone --recursive https://github.com/netcan/config-loader.git 21 | $ cd config-loader 22 | $ mkdir build 23 | $ cd build 24 | $ cmake .. 25 | $ make -j 26 | ``` 27 | 28 | Run 29 | 30 | ```shell 31 | $ cd bin/ 32 | $ ./config_loader_test 33 | ``` 34 | 35 | ## Quickly start 36 | 37 | Firstly use `DEFINE_SCHEMA` macro to define the data structure: 38 | 39 | ```cpp 40 | // define and reflect a struct 41 | DEFINE_SCHEMA(Point, // struct Point { 42 | (double) x, // double x; 43 | (double) y); // double y; 44 | // }; 45 | 46 | // vector and string 47 | DEFINE_SCHEMA(SomeOfPoints, // struct SomeOfPoints { 48 | (std::string) name, // std::string name; 49 | (std::vector) points); // std::vector points; 50 | // }; 51 | ``` 52 | 53 | Provide configuration files, using `loadXML2Obj/loadJSON2Obj/loadYAML2Obj` interfaces: 54 | 55 | ```cpp 56 | SomeOfPoints someOfPoints; 57 | auto res = loadJSON2Obj(someOfPoints, [] { 58 | return R"( 59 | { 60 | "name": "Some of points", 61 | "points":[ 62 | { "x": 1.2, "y": 3.4 }, 63 | { "x": 5.6, "y": 7.8 }, 64 | { "x": 2.2, "y": 3.3 } 65 | ] 66 | } 67 | )"; 68 | }); 69 | REQUIRE(res == Result::SUCCESS); 70 | REQUIRE_THAT(someOfPoints.name, Equals("Some of points")); 71 | REQUIRE(someOfPoints.points.size() == 3); 72 | ``` 73 | 74 | Or, through an XML configuration file. 75 | ```cpp 76 | SomeOfPoints someOfPoints; 77 | auto res = loadXML2Obj(someOfPoints, "configs/xml/SomeOfPoints.xml"); 78 | REQUIRE(res == Result::SUCCESS); 79 | REQUIRE_THAT(someOfPoints.name, Equals("Some of points")); 80 | REQUIRE(someOfPoints.points.size() == 3); 81 | ``` 82 | 83 | Through a YAML configuration file. 84 | ```cpp 85 | SomeOfPoints someOfPoints; 86 | auto res = loadYAML2Obj(someOfPoints, [] { 87 | return R"( 88 | name: Some of points 89 | points: 90 | - x: 1.2 91 | y: 3.4 92 | - x: 5.6 93 | y: 7.8 94 | - x: 2.2 95 | y: 3.3 96 | )"; 97 | }); 98 | REQUIRE(res == Result::SUCCESS); 99 | REQUIRE_THAT(someOfPoints.name, Equals("Some of points")); 100 | REQUIRE(someOfPoints.points.size() == 3); 101 | ``` 102 | 103 | ## Notice 104 | The current framework depends on the following libraries: 105 | - `tinyxml2`, used for parsing xml configuration files 106 | - `jsoncpp`, used for parsing json configuration files 107 | - `yamlcpp`, used for parsing yaml configuration files 108 | 109 | In the future, these libraries may be enabled through CMake options to avoid unnecessary dependencies in actual use: only using xml will only rely on the xml parsing library. 110 | 111 | This framework requires configuration files to be provided in a standardized format. Taking XML as an example, the field name is required to correspond to the XML tag name, and the value corresponds to the text content of the XML; for the `map` data structure, the tag uses the attribute `name` as the key name. 112 | 113 | The semantics of the current error code. 114 | ```cpp 115 | enum class Result { 116 | SUCCESS, // parse successfully 117 | ERR_EMPTY_CONTENT, // The parsing file is empty 118 | ERR_ILL_FORMED, // Illegal parsing file 119 | ERR_MISSING_FIELD, // Missing field 120 | ERR_EXTRACTING_FIELD, // Failed to parse the value 121 | ERR_TYPE, // Type error 122 | }; 123 | ``` 124 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # config-loader 2 | `config-loader`是一个使用C++17编写的 **解析配置文件** 到 **原生数据结构** 的静态反射框架,它拥有如下特点: 3 | 4 | - 简单的接口,用户通过 **定义数据结构** 与提供对应的 **配置文件**,框架利用元编程技术生成 **读取** 接口 5 | - 设计符合开闭原则,扩展 数据结构 无需修改框架 6 | - 目前支持XML/JSON/YAML格式的配置文件,多种方式可以 **灵活组合** 7 | - 轻量级,容易集成,核心代码不到1000行 8 | - 支持 嵌套的数据结构、STL容器 9 | - 测试用例完备 10 | - 通过CMake选项来控制支持的格式 11 | 12 | 将来计划: 13 | 14 | - 支持从原生数据结构写到配置文件、打印数据结构 15 | - 提供额外的C++20版本 16 | 17 | ## 构建与运行 18 | 构建 19 | 20 | ```shell 21 | $ git clone --depth=1 --recursive https://github.com/netcan/config-loader.git 22 | $ cd config-loader 23 | $ mkdir build 24 | $ cd build 25 | $ cmake .. 26 | $ make -j 27 | ``` 28 | 29 | 运行 30 | 31 | ```shell 32 | $ cd bin/ 33 | $ ./config_loader_test 34 | ``` 35 | 36 | ## 快速上手 37 | 使用 `DEFINE_SCHEMA` 定义数据结构: 38 | 39 | ```cpp 40 | // define and reflect a struct 41 | DEFINE_SCHEMA(Point, // struct Point { 42 | (double) x, // double x; 43 | (double) y); // double y; 44 | // }; 45 | 46 | // vector and string 47 | DEFINE_SCHEMA(SomeOfPoints, // struct SomeOfPoints { 48 | (std::string) name, // std::string name; 49 | (std::vector) points); // std::vector points; 50 | // }; 51 | ``` 52 | 53 | 提供配置文件,使用`loadXML2Obj/loadJSON2Obj/loadYAML2Obj`等接口: 54 | 55 | ```cpp 56 | SomeOfPoints someOfPoints; 57 | auto res = loadJSON2Obj(someOfPoints, [] { 58 | return R"( 59 | { 60 | "name": "Some of points", 61 | "points":[ 62 | { "x": 1.2, "y": 3.4 }, 63 | { "x": 5.6, "y": 7.8 }, 64 | { "x": 2.2, "y": 3.3 } 65 | ] 66 | } 67 | )"; 68 | }); 69 | REQUIRE(res == Result::SUCCESS); 70 | REQUIRE_THAT(someOfPoints.name, Equals("Some of points")); 71 | REQUIRE(someOfPoints.points.size() == 3); 72 | ``` 73 | 74 | 又或者,通过XML配置文件。 75 | ```cpp 76 | SomeOfPoints someOfPoints; 77 | auto res = loadXML2Obj(someOfPoints, "configs/xml/SomeOfPoints.xml"); 78 | REQUIRE(res == Result::SUCCESS); 79 | REQUIRE_THAT(someOfPoints.name, Equals("Some of points")); 80 | REQUIRE(someOfPoints.points.size() == 3); 81 | ``` 82 | 83 | 通过YAML配置文件。 84 | ```cpp 85 | SomeOfPoints someOfPoints; 86 | auto res = loadYAML2Obj(someOfPoints, [] { 87 | return R"( 88 | name: Some of points 89 | points: 90 | - x: 1.2 91 | y: 3.4 92 | - x: 5.6 93 | y: 7.8 94 | - x: 2.2 95 | y: 3.3 96 | )"; 97 | }); 98 | REQUIRE(res == Result::SUCCESS); 99 | REQUIRE_THAT(someOfPoints.name, Equals("Some of points")); 100 | REQUIRE(someOfPoints.points.size() == 3); 101 | ``` 102 | 103 | ## 注意事项 104 | 当前框架依赖如下几个库: 105 | - `tinyxml2`,解析xml配置文件用 106 | - `jsoncpp`,解析json配置文件用 107 | - `yamlcpp`,解析yml配置文件用 108 | 109 | 将来可能通过CMake选项来使能这些库,避免在实际使用中产生不必要的依赖:只用xml就只依赖xml的解析库。 110 | 111 | 本框架需要配置文件按规范的格式提供。以XML为例,要求字段名与XML标签名对应,值与XML的文本内容对应;对于`map`数据结构,标签通过属性`name`作为Key名。 112 | 113 | 当前错误码语义。 114 | ```cpp 115 | enum class Result { 116 | SUCCESS, // 解析成功 117 | ERR_EMPTY_CONTENT, // 解析文件为空 118 | ERR_ILL_FORMED, // 解析文件非法 119 | ERR_MISSING_FIELD, // 丢失字段 120 | ERR_EXTRACTING_FIELD, // 解析值失败 121 | ERR_TYPE, // 类型错误 122 | }; 123 | ``` 124 | -------------------------------------------------------------------------------- /include/config-loader/ConfigLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/04. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_CONFIGLOADER_H 6 | #define CONFIG_LOADER_CONFIGLOADER_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #endif //CONFIG_LOADER_CONFIGLOADER_H 17 | -------------------------------------------------------------------------------- /include/config-loader/ConfigLoaderNS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/06/28. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_CONFIGLOADERNS_H 6 | #define CONFIG_LOADER_CONFIGLOADERNS_H 7 | 8 | #define CONFIG_LOADER_NS config_loader 9 | #define CONFIG_LOADER_NS_BEGIN namespace CONFIG_LOADER_NS { 10 | #define CONFIG_LOADER_NS_END } 11 | 12 | #endif //CONFIG_LOADER_CONFIGLOADERNS_H 13 | -------------------------------------------------------------------------------- /include/config-loader/Result.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/05. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_RESULT_H 6 | #define CONFIG_LOADER_RESULT_H 7 | #include 8 | #include 9 | 10 | CONFIG_LOADER_NS_BEGIN 11 | 12 | enum class Result: uint8_t { 13 | SUCCESS, 14 | ERR_EMPTY_CONTENT, 15 | ERR_ILL_FORMED, 16 | ERR_MISSING_FIELD, 17 | ERR_EXTRACTING_FIELD, 18 | ERR_TYPE, 19 | ERR_UNSUPPORTED_PARSER, 20 | }; 21 | 22 | CONFIG_LOADER_NS_END 23 | 24 | #endif //CONFIG_LOADER_RESULT_H 25 | -------------------------------------------------------------------------------- /include/config-loader/core/DefineSchema.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/6/21. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_DEFINESCHEMA_H 6 | #define CONFIG_LOADER_DEFINESCHEMA_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define FIELD_EACH(i, arg) \ 13 | PAIR(arg); \ 14 | template \ 15 | struct FIELD { \ 16 | T& obj; \ 17 | FIELD(T& obj): obj(obj) {} \ 18 | auto value() -> decltype(auto) { \ 19 | return (obj.STRIP(arg)); \ 20 | } \ 21 | static constexpr const char* name() { \ 22 | return EXPAND(STRING(STRIP(arg))); \ 23 | } \ 24 | }; \ 25 | 26 | #define DEFINE_SCHEMA(st, ...) \ 27 | struct st { \ 28 | template struct FIELD; \ 29 | static constexpr size_t _field_count_ = GET_ARG_COUNT(__VA_ARGS__); \ 30 | static constexpr decltype(#st) _schema_name_ = #st; \ 31 | EXPAND(PASTE(REPEAT_, GET_ARG_COUNT(__VA_ARGS__)) (FIELD_EACH, 0, __VA_ARGS__)) \ 32 | } \ 33 | 34 | #define ALIAS_COMPOUND_TYPE(_alias, _compoundType) \ 35 | struct _alias: PARE _compoundType { \ 36 | static constexpr decltype(#_alias) _schema_name_ = #_alias; \ 37 | using self = PARE _compoundType; \ 38 | using self::self; \ 39 | }; \ 40 | CONFIG_LOADER_NS_BEGIN \ 41 | template<> \ 42 | struct CompoundDeserializeTraits<_alias>: \ 43 | CompoundDeserializeTraits<_alias::self>{ }; \ 44 | template<> \ 45 | struct CompoundSerializeTraits<_alias>: \ 46 | CompoundSerializeTraits<_alias::self>{ }; \ 47 | CONFIG_LOADER_NS_END \ 48 | 49 | #endif //CONFIG_LOADER_DEFINESCHEMA_H 50 | -------------------------------------------------------------------------------- /include/config-loader/core/ForEachField.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/06/28. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_FOREACHFIELD_H 6 | #define CONFIG_LOADER_FOREACHFIELD_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | CONFIG_LOADER_NS_BEGIN 13 | 14 | namespace detail { 15 | // for check if `f' has Result value or not(void) 16 | struct DummyFieldInfo { 17 | int& value(); 18 | const char* name(); 19 | }; 20 | 21 | template 22 | constexpr auto forEachField(T &&obj, F &&f, std::index_sequence) { 23 | using TDECAY = std::decay_t; 24 | 25 | if constexpr (std::is_same_v())), Result>) { 26 | Result res{Result::SUCCESS}; 27 | (void) ( ( (res = f(typename TDECAY::template FIELD 28 | (std::forward(obj)))) == Result::SUCCESS) && ... ); 29 | return res; 30 | } else { 31 | (f(typename TDECAY::template FIELD(std::forward(obj))), ...); 32 | } 33 | } 34 | } 35 | 36 | template 37 | constexpr auto forEachField(T&& obj, F&& f) { 38 | return detail::forEachField(std::forward(obj), 39 | std::forward(f), 40 | std::make_index_sequence::_field_count_>{}); 41 | } 42 | 43 | CONFIG_LOADER_NS_END 44 | 45 | #endif //CONFIG_LOADER_FOREACHFIELD_H 46 | -------------------------------------------------------------------------------- /include/config-loader/core/ReflectedTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/06/28. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_REFLECTEDTRAITS_H 6 | #define CONFIG_LOADER_REFLECTEDTRAITS_H 7 | 8 | #include 9 | #include 10 | 11 | CONFIG_LOADER_NS_BEGIN 12 | 13 | template 14 | inline constexpr bool IsReflected_v = false; 15 | 16 | template 17 | inline constexpr bool IsReflected_v 18 | > 19 | = true; 20 | 21 | CONFIG_LOADER_NS_END 22 | 23 | #endif //CONFIG_LOADER_REFLECTEDTRAITS_H 24 | -------------------------------------------------------------------------------- /include/config-loader/deserialize/CompoundDeserializeTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_COMPOUNDDESERIALIZETRAITS_H 6 | #define CONFIG_LOADER_COMPOUNDDESERIALIZETRAITS_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | CONFIG_LOADER_NS_BEGIN 22 | 23 | template 24 | struct CompoundDeserializeTraits>> { 26 | template 27 | static Result deserialize(T& obj, ELEM_TYPE node) { 28 | if (! node.isValid()) { return Result::ERR_MISSING_FIELD; } 29 | return forEachField(obj, [&node](auto&& fieldInfo) { 30 | decltype(auto) fieldName = fieldInfo.name(); 31 | decltype(auto) value = fieldInfo.value(); 32 | auto res = CompoundDeserializeTraits> 33 | ::deserialize(value, node.toChildElem(fieldName)); 34 | if (res != Result::SUCCESS) { LOGE("error handle field: %s", fieldName); } 35 | return res; 36 | }); 37 | } 38 | }; 39 | 40 | template 41 | struct CompoundDeserializeTraits::isSupport>> { 43 | template 44 | static Result deserialize(T& obj, ELEM_TYPE node) { 45 | if (! node.isValid()) { return Result::ERR_MISSING_FIELD; } 46 | return PrimitiveDeserializeTraits::deserialize(obj, node.getValueText()); 47 | } 48 | }; 49 | 50 | //////////////////////////////////////////////////////////////////////////////// 51 | template // for container like list/vector but not string, code reuse 52 | struct SeqContainerDeserialize { 53 | template 54 | static Result deserialize(SEQ& container, ELEM_TYPE node) { 55 | if (! node.isValid()) { return Result::ERR_MISSING_FIELD; } 56 | using value_type = typename SEQ::value_type; 57 | return node.forEachElement([&container](auto&& item) { 58 | value_type value; 59 | CFL_EXPECT_SUCC(CompoundDeserializeTraits::deserialize(value, item)); 60 | container.push_back(std::move(value)); 61 | return Result::SUCCESS; 62 | }); 63 | } 64 | }; 65 | 66 | template // code reuse 67 | struct CompoundDeserializeTraits> 68 | : SeqContainerDeserialize> { }; 69 | 70 | template // code reuse 71 | struct CompoundDeserializeTraits> 72 | : SeqContainerDeserialize> { }; 73 | 74 | //////////////////////////////////////////////////////////////////////////////// 75 | template // for kv container like map/unordered_map, code reuse 76 | struct KVContainerDeserialize { 77 | template 78 | static Result deserialize(KV& container, ELEM_TYPE node) { 79 | if (! node.isValid()) { return Result::ERR_MISSING_FIELD; } 80 | using Key = typename KV::key_type; 81 | using Value = typename KV::mapped_type; 82 | 83 | return node.forEachElement([&container](auto&& item) { 84 | Key key; 85 | auto keyName = item.getKeyName(); 86 | if (keyName == nullptr) { return Result::ERR_EXTRACTING_FIELD; } 87 | CFL_EXPECT_SUCC(PrimitiveDeserializeTraits::deserialize(key, keyName)); 88 | 89 | Value value; // Value may be complex data struct 90 | CFL_EXPECT_SUCC(CompoundDeserializeTraits::deserialize(value, item)); 91 | 92 | if (auto [_, inserted] = container.emplace(std::move(key), std::move(value)); !inserted) { 93 | LOGI("inserted field %s fail, may contain duplicate key", keyName); 94 | } 95 | return Result::SUCCESS; 96 | }); 97 | } 98 | }; 99 | 100 | template 101 | struct CompoundDeserializeTraits> 102 | : KVContainerDeserialize> {}; 103 | 104 | template 105 | struct CompoundDeserializeTraits> 106 | : KVContainerDeserialize> {}; 107 | 108 | //////////////////////////////////////////////////////////////////////////////// 109 | template // for optional type 110 | struct CompoundDeserializeTraits> { 111 | template 112 | static Result deserialize(std::optional& obj, ELEM_TYPE node) { 113 | if (! node.isValid()) { return Result::SUCCESS; } 114 | T value; 115 | CFL_EXPECT_SUCC(CompoundDeserializeTraits::deserialize(value, node)); 116 | obj = std::move(value); 117 | return Result::SUCCESS; 118 | } 119 | }; 120 | 121 | //////////////////////////////////////////////////////////////////////////////// 122 | template // for sum type(variant) 123 | struct CompoundDeserializeTraits> { 124 | template 125 | static Result deserialize(std::variant& obj, ELEM_TYPE node) { 126 | if (! node.isValid()) { return Result::ERR_MISSING_FIELD; } 127 | auto buildVariant = [&obj, &node](auto&& value) { 128 | using type = std::remove_reference_t; 129 | auto res = CompoundDeserializeTraits::deserialize(value, node); 130 | if (res == Result::SUCCESS) { obj.template emplace(std::move(value)); } 131 | return res; 132 | }; 133 | bool success {false}; 134 | (void) ( ( success = (buildVariant(Ts{}) == Result::SUCCESS)) || ... ); 135 | return success ? Result::SUCCESS : Result::ERR_TYPE; 136 | } 137 | }; 138 | 139 | /////////////////////////////////////////////////////////////////////////////// 140 | template // for smart pointer like shared_ptr/unique_ptr, code reuse 141 | struct SmartPointDeserialize { 142 | template 143 | static Result deserialize(SP& sp, ELEM_TYPE node) { 144 | if (! node.isValid()) { return Result::ERR_MISSING_FIELD; } 145 | using SPElemType = typename SP::element_type; 146 | SPElemType value; 147 | CFL_EXPECT_SUCC(CompoundDeserializeTraits::deserialize(value, node)); 148 | sp.reset(new SPElemType(std::move(value))); 149 | return Result::SUCCESS; 150 | } 151 | }; 152 | 153 | template 154 | struct CompoundDeserializeTraits> 155 | : SmartPointDeserialize> {}; 156 | 157 | template 158 | struct CompoundDeserializeTraits> 159 | : SmartPointDeserialize> {}; 160 | 161 | CONFIG_LOADER_NS_END 162 | 163 | #endif //CONFIG_LOADER_COMPOUNDDESERIALIZETRAITS_H 164 | -------------------------------------------------------------------------------- /include/config-loader/deserialize/DeserializeTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_DESERIALIZETRAITS_H 6 | #define CONFIG_LOADER_DESERIALIZETRAITS_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #endif //CONFIG_LOADER_DESERIALIZETRAITS_H 13 | -------------------------------------------------------------------------------- /include/config-loader/deserialize/DeserializeTraitsDecl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_DESERIALIZETRAITSDECL_H 6 | #define CONFIG_LOADER_DESERIALIZETRAITSDECL_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | CONFIG_LOADER_NS_BEGIN 13 | template 14 | struct PrimitiveDeserializeTraits: detail::IsSupport {}; 15 | 16 | template 17 | struct CompoundDeserializeTraits; 18 | CONFIG_LOADER_NS_END 19 | #endif //CONFIG_LOADER_DESERIALIZETRAITSDECL_H 20 | -------------------------------------------------------------------------------- /include/config-loader/deserialize/Deserializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/05. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_DESERIALIZER_H 6 | #define CONFIG_LOADER_DESERIALIZER_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | CONFIG_LOADER_NS_BEGIN 14 | 15 | struct UnsupportedParser; 16 | 17 | std::string getFileContent(const char* path); 18 | 19 | namespace detail { 20 | template 21 | struct Deserializer { 22 | template>* = nullptr> 24 | static Result load(T& obj, GET_CONTENT&& loader) { 25 | std::string content(loader()); 26 | if (content.empty()) { return Result::ERR_EMPTY_CONTENT; } 27 | 28 | PARSER parser; 29 | CFL_EXPECT_SUCC(parser.parse(content.data())); 30 | 31 | auto firstElem = parser.toRootElemType(); 32 | if (! firstElem.isValid()) { return Result::ERR_MISSING_FIELD; } 33 | return CompoundDeserializeTraits::deserialize(obj, firstElem); 34 | } 35 | 36 | static Result load(T& obj, std::string_view path) { 37 | return load(obj, [&path] { 38 | return getFileContent(path.data()); 39 | }); 40 | } 41 | }; 42 | 43 | template 44 | struct Deserializer { 45 | template 46 | static Result load(T&, LOADER&&) { 47 | return Result::ERR_UNSUPPORTED_PARSER; 48 | } 49 | }; 50 | } 51 | 52 | /////////////////////////////////////////////////////////////////////////////// 53 | // xml helper 54 | template 55 | Result loadXML2Obj(T& obj, Content content) { 56 | return detail::Deserializer::load(obj, content); 57 | } 58 | 59 | // json helper 60 | template 61 | Result loadJSON2Obj(T& obj, Content content) { 62 | return detail::Deserializer::load(obj, content); 63 | } 64 | 65 | // yaml helper 66 | template 67 | Result loadYAML2Obj(T& obj, Content content) { 68 | return detail::Deserializer::load(obj, content); 69 | } 70 | 71 | CONFIG_LOADER_NS_END 72 | 73 | #endif //CONFIG_LOADER_DESERIALIZER_H 74 | -------------------------------------------------------------------------------- /include/config-loader/deserialize/PrimitiveDeserializeTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/07. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_PRIMITIVEDESERIALIZETRAITS_H 6 | #define CONFIG_LOADER_PRIMITIVEDESERIALIZETRAITS_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | CONFIG_LOADER_NS_BEGIN 16 | namespace detail { 17 | constexpr bool isHex(std::string_view num) { 18 | return (num.substr(0, 2) == "0x" || 19 | num.substr(0, 2) == "0X"); 20 | } 21 | } 22 | 23 | template 24 | struct PrimitiveDeserializeTraits>> 25 | : detail::IsSupport { 26 | static Result deserialize(Number &num, std::optional valueText) { 27 | if (! valueText.has_value()) { return Result::ERR_EXTRACTING_FIELD; } 28 | // do not treat int8_t/uint8_t as char type 29 | if constexpr(std::is_same_v || std::is_same_v) { 30 | num = std::stol(*valueText, nullptr, detail::isHex(*valueText) ? 16 : 10); 31 | return Result::SUCCESS; 32 | } else { 33 | std::stringstream ss; 34 | ss << *valueText; 35 | if (detail::isHex(*valueText)) { ss << std::hex; } 36 | ss >> num; 37 | return ss.fail() ? Result::ERR_EXTRACTING_FIELD : Result::SUCCESS; 38 | } 39 | } 40 | }; 41 | 42 | template<> 43 | struct PrimitiveDeserializeTraits 44 | : detail::IsSupport { 45 | static Result deserialize(bool &value, std::optional valueText) { 46 | if (! valueText.has_value()) { return Result::ERR_EXTRACTING_FIELD; } 47 | std::stringstream ss; 48 | ss << *valueText; 49 | ss >> value; 50 | if (!ss.fail()) { return Result::SUCCESS; } 51 | if (valueText == "true" || valueText == "True") { 52 | value = true; 53 | return Result::SUCCESS; 54 | } 55 | if (valueText == "false" || valueText == "False") { 56 | value = false; 57 | return Result::SUCCESS; 58 | } 59 | 60 | return Result::ERR_EXTRACTING_FIELD; 61 | } 62 | }; 63 | 64 | template<> 65 | struct PrimitiveDeserializeTraits 66 | : detail::IsSupport { 67 | static Result deserialize(std::string &str, std::optional valueText) { 68 | if (! valueText.has_value()) { return Result::ERR_EXTRACTING_FIELD; } 69 | str = std::move(*valueText); 70 | return Result::SUCCESS; 71 | } 72 | }; 73 | 74 | CONFIG_LOADER_NS_END 75 | 76 | #endif //CONFIG_LOADER_PRIMITIVEDESERIALIZETRAITS_H 77 | -------------------------------------------------------------------------------- /include/config-loader/parsers/JsonCppParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/10. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_JSONCPPPARSER_H 6 | #define CONFIG_LOADER_JSONCPPPARSER_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #if HAS_JSONCPP 17 | #include 18 | #endif 19 | 20 | CONFIG_LOADER_NS_BEGIN 21 | 22 | #if HAS_JSONCPP 23 | struct JsonCppParser { 24 | Result parse(std::string_view content) { 25 | Json::CharReaderBuilder builder; 26 | std::unique_ptr reader(builder.newCharReader()); 27 | return reader->parse(content.data(), content.data() + content.size(), 28 | &root , nullptr) 29 | ? Result::SUCCESS 30 | : Result::ERR_ILL_FORMED; 31 | } 32 | 33 | struct ElemType; 34 | 35 | ElemType toRootElemType() { 36 | return ElemType{root}; 37 | } 38 | 39 | struct ElemType { 40 | explicit ElemType(const Json::Value& elem, const char* keyName = {}) 41 | : keyName(keyName), elem(elem) {} 42 | bool isValid() const { return ! elem.isNull(); } 43 | ElemType toChildElem(const char* fieldName) const { 44 | if (! elem.isObject()) { 45 | return ElemType{Json::Value::nullSingleton()}; 46 | } 47 | return ElemType{elem[fieldName]}; 48 | } 49 | std::optional getValueText() const { 50 | if (elem.isObject() || elem.isArray()) return std::nullopt; 51 | return elem.asString(); 52 | } 53 | const char* getKeyName() const { 54 | return keyName; 55 | } 56 | 57 | template 58 | Result forEachElement(F&& f) const { 59 | switch (elem.type()) { 60 | // null type isn't iterable, handle it as empty container 61 | case Json::ValueType::nullValue: 62 | return Result::SUCCESS; 63 | case Json::ValueType::arrayValue: 64 | for (auto&& e: elem) { 65 | CFL_EXPECT_SUCC(f(ElemType{e})); 66 | } 67 | return Result::SUCCESS; 68 | case Json::ValueType::objectValue: { 69 | auto keys = elem.getMemberNames(); 70 | for (auto &&key: keys) { 71 | CFL_EXPECT_SUCC(f(ElemType{elem[key], key.c_str()})); 72 | } 73 | return Result::SUCCESS; 74 | } 75 | default: // otherwise, is error type 76 | return Result::ERR_TYPE; 77 | } 78 | } 79 | 80 | private: 81 | const char * keyName {}; 82 | const Json::Value elem; 83 | }; 84 | 85 | private: 86 | Json::Value root; 87 | }; 88 | #else 89 | using JsonCppParser = UnsupportedParser; 90 | #endif 91 | 92 | CONFIG_LOADER_NS_END 93 | 94 | 95 | #endif //CONFIG_LOADER_JSONCPPPARSER_H 96 | -------------------------------------------------------------------------------- /include/config-loader/parsers/TinyXML2Parser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/10. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_TINYXML2PARSER_H 6 | #define CONFIG_LOADER_TINYXML2PARSER_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef HAS_TINYXML2 15 | #include 16 | #endif 17 | 18 | CONFIG_LOADER_NS_BEGIN 19 | 20 | #ifdef HAS_TINYXML2 21 | struct TinyXML2Parser { 22 | Result parse(std::string_view content) { 23 | return doc.Parse(content.data()) == tinyxml2::XML_SUCCESS 24 | ? Result::SUCCESS 25 | : Result::ERR_ILL_FORMED; 26 | } 27 | struct ElemType; 28 | 29 | ElemType toRootElemType() { 30 | return ElemType{doc.FirstChildElement()}; 31 | } 32 | 33 | struct ElemType { 34 | explicit ElemType(const tinyxml2::XMLElement* elem): elem(elem) {} 35 | bool isValid() const { return elem != nullptr; } 36 | ElemType toChildElem(const char* fieldName) const { 37 | return ElemType{elem->FirstChildElement(fieldName)}; 38 | } 39 | std::optional getValueText() const { 40 | if (auto valueText = elem->GetText()) { 41 | return valueText; 42 | } 43 | return std::nullopt; 44 | } 45 | const char* getKeyName() const { 46 | if (auto attr = elem->FirstAttribute()) { 47 | return attr->Value(); 48 | } 49 | return nullptr; 50 | } 51 | template 52 | Result forEachElement(F&& f) const { 53 | for (auto item = elem->FirstChildElement() 54 | ; item 55 | ; item = item->NextSiblingElement()) { 56 | CFL_EXPECT_SUCC(f(ElemType{item})); 57 | } 58 | return Result::SUCCESS; 59 | } 60 | private: 61 | const tinyxml2::XMLElement* elem; 62 | }; 63 | 64 | private: 65 | tinyxml2::XMLDocument doc; 66 | }; 67 | #else 68 | using TinyXML2Parser = UnsupportedParser; 69 | #endif 70 | 71 | CONFIG_LOADER_NS_END 72 | 73 | #endif //CONFIG_LOADER_TINYXML2PARSER_H 74 | -------------------------------------------------------------------------------- /include/config-loader/parsers/UnsupportedParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/17. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_UNSUPPORTEDPARSER_H 6 | #define CONFIG_LOADER_UNSUPPORTEDPARSER_H 7 | #include 8 | #include 9 | 10 | CONFIG_LOADER_NS_BEGIN 11 | 12 | struct UnsupportedParser { }; 13 | 14 | CONFIG_LOADER_NS_END 15 | 16 | #endif //CONFIG_LOADER_UNSUPPORTEDPARSER_H 17 | -------------------------------------------------------------------------------- /include/config-loader/parsers/YamlCppParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/16. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_YAMLCPPPARSER_H 6 | #define CONFIG_LOADER_YAMLCPPPARSER_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if HAS_YAMLCPP 15 | #include 16 | #endif 17 | 18 | CONFIG_LOADER_NS_BEGIN 19 | 20 | #if HAS_YAMLCPP 21 | struct YamlCppParser { 22 | Result parse(std::string_view content) { 23 | root = YAML::Load(content.data()); 24 | return root.IsNull() 25 | ? Result::ERR_ILL_FORMED 26 | : Result::SUCCESS; 27 | } 28 | struct ElemType; 29 | 30 | ElemType toRootElemType() { 31 | return ElemType{root}; 32 | } 33 | 34 | struct ElemType { 35 | explicit ElemType(const YAML::Node &node, const char *keyName = {}) 36 | : keyName(keyName), node(node) {} 37 | 38 | bool isValid() const { return node.IsDefined(); } 39 | ElemType toChildElem(const char* fieldName) const { 40 | if (! node.IsMap()) { 41 | return ElemType{YAML::Node()}; 42 | } 43 | return ElemType{node[fieldName]}; 44 | } 45 | 46 | std::optional getValueText() const { 47 | if (node.IsMap() || node.IsSequence()) return std::nullopt; 48 | return node.as(); 49 | } 50 | 51 | const char* getKeyName() const { 52 | return keyName; 53 | } 54 | 55 | template 56 | Result forEachElement(F&& f) const { 57 | switch (node.Type()) { 58 | // null type isn't iterable, handle it as empty container 59 | case YAML::NodeType::Null: 60 | return Result::SUCCESS; 61 | case YAML::NodeType::Sequence: 62 | for(auto&& e: node) { 63 | CFL_EXPECT_SUCC(f(ElemType{e})); 64 | } 65 | return Result::SUCCESS; 66 | case YAML::NodeType::Map: 67 | for(auto&& e: node) { 68 | auto keyName = e.first.as(); 69 | CFL_EXPECT_SUCC(f(ElemType{e.second, keyName.c_str()})); 70 | } 71 | return Result::SUCCESS; 72 | default: // otherwise, is error type 73 | return Result::ERR_TYPE; 74 | } 75 | } 76 | 77 | private: 78 | const char* keyName {}; 79 | const YAML::Node node; 80 | }; 81 | 82 | private: 83 | YAML::Node root; 84 | }; 85 | #else 86 | using YamlCppParser = UnsupportedParser; 87 | #endif 88 | 89 | CONFIG_LOADER_NS_END 90 | 91 | #endif //CONFIG_LOADER_YAMLCPPPARSER_H 92 | -------------------------------------------------------------------------------- /include/config-loader/serialize/CompoundSerializeTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_COMPOUNDSERIALIZETRAITS_H 6 | #define CONFIG_LOADER_COMPOUNDSERIALIZETRAITS_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | CONFIG_LOADER_NS_BEGIN 13 | namespace detail { 14 | inline std::string indent(size_t depth) { 15 | constexpr size_t indentWidth = 2; 16 | return std::string(indentWidth * depth, ' '); 17 | } 18 | } 19 | 20 | template 21 | struct CompoundSerializeTraits>> { 23 | static void dump(std::ostream& out, const T& obj, size_t depth = 0) { 24 | out << "{"; 25 | forEachField(obj, [&out, depth](auto&& fieldInfo) { 26 | decltype(auto) fieldName = fieldInfo.name(); 27 | decltype(auto) value = fieldInfo.value(); 28 | out << "\n" << detail::indent(depth + 1) << "." << fieldName; 29 | CompoundSerializeTraits> 30 | ::dump(out, value, depth + 1); 31 | out << ","; 32 | }); 33 | out << "\n" << detail::indent(depth) << "}"; 34 | } 35 | }; 36 | 37 | template 38 | struct CompoundSerializeTraits::isSupport>> { 40 | static void dump(std::ostream& out, const T& obj, size_t = 0) { 41 | out << "{"; 42 | PrimitiveSerializeTraits::dump(out, obj); 43 | out << "}"; 44 | } 45 | }; 46 | 47 | //////////////////////////////////////////////////////////////////////////////// 48 | template // for container like list/vector but not string, code reuse 49 | struct SeqContainerSerialize { 50 | static void dump(std::ostream& out, const SEQ& container, size_t depth = 0) { 51 | out << "{"; 52 | for (auto&& v: container) { 53 | CompoundSerializeTraits>::dump(out, v, depth + 1); 54 | out << ", "; 55 | } 56 | out << "\n" << detail::indent(depth) << "}"; 57 | } 58 | }; 59 | 60 | template // code reuse 61 | struct CompoundSerializeTraits> 62 | : SeqContainerSerialize> { }; 63 | 64 | template // code reuse 65 | struct CompoundSerializeTraits> 66 | : SeqContainerSerialize> { }; 67 | 68 | //////////////////////////////////////////////////////////////////////////////// 69 | template // for kv container like map/unordered_map, code reuse 70 | struct KVContainerSerialize { 71 | static void dump(std::ostream& out, const KV& container, size_t depth = 0) { 72 | out << "{"; 73 | for (auto&& [k, v]: container) { 74 | out << "{"; 75 | CompoundSerializeTraits>::dump(out, k, depth + 1); 76 | out << ", "; 77 | CompoundSerializeTraits>::dump(out, v, depth + 1); 78 | out << "},"; 79 | } 80 | out << "\n" << detail::indent(depth) << "}"; 81 | } 82 | }; 83 | 84 | template 85 | struct CompoundSerializeTraits> 86 | : KVContainerSerialize> {}; 87 | 88 | template 89 | struct CompoundSerializeTraits> 90 | : KVContainerSerialize> {}; 91 | 92 | //////////////////////////////////////////////////////////////////////////////// 93 | template // for optional type 94 | struct CompoundSerializeTraits> { 95 | static void dump(std::ostream& out, const std::optional& obj, size_t depth = 0) { 96 | out << "{"; 97 | if (obj.has_value()) { 98 | CompoundSerializeTraits>::dump(out, *obj, depth + 1); 99 | } else { 100 | out << "std::nullopt"; 101 | } 102 | out << "}"; 103 | } 104 | }; 105 | 106 | /////////////////////////////////////////////////////////////////////////////// 107 | template // for shared_ptr 108 | struct CompoundSerializeTraits> { 109 | using SPType = std::shared_ptr; 110 | static void dump(std::ostream& out, const SPType& obj, size_t depth = 0) { 111 | if (obj == nullptr) { 112 | out << "nullptr"; 113 | } else { 114 | using ELEM_TYPE = typename SPType::element_type; 115 | out << TypeSerializer_v << "{new " << TypeSerializer_v; 116 | CompoundSerializeTraits::dump(out, *obj, depth + 1); 117 | out << "}"; 118 | } 119 | } 120 | }; 121 | 122 | //////////////////////////////////////////////////////////////////////////////// 123 | template // for sum type(variant) 124 | struct CompoundSerializeTraits> { 125 | static void dump(std::ostream& out, const std::variant& obj, size_t depth = 0) { 126 | out << "{"; 127 | std::visit([&](auto&& v) { 128 | using ValueType = std::decay_t; 129 | out << TypeSerializer_v; 130 | CompoundSerializeTraits::dump(out, v, depth + 1); 131 | }, obj); 132 | out << "}"; 133 | } 134 | }; 135 | 136 | CONFIG_LOADER_NS_END 137 | #endif //CONFIG_LOADER_COMPOUNDSERIALIZETRAITS_H 138 | -------------------------------------------------------------------------------- /include/config-loader/serialize/PrimitiveSerializeTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_PRIMITIVESERIALIZETRAITS_H 6 | #define CONFIG_LOADER_PRIMITIVESERIALIZETRAITS_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | CONFIG_LOADER_NS_BEGIN 13 | 14 | template 15 | struct PrimitiveSerializeTraits>> 16 | : detail::IsSupport { 17 | static void dump(std::ostream& out, Number number) { 18 | // do not treat int8_t/uint8_t as char type 19 | if constexpr(std::is_same_v || std::is_same_v) { 20 | out << +number; 21 | } else { 22 | out << number; 23 | } 24 | } 25 | }; 26 | 27 | template<> 28 | struct PrimitiveSerializeTraits 29 | : detail::IsSupport { 30 | static void dump(std::ostream& out, const std::string& str) { 31 | out << "\"" << str << "\""; 32 | } 33 | }; 34 | 35 | CONFIG_LOADER_NS_END 36 | #endif //CONFIG_LOADER_PRIMITIVESERIALIZETRAITS_H 37 | -------------------------------------------------------------------------------- /include/config-loader/serialize/SerializeTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_SERIALIZETRAITS_H 6 | #define CONFIG_LOADER_SERIALIZETRAITS_H 7 | #include 8 | #include 9 | #include 10 | #endif //CONFIG_LOADER_SERIALIZETRAITS_H 11 | -------------------------------------------------------------------------------- /include/config-loader/serialize/SerializeTraitsDecl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_SERIALIZETRAITSDECL_H 6 | #define CONFIG_LOADER_SERIALIZETRAITSDECL_H 7 | #include 8 | #include 9 | CONFIG_LOADER_NS_BEGIN 10 | template 11 | struct PrimitiveSerializeTraits: detail::IsSupport {}; 12 | 13 | template 14 | struct CompoundSerializeTraits; 15 | CONFIG_LOADER_NS_END 16 | #endif //CONFIG_LOADER_SERIALIZETRAITSDECL_H 17 | -------------------------------------------------------------------------------- /include/config-loader/serialize/Serializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_SERIALIZER_H 6 | #define CONFIG_LOADER_SERIALIZER_H 7 | #include 8 | CONFIG_LOADER_NS_BEGIN 9 | template 10 | void dumpObj2OStream(std::ostream& out, const T& obj) { 11 | CompoundSerializeTraits::dump(out, obj); 12 | } 13 | CONFIG_LOADER_NS_END 14 | #endif //CONFIG_LOADER_SERIALIZER_H 15 | -------------------------------------------------------------------------------- /include/config-loader/serialize/TypeSerializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/09. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_TYPESERIALIZER_H 6 | #define CONFIG_LOADER_TYPESERIALIZER_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | CONFIG_LOADER_NS_BEGIN 22 | template struct TypeSerializer; 23 | 24 | #define TYPE_SERIALIZER(_type, _typeName) \ 25 | struct TypeSerializer \ 26 | { static constexpr decltype(_typeName) name = _typeName; } 27 | 28 | template 29 | struct TypeSerializer> { 30 | static constexpr decltype(T::_schema_name_) name = T::_schema_name_; 31 | }; 32 | 33 | template<> TYPE_SERIALIZER((int8_t), "int8_t"); 34 | template<> TYPE_SERIALIZER((uint8_t), "uint8_t"); 35 | template<> TYPE_SERIALIZER((int16_t), "int16_t"); 36 | template<> TYPE_SERIALIZER((uint16_t), "uint16_t"); 37 | template<> TYPE_SERIALIZER((int32_t), "int32_t"); 38 | template<> TYPE_SERIALIZER((uint32_t), "uint32_t"); 39 | template<> TYPE_SERIALIZER((int64_t), "int64_t"); 40 | template<> TYPE_SERIALIZER((uint64_t), "uint64_t"); 41 | template<> TYPE_SERIALIZER((float), "float"); 42 | template<> TYPE_SERIALIZER((double), "double"); 43 | template<> TYPE_SERIALIZER((std::string), "string"); 44 | 45 | template 46 | TYPE_SERIALIZER((std::vector), 47 | concat("vector<", TypeSerializer::name, ">")); 48 | template 49 | TYPE_SERIALIZER((std::list), 50 | concat("list<", TypeSerializer::name, ">")); 51 | 52 | template 53 | TYPE_SERIALIZER((std::map), 54 | concat("map<", TypeSerializer::name, 55 | ", ", TypeSerializer::name, ">")); 56 | template 57 | TYPE_SERIALIZER((std::unordered_map), 58 | concat("unordered_map<", TypeSerializer::name, 59 | ", ", TypeSerializer::name, ">")); 60 | 61 | template 62 | TYPE_SERIALIZER((std::optional), 63 | concat("optional<", TypeSerializer::name, ">")); 64 | 65 | template 66 | TYPE_SERIALIZER((std::variant), 67 | concat("variant<", join(TypeSerializer::name...)(", "), ">")); 68 | 69 | template 70 | TYPE_SERIALIZER((std::shared_ptr), 71 | concat("shared_ptr<", TypeSerializer::name, ">")); 72 | 73 | template 74 | TYPE_SERIALIZER((std::unique_ptr), 75 | concat("unique_ptr<", TypeSerializer::name, ">")); 76 | 77 | #undef TYPE_SERIALIZER 78 | // helper 79 | template 80 | inline constexpr const char* TypeSerializer_v = TypeSerializer::name; 81 | 82 | template 83 | inline constexpr const char* TypeSerializer_v::name.data())>> = 84 | TypeSerializer::name.data(); 85 | 86 | CONFIG_LOADER_NS_END 87 | #endif // CONFIG_LOADER_TYPESERIALIZER_H 88 | -------------------------------------------------------------------------------- /include/config-loader/utils/Assertion.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/09. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_ASSERTION_H 6 | #define CONFIG_LOADER_ASSERTION_H 7 | 8 | #define CFL_EXPECT_SUCC(call) do { \ 9 | if (auto res = call; res != Result::SUCCESS) { \ 10 | return res; \ 11 | } \ 12 | } while(0) \ 13 | 14 | #endif //CONFIG_LOADER_ASSERTION_H 15 | -------------------------------------------------------------------------------- /include/config-loader/utils/CommonTraits.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/06. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_COMMONTRAITS_H 6 | #define CONFIG_LOADER_COMMONTRAITS_H 7 | #include 8 | CONFIG_LOADER_NS_BEGIN 9 | namespace detail { 10 | template 11 | struct IsSupport { 12 | static constexpr bool isSupport = support; 13 | }; 14 | } 15 | CONFIG_LOADER_NS_END 16 | #endif //CONFIG_LOADER_COMMONTRAITS_H 17 | -------------------------------------------------------------------------------- /include/config-loader/utils/ConstexprStringUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/10. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_CONSTEXPRSTRINGUTILS_H 6 | #define CONFIG_LOADER_CONSTEXPRSTRINGUTILS_H 7 | #include 8 | #include 9 | #include 10 | #include 11 | CONFIG_LOADER_NS_BEGIN 12 | template constexpr auto strLength = 13 | strLength>>; 14 | 15 | template 16 | inline constexpr size_t strLength = N - 1; 17 | 18 | template 19 | inline constexpr size_t strLength> = N - 1; 20 | 21 | template 22 | constexpr auto copy(STR&& src, char* dest) -> decltype(strLength) { 23 | size_t i = 0; 24 | for (; i < strLength; ++i) { 25 | dest[i] = src[i]; 26 | } 27 | return i; 28 | } 29 | 30 | namespace detail { 31 | template 32 | struct ConcatStringFold { 33 | template 34 | friend decltype(auto) constexpr operator<<(ConcatStringFold&& self, STR&& str) { 35 | self.idx += copy(std::forward(str), self.concatedStr.data() + self.idx); 36 | return std::forward(self); 37 | } 38 | std::array concatedStr{}; 39 | size_t idx = 0; 40 | }; 41 | } 42 | 43 | template 44 | constexpr auto concat(STRs&&... strs) { 45 | constexpr size_t len = (strLength + ... + 0); 46 | return (detail::ConcatStringFold{} << ... << strs).concatedStr; // left fold 47 | } 48 | 49 | namespace detail { 50 | template 51 | struct JoinStringFold { 52 | constexpr JoinStringFold(DELIM_TYPE delimiter): delimiter(delimiter) {} 53 | template 54 | friend decltype(auto) constexpr operator<<(JoinStringFold&& self, STR&& str) { 55 | self.idx += copy(std::forward(str), self.joinedStr.data() + self.idx); 56 | if (self.idx + strLength < N) { 57 | self.idx += copy(std::forward(self.delimiter), self.joinedStr.data() + self.idx); 58 | } 59 | return std::forward(self); 60 | } 61 | std::array joinedStr{}; 62 | DELIM_TYPE delimiter; 63 | size_t idx = 0; 64 | }; 65 | }; 66 | 67 | template 68 | constexpr auto join(STRs&& ...strs) { 69 | return [&](auto&& delimiter) { 70 | using DELIM_TYPE = decltype(delimiter); 71 | constexpr size_t strNum = sizeof...(STRs); 72 | constexpr size_t len = (strLength + ... + 0) + 73 | (strNum >= 1 ? strNum - 1 : 0) * strLength; 74 | return (detail::JoinStringFold{std::forward(delimiter)} 75 | << ... << strs).joinedStr; 76 | }; 77 | } 78 | 79 | CONFIG_LOADER_NS_END 80 | #endif // CONFIG_LOADER_CONSTEXPRSTRINGUTILS_H 81 | -------------------------------------------------------------------------------- /include/config-loader/utils/Log.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/09. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_LOG_H 6 | #define CONFIG_LOADER_LOG_H 7 | #include 8 | 9 | #define LOGI(fmt, ...) printf("[ConfigLoader] " fmt "\n", ##__VA_ARGS__) 10 | #define LOGE(fmt, ...) printf("[ConfigLoader] " fmt "\n", ##__VA_ARGS__) 11 | 12 | #endif //CONFIG_LOADER_LOG_H 13 | -------------------------------------------------------------------------------- /include/config-loader/utils/RepeatMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/6/21. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_REPEATMACRO_H 6 | #define CONFIG_LOADER_REPEATMACRO_H 7 | 8 | #define PP_THIRD_ARG(a, b, c, ...) c 9 | #define VA_OPT_SUPPORTED_I(...) PP_THIRD_ARG(__VA_OPT__(, ), 1, 0, ) 10 | #define VA_OPT_SUPPORTED VA_OPT_SUPPORTED_I(?) 11 | 12 | // Traditional MSVC requires a special EXPAND phase 13 | #if (defined(_MSC_VER) && !defined(_MSVC_TRADITIONAL)) || \ 14 | (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) 15 | 16 | #define GET_ARG_COUNT(...) \ 17 | INTERNAL_EXPAND_ARGS_PRIVATE(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__)) 18 | 19 | #define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__ 20 | #define INTERNAL_EXPAND(x) x 21 | #define INTERNAL_EXPAND_ARGS_PRIVATE(...) \ 22 | INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE( \ 23 | __VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, \ 24 | 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, \ 25 | 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, \ 26 | 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ 27 | 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, \ 28 | 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, \ 29 | 6, 5, 4, 3, 2, 1, 0)) 30 | 31 | #else // Other compilers 32 | 33 | #if VA_OPT_SUPPORTED // Standardized in C++20 34 | #define GET_ARG_COUNT(...) \ 35 | INTERNAL_GET_ARG_COUNT_PRIVATE( \ 36 | unused __VA_OPT__(, ) __VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, \ 37 | 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, \ 38 | 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, \ 39 | 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, \ 40 | 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ 41 | 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, \ 42 | 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 43 | #elif defined(__GNUC__) // Extension in GCC/Clang 44 | #define GET_ARG_COUNT(...) \ 45 | INTERNAL_GET_ARG_COUNT_PRIVATE( \ 46 | unused, ##__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, \ 47 | 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, \ 48 | 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, \ 49 | 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, \ 50 | 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, \ 51 | 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, \ 52 | 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 53 | #else // GET_ARG_COUNT() may return 1 here 54 | #define GET_ARG_COUNT(...) \ 55 | INTERNAL_GET_ARG_COUNT_PRIVATE( \ 56 | unused, __VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, \ 57 | 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, \ 58 | 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, \ 59 | 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, \ 60 | 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, \ 61 | 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, \ 62 | 8, 7, 6, 5, 4, 3, 2, 1, 0) 63 | #endif 64 | 65 | #endif 66 | 67 | #define INTERNAL_GET_ARG_COUNT_PRIVATE( \ 68 | e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, \ 69 | e16, e17, e18, e19, e20, e21, e22, e23, e24, e25, e26, e27, e28, e29, \ 70 | e30, e31, e32, e33, e34, e35, e36, e37, e38, e39, e40, e41, e42, e43, \ 71 | e44, e45, e46, e47, e48, e49, e50, e51, e52, e53, e54, e55, e56, e57, \ 72 | e58, e59, e60, e61, e62, e63, e64, e65, e66, e67, e68, e69, e70, e71, \ 73 | e72, e73, e74, e75, e76, e77, e78, e79, e80, e81, e82, e83, e84, e85, \ 74 | e86, e87, e88, e89, e90, e91, e92, e93, e94, e95, e96, e97, e98, e99, \ 75 | e100, count, ...) \ 76 | count 77 | 78 | #define EXPAND(x) x // for msvc 79 | #define REPEAT_0(func, i, arg) 80 | #define REPEAT_1(func, i, arg) func(i, arg) 81 | #define REPEAT_2(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_1(func, i + 1, __VA_ARGS__)) 82 | #define REPEAT_3(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_2(func, i + 1, __VA_ARGS__)) 83 | #define REPEAT_4(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_3(func, i + 1, __VA_ARGS__)) 84 | #define REPEAT_5(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_4(func, i + 1, __VA_ARGS__)) 85 | #define REPEAT_6(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_5(func, i + 1, __VA_ARGS__)) 86 | #define REPEAT_7(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_6(func, i + 1, __VA_ARGS__)) 87 | #define REPEAT_8(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_7(func, i + 1, __VA_ARGS__)) 88 | #define REPEAT_9(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_8(func, i + 1, __VA_ARGS__)) 89 | #define REPEAT_10(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_9(func, i + 1, __VA_ARGS__)) 90 | #define REPEAT_11(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_10(func, i + 1, __VA_ARGS__)) 91 | #define REPEAT_12(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_11(func, i + 1, __VA_ARGS__)) 92 | #define REPEAT_13(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_12(func, i + 1, __VA_ARGS__)) 93 | #define REPEAT_14(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_13(func, i + 1, __VA_ARGS__)) 94 | #define REPEAT_15(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_14(func, i + 1, __VA_ARGS__)) 95 | #define REPEAT_16(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_15(func, i + 1, __VA_ARGS__)) 96 | #define REPEAT_17(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_16(func, i + 1, __VA_ARGS__)) 97 | #define REPEAT_18(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_17(func, i + 1, __VA_ARGS__)) 98 | #define REPEAT_19(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_18(func, i + 1, __VA_ARGS__)) 99 | #define REPEAT_20(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_19(func, i + 1, __VA_ARGS__)) 100 | #define REPEAT_21(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_20(func, i + 1, __VA_ARGS__)) 101 | #define REPEAT_22(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_21(func, i + 1, __VA_ARGS__)) 102 | #define REPEAT_23(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_22(func, i + 1, __VA_ARGS__)) 103 | #define REPEAT_24(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_23(func, i + 1, __VA_ARGS__)) 104 | #define REPEAT_25(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_24(func, i + 1, __VA_ARGS__)) 105 | #define REPEAT_26(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_25(func, i + 1, __VA_ARGS__)) 106 | #define REPEAT_27(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_26(func, i + 1, __VA_ARGS__)) 107 | #define REPEAT_28(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_27(func, i + 1, __VA_ARGS__)) 108 | #define REPEAT_29(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_28(func, i + 1, __VA_ARGS__)) 109 | #define REPEAT_30(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_29(func, i + 1, __VA_ARGS__)) 110 | #define REPEAT_31(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_30(func, i + 1, __VA_ARGS__)) 111 | #define REPEAT_32(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_31(func, i + 1, __VA_ARGS__)) 112 | #define REPEAT_33(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_32(func, i + 1, __VA_ARGS__)) 113 | #define REPEAT_34(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_33(func, i + 1, __VA_ARGS__)) 114 | #define REPEAT_35(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_34(func, i + 1, __VA_ARGS__)) 115 | #define REPEAT_36(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_35(func, i + 1, __VA_ARGS__)) 116 | #define REPEAT_37(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_36(func, i + 1, __VA_ARGS__)) 117 | #define REPEAT_38(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_37(func, i + 1, __VA_ARGS__)) 118 | #define REPEAT_39(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_38(func, i + 1, __VA_ARGS__)) 119 | #define REPEAT_40(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_39(func, i + 1, __VA_ARGS__)) 120 | #define REPEAT_41(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_40(func, i + 1, __VA_ARGS__)) 121 | #define REPEAT_42(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_41(func, i + 1, __VA_ARGS__)) 122 | #define REPEAT_43(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_42(func, i + 1, __VA_ARGS__)) 123 | #define REPEAT_44(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_43(func, i + 1, __VA_ARGS__)) 124 | #define REPEAT_45(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_44(func, i + 1, __VA_ARGS__)) 125 | #define REPEAT_46(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_45(func, i + 1, __VA_ARGS__)) 126 | #define REPEAT_47(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_46(func, i + 1, __VA_ARGS__)) 127 | #define REPEAT_48(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_47(func, i + 1, __VA_ARGS__)) 128 | #define REPEAT_49(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_48(func, i + 1, __VA_ARGS__)) 129 | #define REPEAT_50(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_49(func, i + 1, __VA_ARGS__)) 130 | #define REPEAT_51(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_50(func, i + 1, __VA_ARGS__)) 131 | #define REPEAT_52(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_51(func, i + 1, __VA_ARGS__)) 132 | #define REPEAT_53(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_52(func, i + 1, __VA_ARGS__)) 133 | #define REPEAT_54(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_53(func, i + 1, __VA_ARGS__)) 134 | #define REPEAT_55(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_54(func, i + 1, __VA_ARGS__)) 135 | #define REPEAT_56(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_55(func, i + 1, __VA_ARGS__)) 136 | #define REPEAT_57(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_56(func, i + 1, __VA_ARGS__)) 137 | #define REPEAT_58(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_57(func, i + 1, __VA_ARGS__)) 138 | #define REPEAT_59(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_58(func, i + 1, __VA_ARGS__)) 139 | #define REPEAT_60(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_59(func, i + 1, __VA_ARGS__)) 140 | #define REPEAT_61(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_60(func, i + 1, __VA_ARGS__)) 141 | #define REPEAT_62(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_61(func, i + 1, __VA_ARGS__)) 142 | #define REPEAT_63(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_62(func, i + 1, __VA_ARGS__)) 143 | #define REPEAT_64(func, i, arg, ...) func(i, arg) EXPAND(REPEAT_63(func, i + 1, __VA_ARGS__)) 144 | 145 | #define STR(x) #x 146 | #define CONCATE(x, y) x ## y 147 | #define STRING(x) EXPAND(STR(x)) 148 | #define PARE(...) __VA_ARGS__ 149 | #define EAT(...) 150 | #define PAIR(x) PARE x // PAIR((int) x) => PARE(int) x => int x 151 | #define STRIP(x) EXPAND(EAT x) // STRIP((int) x) => EAT(int) x => x 152 | #define PASTE(x, y) CONCATE(x, y) 153 | #endif //CONFIG_LOADER_REPEATMACRO_H 154 | -------------------------------------------------------------------------------- /src/utils/GetFileContent.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/07. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | CONFIG_LOADER_NS_BEGIN 10 | 11 | std::string getFileContent(const char* path) { 12 | if (path == nullptr || strlen(path) == 0) { 13 | return {}; 14 | } 15 | std::ifstream f(path); 16 | auto fileSize = std::filesystem::file_size(path); 17 | std::string result(fileSize, '\0'); 18 | f.read(result.data(), fileSize); 19 | return result; 20 | } 21 | 22 | CONFIG_LOADER_NS_END 23 | -------------------------------------------------------------------------------- /test/ut/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(config_loader_test 2 | ReflectMacroTest.cpp 3 | UTSchema.h 4 | UtilsTest.cpp 5 | DeserializerTest.cpp 6 | ResultChecking.cpp 7 | TinyXML2Test.cpp 8 | TinyXML2DeserializeTest.cpp 9 | JsonCppTest.cpp 10 | JsonCppDeserializeTest.cpp 11 | YamlCppDeserializeTest.cpp 12 | YamlCppTest.cpp 13 | DeserializeConfig.h 14 | SerializerTest.cpp TypeSerializerTest.cpp ConstexprStringTest.cpp) 15 | 16 | set_target_properties( 17 | config_loader_test 18 | PROPERTIES 19 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 20 | ) 21 | 22 | target_link_libraries(config_loader_test PRIVATE config-loader Catch2WithMain ${THIRD_PARTY_LIBRARIES}) 23 | 24 | add_custom_target(copy_configs 25 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/configs ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/configs) 26 | 27 | add_dependencies(config_loader_test copy_configs) 28 | -------------------------------------------------------------------------------- /test/ut/ConstexprStringTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/10. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | using namespace Catch; 9 | using namespace Catch::Matchers; 10 | using namespace CONFIG_LOADER_NS; 11 | 12 | SCENARIO("test concat") { 13 | GIVEN("concat empty string") { 14 | constexpr auto res = concat(); 15 | static_assert(res.size() == 1); 16 | } 17 | 18 | GIVEN("concat a string") { 19 | constexpr auto res = concat("hello"); 20 | static_assert(res.size() == 6); 21 | REQUIRE_THAT(res.data(), Equals("hello")); 22 | } 23 | 24 | GIVEN("concat two string") { 25 | constexpr auto res = concat("hello", " world"); 26 | static_assert(res.size() == 12); 27 | REQUIRE_THAT(res.data(), Equals("hello world")); 28 | } 29 | 30 | GIVEN("concat three string") { 31 | constexpr auto res = concat("one", "two", "three"); 32 | static_assert(res.size() == 12); 33 | REQUIRE_THAT(res.data(), Equals("onetwothree")); 34 | } 35 | 36 | GIVEN("mixin concat char[N]/array") { 37 | constexpr auto res = concat("one", concat("two"), "three"); 38 | static_assert(res.size() == 12); 39 | REQUIRE_THAT(res.data(), Equals("onetwothree")); 40 | } 41 | } 42 | 43 | SCENARIO("test join") { 44 | GIVEN("empty join") { 45 | constexpr auto res = join()(", "); 46 | static_assert(res.size() == 1); 47 | } 48 | 49 | GIVEN("join a string") { 50 | constexpr auto res = join("one")(", "); 51 | static_assert(res.size() == 4); 52 | REQUIRE_THAT(res.data(), Equals("one")); 53 | } 54 | GIVEN("join two string") { 55 | constexpr auto res = join("one", "two")(", "); 56 | static_assert(res.size() == 9); 57 | REQUIRE_THAT(res.data(), Equals("one, two")); 58 | } 59 | GIVEN("join three string") { 60 | constexpr auto res = join("one", "two", "three")(", "); 61 | static_assert(res.size() == 16); 62 | REQUIRE_THAT(res.data(), Equals("one, two, three")); 63 | } 64 | 65 | GIVEN("mixin join char[N]/array") { 66 | constexpr auto res = join("one", join("two", "three")(","))(", "); 67 | static_assert(res.size() == 15); 68 | REQUIRE_THAT(res.data(), Equals("one, two,three")); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/ut/DeserializeConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/16. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_DESERIALIZECONFIG_H 6 | #define CONFIG_LOADER_DESERIALIZECONFIG_H 7 | 8 | namespace xml_config { 9 | constexpr auto POINT_CONFIG_PATH = "configs/xml/Point.xml"; 10 | constexpr auto RECT_CONFIG_PATH = "configs/xml/Rect.xml"; 11 | constexpr auto SOME_OF_POINTS_CONFIG_PATH = "configs/xml/SomeOfPoints.xml"; 12 | constexpr auto STLOBJ_CONFIG_PATH = "configs/xml/STLObj.xml"; 13 | constexpr auto TREE_CONFIG_PATH = "configs/xml/Tree.xml"; 14 | } 15 | 16 | namespace json_config { 17 | constexpr auto POINT_CONFIG_PATH = "configs/json/Point.json"; 18 | constexpr auto RECT_CONFIG_PATH = "configs/json/Rect.json"; 19 | constexpr auto SOME_OF_POINTS_CONFIG_PATH = "configs/json/SomeOfPoints.json"; 20 | constexpr auto STLOBJ_CONFIG_PATH = "configs/json/STLObj.json"; 21 | constexpr auto TREE_CONFIG_PATH = "configs/json/Tree.json"; 22 | } 23 | 24 | namespace yaml_config { 25 | constexpr auto POINT_CONFIG_PATH = "configs/yml/Point.yml"; 26 | constexpr auto RECT_CONFIG_PATH = "configs/yml/Rect.yml"; 27 | constexpr auto SOME_OF_POINTS_CONFIG_PATH = "configs/yml/SomeOfPoints.yml"; 28 | constexpr auto STLOBJ_CONFIG_PATH = "configs/yml/STLObj.yml"; 29 | constexpr auto TREE_CONFIG_PATH = "configs/yml/Tree.yml"; 30 | } 31 | 32 | #endif //CONFIG_LOADER_DESERIALIZECONFIG_H 33 | -------------------------------------------------------------------------------- /test/ut/DeserializerTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/07. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "UTSchema.h" 9 | #include "DeserializeConfig.h" 10 | 11 | using namespace Catch; 12 | using namespace Catch::Matchers; 13 | using namespace CONFIG_LOADER_NS; 14 | using namespace xml_config; 15 | 16 | SCENARIO("loadXML2Obj test") { 17 | GIVEN("load by path") { 18 | THEN("load both by default config") { 19 | { 20 | Point point; 21 | REQUIRE(loadXML2Obj(point, POINT_CONFIG_PATH) == Result::SUCCESS); 22 | REQUIRE(point.x == 1.2); 23 | REQUIRE(point.y == 3.4); 24 | } 25 | { 26 | Rect rect; 27 | REQUIRE(loadXML2Obj(rect, RECT_CONFIG_PATH) == Result::SUCCESS); 28 | REQUIRE(rect.p1.x == 1.2); 29 | REQUIRE(rect.p1.y == 3.4); 30 | REQUIRE(rect.p2.x == 5.6); 31 | REQUIRE(rect.p2.y == 7.8); 32 | REQUIRE(rect.color == 0x12345678); 33 | } 34 | 35 | { 36 | SomeOfPoints someOfPoints; 37 | REQUIRE(loadXML2Obj(someOfPoints, SOME_OF_POINTS_CONFIG_PATH) == Result::SUCCESS); 38 | 39 | REQUIRE_THAT(someOfPoints.name, 40 | Equals("Some of points")); 41 | REQUIRE(someOfPoints.points.size() == 3); 42 | } 43 | } 44 | 45 | THEN("load by lambda") { 46 | Rect rect; 47 | auto res = loadXML2Obj(rect, [] { 48 | return R"( 49 | 50 | 51 | 52 | 5.6 53 | 7.8 54 | 55 | 56 | 1.2 57 | 3.4 58 | 59 | 0x12345678 60 | 61 | )"; 62 | }); 63 | REQUIRE(res == Result::SUCCESS); 64 | REQUIRE(rect.p2.x == 1.2); 65 | REQUIRE(rect.p2.y == 3.4); 66 | REQUIRE(rect.p1.x == 5.6); 67 | REQUIRE(rect.p1.y == 7.8); 68 | REQUIRE(rect.color == 0x12345678); 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /test/ut/JsonCppDeserializeTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/10. 3 | // 4 | 5 | #include 6 | #include 7 | #include "UTSchema.h" 8 | #include "DeserializeConfig.h" 9 | #include 10 | 11 | using namespace Catch; 12 | using namespace Catch::Matchers; 13 | using namespace CONFIG_LOADER_NS; 14 | using namespace json_config; 15 | 16 | SCENARIO("deserialize json to struct") { 17 | GIVEN("a flatten point config") { 18 | Point point; 19 | REQUIRE(loadJSON2Obj(point, POINT_CONFIG_PATH) == Result::SUCCESS); 20 | REQUIRE(point.x == 1.2); 21 | REQUIRE(point.y == 3.4); 22 | } 23 | 24 | GIVEN("a nest rect config") { 25 | Rect rect; 26 | REQUIRE(loadJSON2Obj(rect, RECT_CONFIG_PATH) == Result::SUCCESS); 27 | REQUIRE(rect.p1.x == 1.2); 28 | REQUIRE(rect.p1.y == 3.4); 29 | REQUIRE(rect.p2.x == 5.6); 30 | REQUIRE(rect.p2.y == 7.8); 31 | REQUIRE(rect.color == 12345678); 32 | } 33 | 34 | GIVEN("a nest rect config that missing p1/p2") { 35 | Rect rect; 36 | auto res = loadJSON2Obj(rect, [] { 37 | return R"({ "color": 12345678 })"; 38 | }); 39 | REQUIRE(res == Result::ERR_MISSING_FIELD); 40 | } 41 | 42 | GIVEN("a complex rect config") { 43 | SomeOfPoints someOfPoints; 44 | REQUIRE(loadJSON2Obj(someOfPoints, SOME_OF_POINTS_CONFIG_PATH) == Result::SUCCESS); 45 | 46 | REQUIRE_THAT(someOfPoints.name, 47 | Equals("Some of points")); 48 | REQUIRE(someOfPoints.points.size() == 3); 49 | double pointsV[] = { 50 | 1.2, 3.4, 51 | 5.6, 7.8, 52 | 2.2, 3.3 53 | }; 54 | for (size_t i = 0; i < someOfPoints.points.size(); ++i) { 55 | REQUIRE(someOfPoints.points[i].x == pointsV[i * 2]); 56 | REQUIRE(someOfPoints.points[i].y == pointsV[i * 2 + 1]); 57 | } 58 | } 59 | } 60 | 61 | /////////////////////////////////////////////////////////////////////////////// 62 | SCENARIO("deserialize json to compound STL container") { 63 | GIVEN("a valid STL obj") { 64 | STLObj data; 65 | REQUIRE(loadJSON2Obj(data, STLOBJ_CONFIG_PATH) == Result::SUCCESS); 66 | REQUIRE(data.m1[0] == 2); 67 | REQUIRE(data.m1[1] == 4); 68 | REQUIRE(data.m1[2] == 6); 69 | REQUIRE(data.m1.size() == 3); 70 | 71 | REQUIRE(data.m2["hello world"].x == 1.2); 72 | REQUIRE(data.m2["hello world"].y == 3.4); 73 | REQUIRE(data.m2.size() == 1); 74 | 75 | REQUIRE(data.m3.has_value()); 76 | REQUIRE(data.m3->empty()); 77 | 78 | REQUIRE(data.m4.has_value()); 79 | REQUIRE(data.m4->x == 5.6); 80 | REQUIRE(data.m4->y == 7.8); 81 | 82 | REQUIRE(! data.m5.has_value()); 83 | REQUIRE(! data.m6.has_value()); 84 | } 85 | } 86 | 87 | SCENARIO("deserialize json to sum type(std::variant)") { 88 | TestVariant obj; 89 | 90 | GIVEN("a string") { 91 | auto res = loadJSON2Obj(obj, [] { 92 | return R"({ "sumType": "hello world!" })"; 93 | }); 94 | REQUIRE(res == Result::SUCCESS); 95 | REQUIRE(obj.sumType.index() == 2); 96 | REQUIRE(! obj.sumType.valueless_by_exception()); 97 | REQUIRE_THAT(std::get<2>(obj.sumType), 98 | Equals("hello world!")); 99 | } 100 | 101 | GIVEN("a int") { 102 | auto res = loadJSON2Obj(obj, [] { 103 | return R"( { "sumType": 987654 } )"; 104 | }); 105 | REQUIRE(res == Result::SUCCESS); 106 | REQUIRE(obj.sumType.index() == 1); 107 | REQUIRE(! obj.sumType.valueless_by_exception()); 108 | REQUIRE(std::get<1>(obj.sumType) == 987654); 109 | } 110 | 111 | GIVEN("a point") { 112 | auto res = loadJSON2Obj(obj, [] { 113 | return R"( { "sumType": { "x": 1.2, "y": 3.4 } } )"; 114 | }); 115 | REQUIRE(res == Result::SUCCESS); 116 | REQUIRE(obj.sumType.index() == 0); 117 | REQUIRE(! obj.sumType.valueless_by_exception()); 118 | auto&& [x, y] = std::get<0>(obj.sumType); 119 | REQUIRE(x == 1.2); 120 | REQUIRE(y == 3.4); 121 | } 122 | 123 | GIVEN("a invalid object") { 124 | auto res = loadJSON2Obj(obj, [] { 125 | return R"( { "sumType": { "x": 1.2 } } )"; 126 | }); 127 | REQUIRE(res == Result::ERR_TYPE); 128 | } 129 | 130 | GIVEN("a invalid type") { 131 | auto res = loadJSON2Obj(obj, [] { 132 | return R"( { "sumType": [1,2,3] } )"; 133 | }); 134 | REQUIRE(res == Result::ERR_TYPE); 135 | } 136 | GIVEN("a null type type") { 137 | auto res = loadJSON2Obj(obj, [] { 138 | return R"( { "sumType": null } )"; 139 | }); 140 | REQUIRE(res == Result::ERR_MISSING_FIELD); 141 | } 142 | GIVEN("a empty object") { 143 | auto res = loadJSON2Obj(obj, [] { 144 | return R"( { "sumType": {} } )"; 145 | }); 146 | REQUIRE(res == Result::ERR_TYPE); 147 | } 148 | } 149 | 150 | SCENARIO("deserialize json to tree type") { 151 | TestTree obj; 152 | GIVEN("a tree") { 153 | REQUIRE(loadJSON2Obj(obj, TREE_CONFIG_PATH) == Result::SUCCESS); 154 | REQUIRE_THAT(obj.name, Equals("hello")); 155 | REQUIRE(obj.children.size() == 3); 156 | REQUIRE_THAT(obj.children[0]->name, Equals("world")); 157 | REQUIRE_THAT(obj.children[1]->name, Equals("first")); 158 | REQUIRE_THAT(obj.children[2]->name, Equals("second")); 159 | REQUIRE(obj.children[2]->children.size() == 1); 160 | REQUIRE_THAT(obj.children[2]->children[0]->name, Equals("leaf")); 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /test/ut/JsonCppTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/09. 3 | // 4 | 5 | #ifdef HAS_JSONCPP 6 | #include 7 | #include 8 | #include 9 | #include "DeserializeConfig.h" 10 | #include 11 | 12 | using CONFIG_LOADER_NS::getFileContent; 13 | using namespace Catch; 14 | using namespace Catch::Matchers; 15 | 16 | static void checkPoint(const Json::Value& root, double x, double y) { 17 | REQUIRE(root.isObject()); 18 | REQUIRE(root["x"] == x); 19 | REQUIRE(root["y"] == y); 20 | } 21 | 22 | using namespace json_config; 23 | SCENARIO("load a json config") { 24 | Json::CharReaderBuilder builder; 25 | std::unique_ptr reader(builder.newCharReader()); 26 | 27 | GIVEN("a point config") { 28 | Json::Value root; 29 | auto pointCfg = getFileContent(POINT_CONFIG_PATH); 30 | REQUIRE(reader->parse(pointCfg.data(), pointCfg.data() + pointCfg.size(), 31 | &root , nullptr)); 32 | checkPoint(root, 1.2, 3.4); 33 | } 34 | 35 | GIVEN("a rect config") { 36 | Json::Value root; 37 | auto rectCfg = getFileContent(RECT_CONFIG_PATH); 38 | REQUIRE(reader->parse(rectCfg.data(), rectCfg.data() + rectCfg.size(), 39 | &root , nullptr)); 40 | 41 | checkPoint(root["p1"], 1.2, 3.4); 42 | checkPoint(root["p2"], 5.6, 7.8); 43 | REQUIRE(root["color"] == 12345678); 44 | } 45 | 46 | GIVEN("a someOfPoints config") { 47 | Json::Value root; 48 | auto someOfPointsCfg = getFileContent(SOME_OF_POINTS_CONFIG_PATH); 49 | REQUIRE(reader->parse(someOfPointsCfg.data(), someOfPointsCfg.data() + someOfPointsCfg.size(), 50 | &root , nullptr)); 51 | REQUIRE_THAT(root["name"].asString(), Equals("Some of points")); 52 | 53 | double pointsV[] = { 54 | 1.2, 3.4, 55 | 5.6, 7.8, 56 | 2.2, 3.3 57 | }; 58 | 59 | size_t pointIdx = 0; 60 | for (auto&& point = root["point"].begin() 61 | ; point != root["point"].end() 62 | ; ++point) { 63 | checkPoint(*point, pointsV[pointIdx], pointsV[pointIdx + 1]); 64 | pointIdx += 2; 65 | } 66 | } 67 | } 68 | 69 | SCENARIO("test Json::Value as string") { 70 | GIVEN("a int") { 71 | Json::Value v = 2; 72 | REQUIRE_THAT(v.asString(), Equals("2")); 73 | } 74 | GIVEN("a double") { 75 | Json::Value v = 1.234; 76 | REQUIRE_THAT(v.asString(), Equals("1.234")); 77 | } 78 | GIVEN("a string") { 79 | Json::Value v = "hello world"; 80 | REQUIRE_THAT(v.asString(), Equals("hello world")); 81 | } 82 | GIVEN("a nullptr") { 83 | Json::Value v = Json::nullValue; 84 | REQUIRE_THAT(v.asString(), Equals("")); 85 | } 86 | 87 | } 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /test/ut/ReflectMacroTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/04. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include "UTSchema.h" 8 | 9 | using namespace CONFIG_LOADER_NS; 10 | 11 | SCENARIO("reflect a struct") { 12 | WHEN("retrieve field count") { 13 | static_assert(Point::_field_count_ == 2); 14 | static_assert(Rect::_field_count_ == 3); 15 | } 16 | WHEN("no extract space overhead") { 17 | static_assert(sizeof(Point) == 2 * 8); 18 | static_assert(alignof(Point) == 8); 19 | static_assert(sizeof(Rect) == 2 * 8 * 2 + 4 /* spacing */ + 4); 20 | static_assert(alignof(Rect) == 8); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /test/ut/ResultChecking.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/08. 3 | // 4 | 5 | #include 6 | #include 7 | #include "UTSchema.h" 8 | 9 | using namespace CONFIG_LOADER_NS; 10 | 11 | SCENARIO("Error checking") { 12 | WHEN("a empty config") { 13 | Point point; 14 | auto res = loadXML2Obj(point, [] { 15 | return ""; 16 | }); 17 | REQUIRE(res == Result::ERR_EMPTY_CONTENT); 18 | } 19 | 20 | WHEN("a ill-format config") { 21 | Point point; 22 | auto res = loadXML2Obj(point, [] { 23 | return " 34 | 35 | 1.2 36 | 37 | )"; 38 | }); 39 | REQUIRE(res == Result::ERR_MISSING_FIELD); 40 | } 41 | 42 | { 43 | SomeOfPoints someOfPoints; 44 | auto res = loadXML2Obj(someOfPoints, [] { 45 | return R"( 46 | 47 | 48 | Some of points 49 | 50 | 51 | 3.3 52 | 53 | 54 | 55 | )"; 56 | }); 57 | REQUIRE(res == Result::ERR_MISSING_FIELD); 58 | } 59 | } 60 | 61 | WHEN("a err extracting field config") { 62 | Point point; 63 | auto res = loadXML2Obj(point, [] { 64 | return R"( 65 | 66 | 67 | abc 68 | 69 | )"; 70 | }); 71 | REQUIRE(res == Result::ERR_EXTRACTING_FIELD); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/ut/SerializerTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/06. 3 | // 4 | #include 5 | #include 6 | #include "UTSchema.h" 7 | #include "DeserializeConfig.h" 8 | #include 9 | 10 | using namespace Catch; 11 | using namespace CONFIG_LOADER_NS; 12 | using namespace xml_config; 13 | 14 | SCENARIO("dumping data structure") { 15 | std::cout << std::endl 16 | << "////////////////////////////////////////////////////////////////////////////////////////" 17 | << std::endl; 18 | 19 | GIVEN("a point") { 20 | Point point; 21 | REQUIRE(loadXML2Obj(point, POINT_CONFIG_PATH) == Result::SUCCESS); 22 | dumpObj2OStream(std::cout, point); 23 | } 24 | GIVEN("a Rect") { 25 | Rect rect; 26 | REQUIRE(loadXML2Obj(rect, RECT_CONFIG_PATH) == Result::SUCCESS); 27 | dumpObj2OStream(std::cout, rect); 28 | } 29 | 30 | GIVEN("some of points") { 31 | SomeOfPoints someOfPoints; 32 | REQUIRE(loadXML2Obj(someOfPoints, SOME_OF_POINTS_CONFIG_PATH) == Result::SUCCESS); 33 | dumpObj2OStream(std::cout, someOfPoints); 34 | } 35 | 36 | GIVEN("stl obj") { 37 | STLObj stlObj; 38 | REQUIRE(loadXML2Obj(stlObj, STLOBJ_CONFIG_PATH) == Result::SUCCESS); 39 | dumpObj2OStream(std::cout, stlObj); 40 | } 41 | 42 | GIVEN("test variant") { 43 | { 44 | TestVariant obj{ Point { 1, 2 } }; 45 | dumpObj2OStream(std::cout, obj); 46 | } 47 | { 48 | std::cout << std::endl; 49 | TestVariant obj{ 0x1234 }; 50 | dumpObj2OStream(std::cout, obj); 51 | } 52 | { 53 | std::cout << std::endl; 54 | TestVariant obj{ "hello world" }; 55 | dumpObj2OStream(std::cout, obj); 56 | } 57 | } 58 | 59 | GIVEN("tree obj") { 60 | TestTree tree; 61 | REQUIRE(loadXML2Obj(tree, TREE_CONFIG_PATH) == Result::SUCCESS); 62 | dumpObj2OStream(std::cout, tree); 63 | } 64 | } 65 | 66 | ALIAS_COMPOUND_TYPE(Tree, (std::map>)); 67 | 68 | SCENARIO("dumping alias structure") { 69 | std::cout << std::endl 70 | << "////////////////////////////////////////////////////////////////////////////////////////" 71 | << std::endl; 72 | 73 | Tree tree { 74 | { {0}, std::make_shared() }, 75 | { {1}, std::make_shared( 76 | Tree {{ {2}, std::make_shared() }}) 77 | }, 78 | { {3}, std::make_shared() }, 79 | }; 80 | dumpObj2OStream(std::cout, tree); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /test/ut/TinyXML2DeserializeTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/06. 3 | // 4 | 5 | #include 6 | #include 7 | #include "UTSchema.h" 8 | #include "DeserializeConfig.h" 9 | #include 10 | 11 | using namespace Catch; 12 | using namespace Catch::Matchers; 13 | using namespace CONFIG_LOADER_NS; 14 | using namespace xml_config; 15 | 16 | SCENARIO("deserialize xml to struct") { 17 | GIVEN("a flatten point config") { 18 | Point point; 19 | REQUIRE(loadXML2Obj(point, POINT_CONFIG_PATH) == Result::SUCCESS); 20 | REQUIRE(point.x == 1.2); 21 | REQUIRE(point.y == 3.4); 22 | } 23 | 24 | GIVEN("a nest rect config") { 25 | Rect rect; 26 | REQUIRE(loadXML2Obj(rect, RECT_CONFIG_PATH) == Result::SUCCESS); 27 | REQUIRE(rect.p1.x == 1.2); 28 | REQUIRE(rect.p1.y == 3.4); 29 | REQUIRE(rect.p2.x == 5.6); 30 | REQUIRE(rect.p2.y == 7.8); 31 | REQUIRE(rect.color == 0x12345678); 32 | } 33 | 34 | GIVEN("a nest rect config that missing p1/p2") { 35 | Rect rect; 36 | auto res = loadXML2Obj(rect, [] { 37 | return R"( 38 | 39 | 40 | 0x12345678 41 | 42 | )"; 43 | }); 44 | REQUIRE(res == Result::ERR_MISSING_FIELD); 45 | } 46 | 47 | GIVEN("a complex rect config") { 48 | SomeOfPoints someOfPoints; 49 | REQUIRE(loadXML2Obj(someOfPoints, SOME_OF_POINTS_CONFIG_PATH) == Result::SUCCESS); 50 | REQUIRE_THAT(someOfPoints.name, 51 | Equals("Some of points")); 52 | REQUIRE(someOfPoints.points.size() == 3); 53 | double pointsV[] = { 54 | 1.2, 3.4, 55 | 5.6, 7.8, 56 | 2.2, 3.3 57 | }; 58 | for (size_t i = 0; i < someOfPoints.points.size(); ++i) { 59 | REQUIRE(someOfPoints.points[i].x == pointsV[i * 2]); 60 | REQUIRE(someOfPoints.points[i].y == pointsV[i * 2 + 1]); 61 | } 62 | } 63 | } 64 | 65 | /////////////////////////////////////////////////////////////////////////////// 66 | SCENARIO("deserialize xml to compound STL container") { 67 | GIVEN("a valid STL obj") { 68 | STLObj data; 69 | REQUIRE(loadXML2Obj(data, STLOBJ_CONFIG_PATH) == Result::SUCCESS); 70 | REQUIRE(data.m1[0] == 2); 71 | REQUIRE(data.m1[1] == 4); 72 | REQUIRE(data.m1[2] == 6); 73 | REQUIRE(data.m1.size() == 3); 74 | 75 | REQUIRE(data.m2["hello world"].x == 1.2); 76 | REQUIRE(data.m2["hello world"].y == 3.4); 77 | REQUIRE(data.m2.size() == 1); 78 | 79 | REQUIRE(! data.m3.has_value()); 80 | 81 | REQUIRE(data.m4.has_value()); 82 | REQUIRE(data.m4->x == 5.6); 83 | REQUIRE(data.m4->y == 7.8); 84 | 85 | REQUIRE(! data.m5.has_value()); 86 | REQUIRE(! data.m6.has_value()); 87 | 88 | } 89 | } 90 | 91 | DEFINE_SCHEMA(TestBool, 92 | (bool) m1); 93 | 94 | SCENARIO("deserialize xml to bool type") { 95 | TestBool obj; 96 | GIVEN("a valid bool") { 97 | auto res = loadXML2Obj(obj, [] { 98 | return "true"; 99 | }); 100 | REQUIRE(res == Result::SUCCESS); 101 | REQUIRE(obj.m1); 102 | } 103 | GIVEN("a valid bool") { 104 | auto res = loadXML2Obj(obj, [] { 105 | return "True"; 106 | }); 107 | REQUIRE(res == Result::SUCCESS); 108 | REQUIRE(obj.m1); 109 | } 110 | GIVEN("a valid bool") { 111 | auto res = loadXML2Obj(obj, [] { 112 | return "1"; 113 | }); 114 | REQUIRE(res == Result::SUCCESS); 115 | REQUIRE(obj.m1); 116 | } 117 | GIVEN("a valid bool") { 118 | auto res = loadXML2Obj(obj, [] { 119 | return "false"; 120 | }); 121 | REQUIRE(res == Result::SUCCESS); 122 | REQUIRE(! obj.m1); 123 | } 124 | GIVEN("a invalid bool") { 125 | auto res = loadXML2Obj(obj, [] { 126 | return "unknown"; 127 | }); 128 | REQUIRE(res == Result::ERR_EXTRACTING_FIELD); 129 | } 130 | } 131 | 132 | DEFINE_SCHEMA(TestInt8T, 133 | (uint8_t) m1, 134 | (int8_t) m2); 135 | SCENARIO("deserialize xml to int8_t type") { 136 | TestInt8T obj; 137 | GIVEN("a valid uint8_t") { 138 | auto res = loadXML2Obj(obj, [] { 139 | return "480"; 140 | }); 141 | REQUIRE(res == Result::SUCCESS); 142 | REQUIRE(obj.m1 == 48); 143 | REQUIRE(obj.m2 == 0); // isn't '0' (aka 48) 144 | } 145 | } 146 | 147 | DEFINE_SCHEMA(TestInt, 148 | (int) number); 149 | 150 | SCENARIO("deserialize xml to a number") { 151 | TestInt obj; 152 | GIVEN("a HEX number") { 153 | auto res = loadXML2Obj(obj, [] { 154 | return "0X12"; 155 | }); 156 | REQUIRE(res == Result::SUCCESS); 157 | REQUIRE(obj.number == 0x12); 158 | } 159 | GIVEN("a hex number") { 160 | auto res = loadXML2Obj(obj, [] { 161 | return "0x12"; 162 | }); 163 | REQUIRE(res == Result::SUCCESS); 164 | REQUIRE(obj.number == 0x12); 165 | } 166 | 167 | GIVEN("a hex number") { 168 | auto res = loadXML2Obj(obj, [] { 169 | return "0x"; 170 | }); 171 | REQUIRE(res == Result::ERR_EXTRACTING_FIELD); 172 | } 173 | 174 | } 175 | 176 | SCENARIO("deserialize xml to sum type(std::variant)") { 177 | TestVariant obj; 178 | GIVEN("a string") { 179 | auto res = loadXML2Obj(obj, [] { 180 | return R"( 181 | 182 | hello world! 183 | 184 | )"; 185 | }); 186 | REQUIRE(res == Result::SUCCESS); 187 | REQUIRE(obj.sumType.index() == 2); 188 | REQUIRE(! obj.sumType.valueless_by_exception()); 189 | REQUIRE_THAT(std::get<2>(obj.sumType), 190 | Equals("hello world!")); 191 | } 192 | 193 | GIVEN("a int") { 194 | auto res = loadXML2Obj(obj, [] { 195 | return R"( 196 | 197 | 987654 198 | 199 | )"; 200 | }); 201 | REQUIRE(res == Result::SUCCESS); 202 | REQUIRE(obj.sumType.index() == 1); 203 | REQUIRE(! obj.sumType.valueless_by_exception()); 204 | REQUIRE(std::get<1>(obj.sumType) == 987654); 205 | } 206 | 207 | GIVEN("a point") { 208 | auto res = loadXML2Obj(obj, [] { 209 | return R"( 210 | 211 | 212 | 1.23.4 213 | 214 | 215 | )"; 216 | }); 217 | REQUIRE(res == Result::SUCCESS); 218 | REQUIRE(obj.sumType.index() == 0); 219 | REQUIRE(! obj.sumType.valueless_by_exception()); 220 | auto&& [x, y] = std::get<0>(obj.sumType); 221 | REQUIRE(x == 1.2); 222 | REQUIRE(y == 3.4); 223 | } 224 | 225 | GIVEN("a missing field") { 226 | auto res = loadXML2Obj(obj, [] { 227 | return R"( 228 | 229 | 230 | )"; 231 | }); 232 | REQUIRE(res == Result::ERR_MISSING_FIELD); 233 | } 234 | 235 | GIVEN("a invalid object") { 236 | auto res = loadXML2Obj(obj, [] { 237 | return R"( 238 | 239 | 240 | 1.2 241 | 242 | 243 | )"; 244 | }); 245 | REQUIRE(res == Result::ERR_TYPE); 246 | } 247 | 248 | GIVEN("a empty object") { 249 | auto res = loadXML2Obj(obj, [] { 250 | return R"( 251 | 252 | 253 | 254 | 255 | )"; 256 | }); 257 | REQUIRE(res == Result::ERR_TYPE); 258 | } 259 | } 260 | 261 | SCENARIO("deserialize xml to tree type") { 262 | TestTree obj; 263 | GIVEN("a tree") { 264 | REQUIRE(loadXML2Obj(obj, TREE_CONFIG_PATH) == Result::SUCCESS); 265 | REQUIRE_THAT(obj.name, Equals("hello")); 266 | REQUIRE(obj.children.size() == 3); 267 | REQUIRE_THAT(obj.children[0]->name, Equals("world")); 268 | REQUIRE_THAT(obj.children[1]->name, Equals("first")); 269 | REQUIRE_THAT(obj.children[2]->name, Equals("second")); 270 | REQUIRE(obj.children[2]->children.size() == 1); 271 | REQUIRE_THAT(obj.children[2]->children[0]->name, Equals("leaf")); 272 | } 273 | 274 | } 275 | -------------------------------------------------------------------------------- /test/ut/TinyXML2Test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/04. 3 | // 4 | 5 | #ifdef HAS_TINYXML2 6 | #include 7 | #include 8 | #include 9 | #include "DeserializeConfig.h" 10 | using namespace Catch; 11 | using namespace Catch::Matchers; 12 | using namespace tinyxml2; 13 | 14 | static void checkPoint(XMLElement* point, double x, double y) { 15 | double value; 16 | REQUIRE(point != nullptr); 17 | REQUIRE(point->FirstChildElement("x")->QueryDoubleText(&value) == XML_SUCCESS); 18 | REQUIRE(value == x); 19 | REQUIRE(point->FirstChildElement("y")->QueryDoubleText(&value) == XML_SUCCESS); 20 | REQUIRE(value == y); 21 | }; 22 | 23 | SCENARIO("Load a xml config") { 24 | using namespace xml_config; 25 | WHEN("load point config") { 26 | XMLDocument doc; 27 | REQUIRE(doc.LoadFile(POINT_CONFIG_PATH) == XML_SUCCESS); 28 | auto root = doc.FirstChildElement(); 29 | REQUIRE_THAT(root->Name(), Equals("point")); 30 | 31 | checkPoint(root, 1.2, 3.4); 32 | } 33 | 34 | WHEN("load rect config") { 35 | XMLDocument doc; 36 | REQUIRE(doc.LoadFile(RECT_CONFIG_PATH) == XML_SUCCESS); 37 | auto root = doc.FirstChildElement(); 38 | REQUIRE_THAT(root->Name(), Equals("rect")); 39 | 40 | checkPoint(root->FirstChildElement("p1"), 1.2, 3.4); 41 | checkPoint(root->FirstChildElement("p2"), 5.6, 7.8); 42 | } 43 | 44 | WHEN("load some of points") { 45 | XMLDocument doc; 46 | REQUIRE(doc.LoadFile(SOME_OF_POINTS_CONFIG_PATH) == XML_SUCCESS); 47 | auto root = doc.FirstChildElement(); 48 | REQUIRE_THAT(root->Name(), Equals("some_of_points")); 49 | REQUIRE_THAT(root->FirstChildElement()->GetText(), Equals("Some of points")); 50 | 51 | auto points = root->FirstChildElement("points"); 52 | REQUIRE(points != nullptr); 53 | 54 | double pointsV[] = { 55 | 1.2, 3.4, 56 | 5.6, 7.8, 57 | 2.2, 3.3 58 | }; 59 | size_t pointIdx = 0; 60 | 61 | for (auto point = points->FirstChildElement() 62 | ; point 63 | ; point = point->NextSiblingElement()) { 64 | REQUIRE_THAT(point->Name(), Equals("value")); 65 | checkPoint(point, pointsV[pointIdx], pointsV[pointIdx + 1]); 66 | pointIdx += 2; 67 | } 68 | 69 | } 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /test/ut/TypeSerializerTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/08/09. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "UTSchema.h" 9 | using namespace Catch; 10 | using namespace Catch::Matchers; 11 | using namespace CONFIG_LOADER_NS; 12 | 13 | SCENARIO("test builtin TypeDeserializer") { 14 | GIVEN("vector") { 15 | REQUIRE_THAT(TypeSerializer_v>, 16 | Equals("vector")); 17 | REQUIRE_THAT(TypeSerializer_v>, 18 | Equals("vector")); 19 | REQUIRE_THAT(TypeSerializer_v>, 20 | Equals("vector")); 21 | REQUIRE_THAT(TypeSerializer_v>, 22 | Equals("vector")); 23 | REQUIRE_THAT(TypeSerializer_v>, 24 | Equals("vector")); 25 | REQUIRE_THAT(TypeSerializer_v>, 26 | Equals("vector")); 27 | REQUIRE_THAT(TypeSerializer_v>, 28 | Equals("vector")); 29 | REQUIRE_THAT(TypeSerializer_v>, 30 | Equals("vector")); 31 | 32 | REQUIRE_THAT(TypeSerializer_v>>, 33 | Equals("vector>")); 34 | } 35 | 36 | GIVEN("list") { 37 | REQUIRE_THAT(TypeSerializer_v>, 38 | Equals("list")); 39 | REQUIRE_THAT(TypeSerializer_v>, 40 | Equals("list")); 41 | REQUIRE_THAT(TypeSerializer_v>, 42 | Equals("list")); 43 | REQUIRE_THAT(TypeSerializer_v>, 44 | Equals("list")); 45 | REQUIRE_THAT(TypeSerializer_v>, 46 | Equals("list")); 47 | REQUIRE_THAT(TypeSerializer_v>, 48 | Equals("list")); 49 | REQUIRE_THAT(TypeSerializer_v>, 50 | Equals("list")); 51 | REQUIRE_THAT(TypeSerializer_v>, 52 | Equals("list")); 53 | 54 | REQUIRE_THAT(TypeSerializer_v>>, 55 | Equals("list>")); 56 | } 57 | 58 | GIVEN("optional") { 59 | REQUIRE_THAT(TypeSerializer_v>, 60 | Equals("optional")); 61 | REQUIRE_THAT(TypeSerializer_v>, 62 | Equals("optional")); 63 | REQUIRE_THAT(TypeSerializer_v>, 64 | Equals("optional")); 65 | REQUIRE_THAT(TypeSerializer_v>, 66 | Equals("optional")); 67 | REQUIRE_THAT(TypeSerializer_v>, 68 | Equals("optional")); 69 | REQUIRE_THAT(TypeSerializer_v>, 70 | Equals("optional")); 71 | REQUIRE_THAT(TypeSerializer_v>, 72 | Equals("optional")); 73 | REQUIRE_THAT(TypeSerializer_v>, 74 | Equals("optional")); 75 | 76 | REQUIRE_THAT(TypeSerializer_v>>, 77 | Equals("optional>")); 78 | } 79 | 80 | GIVEN("shared_ptr") { 81 | REQUIRE_THAT(TypeSerializer_v>, 82 | Equals("shared_ptr")); 83 | REQUIRE_THAT(TypeSerializer_v>, 84 | Equals("shared_ptr")); 85 | REQUIRE_THAT(TypeSerializer_v>, 86 | Equals("shared_ptr")); 87 | REQUIRE_THAT(TypeSerializer_v>, 88 | Equals("shared_ptr")); 89 | REQUIRE_THAT(TypeSerializer_v>, 90 | Equals("shared_ptr")); 91 | REQUIRE_THAT(TypeSerializer_v>, 92 | Equals("shared_ptr")); 93 | REQUIRE_THAT(TypeSerializer_v>, 94 | Equals("shared_ptr")); 95 | REQUIRE_THAT(TypeSerializer_v>, 96 | Equals("shared_ptr")); 97 | 98 | REQUIRE_THAT(TypeSerializer_v>>, 99 | Equals("shared_ptr>")); 100 | } 101 | 102 | GIVEN("unique_ptr") { 103 | REQUIRE_THAT(TypeSerializer_v>, 104 | Equals("unique_ptr")); 105 | REQUIRE_THAT(TypeSerializer_v>, 106 | Equals("unique_ptr")); 107 | REQUIRE_THAT(TypeSerializer_v>, 108 | Equals("unique_ptr")); 109 | REQUIRE_THAT(TypeSerializer_v>, 110 | Equals("unique_ptr")); 111 | REQUIRE_THAT(TypeSerializer_v>, 112 | Equals("unique_ptr")); 113 | REQUIRE_THAT(TypeSerializer_v>, 114 | Equals("unique_ptr")); 115 | REQUIRE_THAT(TypeSerializer_v>, 116 | Equals("unique_ptr")); 117 | REQUIRE_THAT(TypeSerializer_v>, 118 | Equals("unique_ptr")); 119 | 120 | REQUIRE_THAT(TypeSerializer_v>>, 121 | Equals("unique_ptr>")); 122 | } 123 | 124 | GIVEN("map/unordered_map") { 125 | REQUIRE_THAT((TypeSerializer_v>), 126 | Equals("map")); 127 | REQUIRE_THAT((TypeSerializer_v>), 128 | Equals("unordered_map")); 129 | } 130 | 131 | GIVEN("variant") { 132 | REQUIRE_THAT((TypeSerializer_v>), 134 | Equals("variant")); 136 | } 137 | } 138 | 139 | SCENARIO("test UTSchema TypeDeserializer") { 140 | REQUIRE_THAT((TypeSerializer_v), 141 | Equals("Point")); 142 | REQUIRE_THAT((TypeSerializer_v>), 143 | Equals("vector")); 144 | } 145 | -------------------------------------------------------------------------------- /test/ut/UTSchema.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/04. 3 | // 4 | 5 | #ifndef CONFIG_LOADER_UTSCHEMA_H 6 | #define CONFIG_LOADER_UTSCHEMA_H 7 | #include 8 | 9 | // define and reflect a struct 10 | 11 | DEFINE_SCHEMA(Point, 12 | (double) x, 13 | (double) y); 14 | 15 | // nested 16 | DEFINE_SCHEMA(Rect, 17 | (Point) p1, 18 | (Point) p2, 19 | (uint32_t) color); 20 | 21 | // vector and string 22 | DEFINE_SCHEMA(SomeOfPoints, 23 | (std::string) name, 24 | (std::vector) points); 25 | 26 | DEFINE_SCHEMA(STLObj, 27 | (std::map) m1, 28 | (std::unordered_map) m2, 29 | (std::optional>) m3, 30 | (std::optional) m4, 31 | (std::optional) m5, 32 | (std::optional>) m6); 33 | 34 | DEFINE_SCHEMA(TestVariant, 35 | (std::variant) sumType); 36 | 37 | DEFINE_SCHEMA(TestTree, 38 | (std::string) name, 39 | (std::vector>) children); 40 | 41 | #endif //CONFIG_LOADER_UTSCHEMA_H 42 | -------------------------------------------------------------------------------- /test/ut/UtilsTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/09. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | SCENARIO("test stringstream") { 9 | std::stringstream ss; 10 | WHEN("extract error") { 11 | ss << "asfdasjl"; 12 | int value; 13 | ss >> value; 14 | REQUIRE(ss.fail()); 15 | } 16 | WHEN("extract empty error") { 17 | ss << ""; 18 | int value; 19 | ss >> value; 20 | REQUIRE(ss.fail()); 21 | } 22 | WHEN("extract int") { 23 | ss << "123"; 24 | int value; 25 | ss >> value; 26 | REQUIRE(! ss.fail()); 27 | REQUIRE(value == 123); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /test/ut/YamlCppDeserializeTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/16. 3 | // 4 | 5 | #include 6 | #include 7 | #include "UTSchema.h" 8 | #include "DeserializeConfig.h" 9 | #include 10 | 11 | using namespace Catch; 12 | using namespace Catch::Matchers; 13 | using namespace CONFIG_LOADER_NS; 14 | using namespace yaml_config; 15 | 16 | SCENARIO("deserialize yaml to struct") { 17 | GIVEN("a flatten point config") { 18 | Point point; 19 | REQUIRE(loadYAML2Obj(point, POINT_CONFIG_PATH) == Result::SUCCESS); 20 | REQUIRE(point.x == 1.2); 21 | REQUIRE(point.y == 3.4); 22 | } 23 | 24 | GIVEN("a nest rect config") { 25 | Rect rect; 26 | REQUIRE(loadYAML2Obj(rect, RECT_CONFIG_PATH) == Result::SUCCESS); 27 | REQUIRE(rect.p1.x == 1.2); 28 | REQUIRE(rect.p1.y == 3.4); 29 | REQUIRE(rect.p2.x == 5.6); 30 | REQUIRE(rect.p2.y == 7.8); 31 | REQUIRE(rect.color == 0x12345678); 32 | } 33 | 34 | GIVEN("a nest rect config that missing p1/p2") { 35 | Rect rect; 36 | auto res = loadYAML2Obj(rect, [] { 37 | return R"("color": 12345678)"; 38 | }); 39 | REQUIRE(res == Result::ERR_MISSING_FIELD); 40 | } 41 | 42 | GIVEN("a complex rect config") { 43 | SomeOfPoints someOfPoints; 44 | REQUIRE(loadYAML2Obj(someOfPoints, SOME_OF_POINTS_CONFIG_PATH) == Result::SUCCESS); 45 | REQUIRE_THAT(someOfPoints.name, 46 | Equals("Some of points")); 47 | REQUIRE(someOfPoints.points.size() == 3); 48 | double pointsV[] = { 49 | 1.2, 3.4, 50 | 5.6, 7.8, 51 | 2.2, 3.3 52 | }; 53 | for (size_t i = 0; i < someOfPoints.points.size(); ++i) { 54 | REQUIRE(someOfPoints.points[i].x == pointsV[i * 2]); 55 | REQUIRE(someOfPoints.points[i].y == pointsV[i * 2 + 1]); 56 | } 57 | } 58 | } 59 | 60 | /////////////////////////////////////////////////////////////////////////////// 61 | SCENARIO("deserialize yaml to compound STL container") { 62 | GIVEN("a valid STL obj") { 63 | STLObj data; 64 | REQUIRE(loadYAML2Obj(data, STLOBJ_CONFIG_PATH) == Result::SUCCESS); 65 | REQUIRE(data.m1[0] == 2); 66 | REQUIRE(data.m1[1] == 4); 67 | REQUIRE(data.m1[2] == 6); 68 | REQUIRE(data.m1.size() == 3); 69 | 70 | REQUIRE(data.m2["hello world"].x == 1.2); 71 | REQUIRE(data.m2["hello world"].y == 3.4); 72 | REQUIRE(data.m2.size() == 1); 73 | 74 | REQUIRE(data.m3.has_value()); 75 | REQUIRE(data.m3->empty()); 76 | 77 | REQUIRE(data.m4.has_value()); 78 | REQUIRE(data.m4->x == 5.6); 79 | REQUIRE(data.m4->y == 7.8); 80 | 81 | REQUIRE(! data.m5.has_value()); 82 | REQUIRE(! data.m6.has_value()); 83 | } 84 | } 85 | 86 | SCENARIO("deserialize yaml to sum type(std::variant)") { 87 | TestVariant obj; 88 | 89 | GIVEN("a string") { 90 | auto res = loadYAML2Obj(obj, [] { 91 | return R"( sumType: hello world! )"; 92 | }); 93 | REQUIRE(res == Result::SUCCESS); 94 | REQUIRE(obj.sumType.index() == 2); 95 | REQUIRE(! obj.sumType.valueless_by_exception()); 96 | REQUIRE_THAT(std::get<2>(obj.sumType), 97 | Equals("hello world!")); 98 | } 99 | 100 | GIVEN("a int") { 101 | auto res = loadYAML2Obj(obj, [] { 102 | return R"( sumType: 987654 )"; 103 | }); 104 | REQUIRE(res == Result::SUCCESS); 105 | REQUIRE(obj.sumType.index() == 1); 106 | REQUIRE(! obj.sumType.valueless_by_exception()); 107 | REQUIRE(std::get<1>(obj.sumType) == 987654); 108 | } 109 | 110 | GIVEN("a point") { 111 | auto res = loadYAML2Obj(obj, [] { 112 | return R"( 113 | sumType: 114 | x: 1.2 115 | y: 3.4 116 | )"; 117 | }); 118 | REQUIRE(res == Result::SUCCESS); 119 | REQUIRE(obj.sumType.index() == 0); 120 | REQUIRE(! obj.sumType.valueless_by_exception()); 121 | auto&& [x, y] = std::get<0>(obj.sumType); 122 | REQUIRE(x == 1.2); 123 | REQUIRE(y == 3.4); 124 | } 125 | 126 | GIVEN("a invalid object") { 127 | auto res = loadYAML2Obj(obj, [] { 128 | return R"( 129 | sumType: 130 | x: 1.2 131 | )"; 132 | }); 133 | REQUIRE(res == Result::ERR_TYPE); 134 | } 135 | 136 | GIVEN("a invalid type") { 137 | auto res = loadYAML2Obj(obj, [] { 138 | return R"( sumType: [1,2,3] )"; 139 | }); 140 | REQUIRE(res == Result::ERR_TYPE); 141 | } 142 | GIVEN("a null type type") { 143 | auto res = loadYAML2Obj(obj, [] { 144 | return R"( dummy: true )"; 145 | }); 146 | REQUIRE(res == Result::ERR_MISSING_FIELD); 147 | } 148 | 149 | GIVEN("a empty object") { 150 | auto res = loadYAML2Obj(obj, [] { 151 | return R"( sumType: [] )"; 152 | }); 153 | REQUIRE(res == Result::ERR_TYPE); 154 | } 155 | } 156 | 157 | SCENARIO("deserialize yaml to tree type") { 158 | TestTree obj; 159 | GIVEN("a tree") { 160 | REQUIRE(loadYAML2Obj(obj, TREE_CONFIG_PATH) == Result::SUCCESS); 161 | REQUIRE_THAT(obj.name, Equals("hello")); 162 | REQUIRE(obj.children.size() == 3); 163 | REQUIRE_THAT(obj.children[0]->name, Equals("world")); 164 | REQUIRE_THAT(obj.children[1]->name, Equals("first")); 165 | REQUIRE_THAT(obj.children[2]->name, Equals("second")); 166 | REQUIRE(obj.children[2]->children.size() == 1); 167 | REQUIRE_THAT(obj.children[2]->children[0]->name, Equals("leaf")); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /test/ut/YamlCppTest.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by netcan on 2021/07/16. 3 | // 4 | 5 | #ifdef HAS_YAMLCPP 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "DeserializeConfig.h" 11 | using namespace Catch; 12 | using namespace Catch::Matchers; 13 | 14 | static void checkPoint(const YAML::Node& node, double x, double y) { 15 | REQUIRE(node["x"].as() == x); 16 | REQUIRE(node["y"].as() == y); 17 | }; 18 | 19 | SCENARIO("Load a yaml config") { 20 | using namespace yaml_config; 21 | GIVEN("a point config") { 22 | YAML::Node root = YAML::LoadFile(POINT_CONFIG_PATH); 23 | REQUIRE(! root.IsNull()); 24 | checkPoint(root, 1.2, 3.4); 25 | } 26 | 27 | GIVEN("a rect config") { 28 | YAML::Node root = YAML::LoadFile(RECT_CONFIG_PATH); 29 | REQUIRE(! root.IsNull()); 30 | checkPoint(root["p1"], 1.2, 3.4); 31 | checkPoint(root["p2"], 5.6, 7.8); 32 | REQUIRE(root["color"].as() == 0x12345678); 33 | } 34 | 35 | GIVEN("some of points") { 36 | YAML::Node root = YAML::LoadFile(SOME_OF_POINTS_CONFIG_PATH); 37 | REQUIRE(! root.IsNull()); 38 | REQUIRE_THAT(root["name"].as(), Equals("Some of points")); 39 | double pointsV[] = { 40 | 1.2, 3.4, 41 | 5.6, 7.8, 42 | 2.2, 3.3 43 | }; 44 | size_t pointIdx = 0; 45 | for (auto&& point = root["point"].begin() 46 | ; point != root["point"].end() 47 | ; ++point) { 48 | checkPoint(*point, pointsV[pointIdx], pointsV[pointIdx + 1]); 49 | pointIdx += 2; 50 | } 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /test/ut/configs/json/Point.json: -------------------------------------------------------------------------------- 1 | { "x": 1.2, "y": 3.4 } -------------------------------------------------------------------------------- /test/ut/configs/json/Rect.json: -------------------------------------------------------------------------------- 1 | { 2 | "p1": { "x": 1.2, "y": 3.4 }, 3 | "p2": { "x": 5.6, "y": 7.8 }, 4 | "color": 12345678 5 | } 6 | -------------------------------------------------------------------------------- /test/ut/configs/json/STLObj.json: -------------------------------------------------------------------------------- 1 | { 2 | "m1": { "0": 2, "1": 4, "2": 6, }, 3 | "m2": { 4 | "hello world": { "x": 1.2, "y": 3.4 } 5 | }, 6 | "m3": {}, 7 | "m4": { "x": 5.6, "y": 7.8 } 8 | } 9 | -------------------------------------------------------------------------------- /test/ut/configs/json/SomeOfPoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Some of points", 3 | "points":[ 4 | { "x": 1.2, "y": 3.4 }, 5 | { "x": 5.6, "y": 7.8 }, 6 | { "x": 2.2, "y": 3.3 } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/ut/configs/json/Tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello", 3 | "children": [ 4 | { "name": "world", "children": [] }, 5 | { "name": "first", "children": [] }, 6 | { "name": "second", 7 | "children": [ 8 | { "name" : "leaf", "children": [] } 9 | ] 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/ut/configs/xml/Point.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.2 4 | 3.4 5 | -------------------------------------------------------------------------------- /test/ut/configs/xml/Rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.2 3.4 4 | 5.6 7.8 5 | 0x12345678 6 | 7 | -------------------------------------------------------------------------------- /test/ut/configs/xml/STLObj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2 4 | 4 5 | 6 6 | 8 7 | 8 | 9 | 10 | 1.2 11 | 3.4 12 | 13 | 14 | 15 | 5.6 16 | 7.8 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/ut/configs/xml/SomeOfPoints.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Some of points 4 | 5 | 1.2 3.4 6 | 5.6 7.8 7 | 2.2 3.3 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/ut/configs/xml/Tree.xml: -------------------------------------------------------------------------------- 1 | 2 | hello 3 | 4 | world 5 | first 6 | 7 | second 8 | 9 | leaf 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/ut/configs/yml/Point.yml: -------------------------------------------------------------------------------- 1 | x: 1.2 2 | y: 3.4 3 | -------------------------------------------------------------------------------- /test/ut/configs/yml/Rect.yml: -------------------------------------------------------------------------------- 1 | p1: 2 | x: 1.2 3 | y: 3.4 4 | p2: 5 | x: 5.6 6 | y: 7.8 7 | color: 0x12345678 8 | -------------------------------------------------------------------------------- /test/ut/configs/yml/STLObj.yml: -------------------------------------------------------------------------------- 1 | m1: 2 | 0: 2 3 | 1: 4 4 | 2: 6 5 | m2: 6 | hello world: 7 | x: 1.2 8 | y: 3.4 9 | m3: [] 10 | m4: 11 | x: 5.6 12 | y: 7.8 13 | -------------------------------------------------------------------------------- /test/ut/configs/yml/SomeOfPoints.yml: -------------------------------------------------------------------------------- 1 | name: Some of points 2 | points: 3 | - x: 1.2 4 | y: 3.4 5 | - x: 5.6 6 | y: 7.8 7 | - x: 2.2 8 | y: 3.3 9 | -------------------------------------------------------------------------------- /test/ut/configs/yml/Tree.yml: -------------------------------------------------------------------------------- 1 | name: hello 2 | children: 3 | - name: world 4 | children: [] 5 | - name: first 6 | children: [] 7 | - name: second 8 | children: 9 | - name: leaf 10 | children: [] 11 | -------------------------------------------------------------------------------- /third-party/ThirdParty.cmake: -------------------------------------------------------------------------------- 1 | if (${BUILD_TESTS}) 2 | add_subdirectory(third-party/Catch2) 3 | endif() 4 | 5 | if (HAS_TINYXML2) 6 | target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_TINYXML2) 7 | add_subdirectory(third-party/tinyxml2) 8 | list(APPEND THIRD_PARTY_LIBRARIES tinyxml2::tinyxml2) 9 | endif() 10 | 11 | if (HAS_JSONCPP) 12 | target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_JSONCPP) 13 | add_subdirectory(third-party/jsoncpp) 14 | list(APPEND THIRD_PARTY_LIBRARIES jsoncpp_static) 15 | endif() 16 | 17 | if (HAS_YAMLCPP) 18 | target_compile_definitions(${PROJECT_NAME} PUBLIC HAS_YAMLCPP) 19 | add_subdirectory(third-party/yaml-cpp) 20 | list(APPEND THIRD_PARTY_LIBRARIES yaml-cpp) 21 | endif() --------------------------------------------------------------------------------