├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── cppproperties ├── CMakeLists.txt ├── archiver.hpp ├── archiver_gpds.hpp ├── archiver_json.hpp ├── archiver_xml.hpp ├── cppproperties-config.cmake ├── cppproperties.hpp ├── exceptions.hpp ├── external.cmake ├── properties.hpp ├── property.hpp ├── property_ex.hpp ├── property_ex_boost.hpp ├── property_ex_cpp.hpp ├── property_ex_cpp_modern.hpp ├── property_ex_qt.hpp └── qt_widgets │ ├── boolean.hpp │ ├── factory.hpp │ ├── integer.hpp │ ├── nested.hpp │ └── widget_base.hpp ├── doc └── uml │ └── model.mdj ├── examples ├── CMakeLists.txt ├── basic │ ├── CMakeLists.txt │ └── main.cpp ├── custom_types │ ├── CMakeLists.txt │ └── main.cpp ├── gpds │ ├── CMakeLists.txt │ └── main.cpp ├── json │ ├── CMakeLists.txt │ └── main.cpp ├── linked_properties │ ├── CMakeLists.txt │ └── main.cpp ├── linked_property_functions │ ├── CMakeLists.txt │ └── main.cpp ├── nested │ ├── CMakeLists.txt │ └── main.cpp ├── notification │ ├── CMakeLists.txt │ └── main.cpp ├── qt_widgets │ ├── CMakeLists.txt │ └── main.cpp ├── serialization │ ├── CMakeLists.txt │ └── main.cpp └── xml │ ├── CMakeLists.txt │ └── main.cpp ├── readme.md └── test ├── 3rdparty └── doctest │ └── doctest.hpp ├── CMakeLists.txt ├── test.hpp ├── test_main.cpp └── test_suites ├── 01_builtin_types.cpp ├── 02_builtin_types_cpp.cpp ├── 03_nested.cpp ├── CMakeLists.txt ├── attributes.cpp ├── boost.cpp ├── isolation.cpp ├── iterable.cpp ├── linked_properties.cpp ├── properties.cpp ├── property.cpp ├── qt.cpp └── serialization_json.cpp /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [main] 7 | 8 | env: 9 | BUILD_TYPE: Release 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | toolchain: 17 | [ 18 | { 19 | name: "Ubuntu GCC", 20 | cxx: "g++", 21 | cc: "gcc", 22 | packages: "nlohmann-json3-dev libtinyxml2-dev", 23 | os: ubuntu-latest, 24 | }, 25 | , 26 | { 27 | name: "Ubuntu Clang", 28 | cxx: "clang++", 29 | cc: "clang", 30 | packages: "nlohmann-json3-dev libtinyxml2-dev", 31 | os: ubuntu-latest, 32 | } 33 | ] 34 | 35 | continue-on-error: false 36 | runs-on: ${{ matrix.toolchain.os }} 37 | env: 38 | CC: ${{ matrix.toolchain.cc }} 39 | CXX: ${{ matrix.toolchain.cxx }} 40 | 41 | name: "${{ matrix.toolchain.name }}" 42 | if: "!contains(github.event.head_commit.message, '[ci skip]')" 43 | steps: 44 | - uses: actions/checkout@v2 45 | 46 | - name: Install Ninja 47 | uses: seanmiddleditch/gha-setup-ninja@master 48 | 49 | - name: Install packages (via apt) 50 | if: runner.os == 'Linux' 51 | run: sudo apt install ${{ matrix.toolchain.packages }} -y 52 | 53 | - name: Configure 54 | run: cmake -Bbuild -GNinja -DCPPPROPERTIES_BUILD_EXAMPLES=ON -DCPPPROPERTIES_BUILD_TESTS=ON 55 | 56 | - name: Build 57 | run: cmake --build build 58 | 59 | - name: Run tests 60 | run: ./build/test/tests 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cppcheck*.xml 2 | .build/ 3 | doc/doxygen/ 4 | 5 | 6 | # Created by https://www.toptal.com/developers/gitignore/api/clion+all 7 | # Edit at https://www.toptal.com/developers/gitignore?templates=clion+all 8 | 9 | ### CLion+all ### 10 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 11 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 12 | 13 | # User-specific stuff 14 | .idea/**/workspace.xml 15 | .idea/**/tasks.xml 16 | .idea/**/usage.statistics.xml 17 | .idea/**/dictionaries 18 | .idea/**/shelf 19 | 20 | # AWS User-specific 21 | .idea/**/aws.xml 22 | 23 | # Generated files 24 | .idea/**/contentModel.xml 25 | 26 | # Sensitive or high-churn files 27 | .idea/**/dataSources/ 28 | .idea/**/dataSources.ids 29 | .idea/**/dataSources.local.xml 30 | .idea/**/sqlDataSources.xml 31 | .idea/**/dynamic.xml 32 | .idea/**/uiDesigner.xml 33 | .idea/**/dbnavigator.xml 34 | 35 | # Gradle 36 | .idea/**/gradle.xml 37 | .idea/**/libraries 38 | 39 | # Gradle and Maven with auto-import 40 | # When using Gradle or Maven with auto-import, you should exclude module files, 41 | # since they will be recreated, and may cause churn. Uncomment if using 42 | # auto-import. 43 | # .idea/artifacts 44 | # .idea/compiler.xml 45 | # .idea/jarRepositories.xml 46 | # .idea/modules.xml 47 | # .idea/*.iml 48 | # .idea/modules 49 | # *.iml 50 | # *.ipr 51 | 52 | # CMake 53 | cmake-build-*/ 54 | 55 | # Mongo Explorer plugin 56 | .idea/**/mongoSettings.xml 57 | 58 | # File-based project format 59 | *.iws 60 | 61 | # IntelliJ 62 | out/ 63 | 64 | # mpeltonen/sbt-idea plugin 65 | .idea_modules/ 66 | 67 | # JIRA plugin 68 | atlassian-ide-plugin.xml 69 | 70 | # Cursive Clojure plugin 71 | .idea/replstate.xml 72 | 73 | # SonarLint plugin 74 | .idea/sonarlint/ 75 | 76 | # Crashlytics plugin (for Android Studio and IntelliJ) 77 | com_crashlytics_export_strings.xml 78 | crashlytics.properties 79 | crashlytics-build.properties 80 | fabric.properties 81 | 82 | # Editor-based Rest Client 83 | .idea/httpRequests 84 | 85 | # Android studio 3.1+ serialized cache file 86 | .idea/caches/build_file_checksums.ser 87 | 88 | ### CLion+all Patch ### 89 | # Ignore everything but code style settings and run configurations 90 | # that are supposed to be shared within teams. 91 | 92 | .idea/* 93 | 94 | !.idea/codeStyles 95 | !.idea/runConfigurations 96 | 97 | # End of https://www.toptal.com/developers/gitignore/api/clion+all 98 | 99 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | 3 | project( 4 | cppproperties 5 | VERSION 0.1.0 6 | LANGUAGES CXX 7 | HOMEPAGE_URL "https://github.com/tectu/cppproperties" 8 | ) 9 | 10 | # User options 11 | option(CPPPROPERTIES_BUILD_EXAMPLES "Whether to build examples." ON) 12 | option(CPPPROPERTIES_BUILD_TESTS "Whether to build tests." ON) 13 | option(CPPPROPERTIES_ENABLE_BOOST "Whether to enable built-in support for common boost types." OFF) 14 | option(CPPPROPERTIES_ENABLE_QT "Whether to enable built-in support for Qt types." OFF) 15 | option(CPPPROPERTIES_ENABLE_QT_WIDGETS "Whether to enable built-in support for Qt widgets." OFF) 16 | option(CPPPROPERTIES_ENABLE_JSON "Whether to enable JSON (de)serialization." ON) 17 | option(CPPPROPERTIES_ENABLE_XML "Whether to enable XML (de)serialization." ON) 18 | option(CPPPROPERTIES_ENABLE_GPDS "Whether to enable GPDS (de)serialization." OFF) 19 | option(CPPPROPERTIES_PERFORM_CPPCHECK "Whether to run cppcheck." OFF) 20 | 21 | # Include examples (if supposed to) 22 | if (CPPPROPERTIES_BUILD_EXAMPLES) 23 | add_subdirectory(examples) 24 | endif() 25 | 26 | # Add the actual library 27 | add_subdirectory(cppproperties) 28 | 29 | # Add tests 30 | if (CPPPROPERTIES_BUILD_TESTS) 31 | enable_testing() 32 | add_subdirectory(test) 33 | endif() 34 | 35 | if (CPPPROPERTIES_PERFORM_CPPCHECK) 36 | find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck) 37 | if (CMAKE_CXX_CPPCHECK) 38 | list( 39 | APPEND CMAKE_CXX_CPPCHECK 40 | "--enable=warning" 41 | "--inconclusive" 42 | "--force" 43 | "--inline-suppr" 44 | "--suppressions-list=${CMAKE_SOURCE_DIR}/CppCheckSuppressions.txt" 45 | ) 46 | endif() 47 | endif() 48 | 49 | # Print some information 50 | message(STATUS "----------------------------") 51 | message(STATUS "cpp-properties configuration") 52 | message(STATUS "") 53 | message(STATUS "Build:") 54 | message(STATUS " Examples : " ${CPPPROPERTIES_BUILD_EXAMPLES}) 55 | message(STATUS " Tests : " ${CPPPROPERTIES_BUILD_TESTS}) 56 | message(STATUS "") 57 | message(STATUS "Types:") 58 | message(STATUS " Boost : " ${CPPPROPERTIES_ENABLE_BOOST}) 59 | message(STATUS " Qt : " ${CPPPROPERTIES_ENABLE_QT}) 60 | message(STATUS "") 61 | message(STATUS "Serialization:") 62 | message(STATUS " JSON : " ${CPPPROPERTIES_ENABLE_JSON}) 63 | message(STATUS " XML : " ${CPPPROPERTIES_ENABLE_XML}) 64 | message(STATUS " GPDS : " ${CPPPROPERTIES_ENABLE_GPDS}) 65 | message(STATUS "") 66 | message(STATUS "GUI Elements:") 67 | message(STATUS " Qt : " ${CPPPROPERTIES_ENABLE_QT_WIDGETS}) 68 | message(STATUS "----------------------------") 69 | 70 | 71 | ################################################################################ 72 | # CPack # 73 | ################################################################################ 74 | set(CPACK_PACKAGE_VENDOR "Joel Bodenmann") 75 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A library to bring a property system to modern C++.") 76 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 77 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/readme.md") 78 | include(CPack) 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Joel Bodenmann 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 | -------------------------------------------------------------------------------- /cppproperties/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Include external dependencies 2 | include(external.cmake) 3 | 4 | # Variables for convenience 5 | set(TARGET cppproperties) 6 | 7 | # List of public headers 8 | set(HEADERS_PUBLIC 9 | archiver.hpp 10 | $<$:${CMAKE_CURRENT_LIST_DIR}/archiver_json.hpp> 11 | $<$:${CMAKE_CURRENT_LIST_DIR}/archiver_xml.hpp> 12 | $<$:${CMAKE_CURRENT_LIST_DIR}/archiver_gpds.hpp> 13 | exceptions.hpp 14 | properties.hpp 15 | property.hpp 16 | property_ex.hpp 17 | property_ex_cpp.hpp 18 | property_ex_cpp_modern.hpp 19 | $<$:${CMAKE_CURRENT_LIST_DIR}/property_ex_boost.hpp> 20 | $<$:${CMAKE_CURRENT_LIST_DIR}/property_ex_qt.hpp> 21 | ) 22 | 23 | set(HEADERS_QT_WIDGETS_PUBLIC 24 | ${CMAKE_CURRENT_LIST_DIR}/qt_widgets/factory.hpp 25 | ${CMAKE_CURRENT_LIST_DIR}/qt_widgets/widget_base.hpp 26 | ${CMAKE_CURRENT_LIST_DIR}/qt_widgets/boolean.hpp 27 | ${CMAKE_CURRENT_LIST_DIR}/qt_widgets/integer.hpp 28 | ${CMAKE_CURRENT_LIST_DIR}/qt_widgets/nested.hpp 29 | ) 30 | 31 | add_library(${TARGET} INTERFACE IMPORTED GLOBAL) 32 | target_compile_features( 33 | ${TARGET} 34 | INTERFACE 35 | cxx_std_20 36 | ) 37 | 38 | target_compile_definitions( 39 | ${TARGET} 40 | INTERFACE 41 | $<$:CPPPROPERTIES_ENABLE_BOOST> 42 | $<$:CPPPROPERTIES_ENABLE_QT> 43 | $<$:CPPPROPERTIES_ENABLE_QT_WIDGETS> 44 | $<$:CPPPROPERTIES_ENABLE_JSON> 45 | $<$:CPPPROPERTIES_ENABLE_XML> 46 | $<$:CPPPROPERTIES_ENABLE_GPDS> 47 | ) 48 | 49 | target_sources( 50 | ${TARGET} 51 | INTERFACE 52 | ${HEADERS_PUBLIC} 53 | $<$:${HEADERS_QT_WIDGETS_PUBLIC}> 54 | ) 55 | 56 | target_link_libraries( 57 | ${TARGET} 58 | INTERFACE 59 | $<$:Boost::headers> 60 | $<$:Qt::Core> 61 | $<$:gpds::gpds-shared> 62 | $<$:nlohmann_json::nlohmann_json> 63 | $<$:tinyxml2::tinyxml2> 64 | $<$:Qt::Gui> 65 | $<$:Qt::Widgets> 66 | ) 67 | 68 | if (CPPPROPERTIES_ENABLE_QT_WIDGETS) 69 | set_target_properties( 70 | ${TARGET} 71 | PROPERTIES 72 | AUTOMOC ON 73 | ) 74 | endif() 75 | -------------------------------------------------------------------------------- /cppproperties/archiver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace tct::properties 8 | { 9 | 10 | class properties; 11 | 12 | /** 13 | * Archiver interface for (de)serialization of properties. 14 | * 15 | * Implementing this interface allows adding support for (de)serialization 16 | * using any suitable format. For example, there are built-in archivers (by 17 | * implementing this interface) for JSON and XML (de)serialization. 18 | */ 19 | struct archiver 20 | { 21 | public: 22 | /** 23 | * Serialize properties to string. 24 | * 25 | * @param p The properties to serialize. 26 | * @return The serialized properties. 27 | */ 28 | [[nodiscard]] 29 | virtual 30 | std::string 31 | save(const properties& p) const = 0; 32 | 33 | /** 34 | * Deserialize properties from string. 35 | * 36 | * @param p The target properties. 37 | * @param str The serialized properties. 38 | * @return `true` on success, `false` on failure with optional error message. 39 | */ 40 | virtual 41 | std::pair 42 | load(properties& p, const std::string& str) const = 0; 43 | 44 | /** 45 | * Serialize properties to a file. 46 | * 47 | * @param p The properties to serialize to a file. 48 | * @param path The path of the output file. 49 | * @return `true` on success, `false` on failure with optional error message. 50 | */ 51 | [[nodiscard("file i/o might fail")]] 52 | std::pair 53 | save(const properties& p, const std::filesystem::path& path) const 54 | { 55 | // Prepare file 56 | std::ofstream file; 57 | file.open(path, std::ios::out | std::ios::trunc); 58 | if (not file.is_open()) 59 | return { false, "Could not open file for writing at path " + path.string() }; 60 | 61 | // Write to file 62 | file << save(p); 63 | 64 | // Close file 65 | file.close(); 66 | 67 | return { true, "" }; 68 | } 69 | 70 | /** 71 | * Deserialize properties from a file. 72 | * 73 | * @param p The target properties. 74 | * @param path The path of the input file. 75 | * @return `true` on success, `false` on failure with optional error message. 76 | */ 77 | [[nodiscard("file i/o might fail")]] 78 | std::pair 79 | load(properties& p, const std::filesystem::path& path) const 80 | { 81 | // Prepare file 82 | std::ifstream file; 83 | file.open(path, std::ios::in); 84 | if (not file.is_open()) 85 | return { false, "Could not open file for reading at path " + path.string() }; 86 | 87 | // Read from file 88 | std::stringstream ss; 89 | ss << file.rdbuf(); 90 | 91 | // Close the file 92 | file.close(); 93 | 94 | // Load from string 95 | return load(p, ss.str()); 96 | } 97 | }; 98 | 99 | } 100 | -------------------------------------------------------------------------------- /cppproperties/archiver_gpds.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gpds 4 | { 5 | class container; 6 | } 7 | 8 | namespace tct::properties 9 | { 10 | class properties; 11 | 12 | /** 13 | * Built-in archiver for (de)serializing to/from GPDS (https://github.com/simulton/gpds). 14 | */ 15 | class archiver_gpds 16 | { 17 | public: 18 | /** 19 | * Serialize properties to GPDS container. 20 | * 21 | * @param p The properties to serialize. 22 | * @return The serialized container. 23 | */ 24 | [[nodiscard]] 25 | gpds::container 26 | save(const properties& p) const; 27 | 28 | /** 29 | * Deserialize properties from a GPDS container. 30 | * 31 | * @param p The properties to deserialize into. 32 | * @param c The container to deserialize. 33 | * @return @p true on success, @p false otherwise with an optional error message. 34 | */ 35 | std::pair 36 | load(properties&p, const gpds::container& c) const; 37 | 38 | private: 39 | static 40 | void 41 | write_recursively(gpds::container& root, const ::tct::properties::properties& p); 42 | 43 | static 44 | void 45 | read_recursively(const gpds::container& root, ::tct::properties::properties& p); 46 | }; 47 | } 48 | 49 | #include 50 | 51 | #include "properties.hpp" 52 | 53 | namespace tct::properties 54 | { 55 | 56 | inline 57 | gpds::container 58 | archiver_gpds::save(const properties& p) const 59 | { 60 | // Create container 61 | gpds::container c; 62 | 63 | // Iterate properties 64 | write_recursively(c, p); 65 | 66 | return c; 67 | } 68 | 69 | inline 70 | std::pair 71 | archiver_gpds::load(properties& p, const gpds::container& c) const 72 | { 73 | read_recursively(c, p); 74 | 75 | return { true, "success." }; 76 | } 77 | 78 | inline 79 | void 80 | archiver_gpds::write_recursively(gpds::container& root, const ::tct::properties::properties& p) 81 | { 82 | // Values 83 | for (const auto& [key, value] : p) { 84 | assert(!key.empty()); 85 | assert(value); 86 | 87 | // Check if nested 88 | const properties* nested = dynamic_cast(value); 89 | if (nested) { 90 | // Create container 91 | gpds::container c; 92 | 93 | // Values 94 | write_recursively(c, *nested); 95 | 96 | // Add to root 97 | root.add_value(key, c); 98 | } 99 | 100 | // Not nested 101 | else { 102 | assert(value->to_string); 103 | 104 | // Value 105 | gpds::value& gpds_v = root.add_value(key, value->to_string()); 106 | 107 | // Attributes 108 | for (const auto& [attr_key, attr_value] : value->attributes()) 109 | gpds_v.add_attribute(attr_key.c_str(), attr_value); 110 | } 111 | } 112 | 113 | // Attributes 114 | for (const auto& [attr_key, attr_value] : p.attributes()) 115 | root.add_attribute(attr_key.c_str(), attr_value); 116 | } 117 | 118 | inline 119 | void 120 | archiver_gpds::read_recursively(const gpds::container& root, ::tct::properties::properties& p) 121 | { 122 | // Iterate properties 123 | for (auto& [key, value] : p) { 124 | // Get GPDS container element 125 | assert(root.values.count(key) == 1); 126 | const auto& it = root.values.find(key); 127 | assert(it != std::cend(root.values)); 128 | const gpds::value& v = it->second; 129 | 130 | // Check if nested 131 | if (v.is_type()) { 132 | // Find the nested properties 133 | properties* nested = p.get_nested_properties(key); 134 | if (!nested) 135 | throw std::runtime_error("Could not retrieve nested property \"" + key + "\"."); 136 | 137 | // Get the nested GPDS container 138 | if (const auto& cc = v.get(); cc) 139 | read_recursively(**cc, *nested); 140 | } 141 | 142 | // Not nested 143 | else { 144 | if (!v.is_type()) 145 | continue; 146 | 147 | value->from_string(v.get().value()); 148 | 149 | // Attributes 150 | for (const auto& [attr_key, attr_value] : v.attributes.map) 151 | value->set_attribute(attr_key, attr_value); 152 | } 153 | } 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /cppproperties/archiver_json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "archiver.hpp" 4 | 5 | namespace tct::properties 6 | { 7 | 8 | class properties; 9 | 10 | /** 11 | * Built-in archiver for (de)serialization to/from JSON. 12 | * 13 | * @details This implementation uses `nlohmann::json`. 14 | */ 15 | class archiver_json : 16 | public archiver 17 | { 18 | public: 19 | [[nodiscard]] 20 | std::string 21 | save(const properties& p) const override; 22 | 23 | std::pair 24 | load(properties& p, const std::string& str) const override; 25 | 26 | private: 27 | static 28 | void 29 | write_recursively(); 30 | 31 | static 32 | void 33 | read_recursively(); 34 | }; 35 | 36 | } 37 | 38 | 39 | #include 40 | 41 | #include "properties.hpp" 42 | 43 | namespace tct::properties 44 | { 45 | 46 | inline 47 | std::string 48 | archiver_json::save(const properties& p) const 49 | { 50 | nlohmann::json json; 51 | 52 | for (const auto& [key, value] : p) { 53 | json[key] = value->to_string(); 54 | } 55 | 56 | return json.dump(4); 57 | } 58 | 59 | inline 60 | std::pair 61 | archiver_json::load(properties& p, const std::string& str) const 62 | { 63 | nlohmann::json json(str); 64 | 65 | return { true, "success" }; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /cppproperties/archiver_xml.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "archiver.hpp" 4 | 5 | namespace tinyxml2 6 | { 7 | class XMLElement; 8 | class XMLDocument; 9 | } 10 | 11 | namespace tct::properties 12 | { 13 | 14 | class properties; 15 | 16 | /** 17 | * Built-in archiver for (de)serialization to/from XML. 18 | * 19 | * @details This implementation uses the tinyxml2 library. 20 | */ 21 | class archiver_xml : 22 | public archiver 23 | { 24 | public: 25 | [[nodiscard]] 26 | std::string 27 | save(const properties& p) const override; 28 | 29 | std::pair 30 | load(properties& p, const std::string& str) const override; 31 | 32 | private: 33 | static 34 | void 35 | write_recursively(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement& root, const ::tct::properties::properties& p); 36 | 37 | static 38 | void 39 | read_recursively(tinyxml2::XMLElement& root, ::tct::properties::properties& p); 40 | }; 41 | 42 | } 43 | 44 | #include 45 | 46 | #include 47 | 48 | #include "properties.hpp" 49 | 50 | namespace tct::properties 51 | { 52 | 53 | inline 54 | std::string 55 | archiver_xml::save(const properties& p) const 56 | { 57 | // Create document 58 | tinyxml2::XMLDocument doc; 59 | 60 | // Add the root node 61 | tinyxml2::XMLElement* root = doc.NewElement("properties"); 62 | doc.InsertEndChild(root); 63 | 64 | // Iterate properties 65 | write_recursively(doc, *root, p); 66 | 67 | // Print to string 68 | tinyxml2::XMLPrinter printer; 69 | doc.Print(&printer); 70 | std::string str = printer.CStr(); 71 | 72 | // Free up memory 73 | doc.Clear(); 74 | 75 | return str; 76 | } 77 | 78 | inline 79 | std::pair 80 | archiver_xml::load(properties& p, const std::string& str) const 81 | { 82 | // Create document 83 | tinyxml2::XMLDocument doc; 84 | doc.Parse(str.data()); 85 | 86 | // Retrieve the root node 87 | tinyxml2::XMLElement* root = doc.FirstChildElement("properties"); 88 | if (!root) 89 | return { false, "Could not find root node \"properties\"" }; 90 | 91 | // Iterate properties 92 | read_recursively(*root, p); 93 | 94 | return { true, "" }; 95 | } 96 | 97 | inline 98 | void 99 | archiver_xml::write_recursively(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement& root, const ::tct::properties::properties& p) 100 | { 101 | for (const auto& [key, value] : p) { 102 | assert(!key.empty()); 103 | assert(value); 104 | 105 | // Attributes 106 | for (const auto& [attr_key, attr_value] : p.attributes()) 107 | root.SetAttribute(attr_key.c_str(), attr_value.c_str()); 108 | 109 | // Create new element 110 | tinyxml2::XMLElement* element = doc.NewElement(key.c_str()); 111 | if (!element) 112 | throw std::runtime_error("Could not create new tinyxml::XMLElement object"); 113 | 114 | // Check if nested 115 | const properties* nested = dynamic_cast(value); 116 | if (nested) 117 | write_recursively(doc, *element, *nested); 118 | 119 | // Not nested 120 | else { 121 | assert(value->to_string); 122 | 123 | // Value 124 | element->SetText(value->to_string().c_str()); 125 | 126 | // Attributes 127 | for (const auto& [attr_key, attr_value] : value->attributes()) 128 | element->SetAttribute(attr_key.c_str(), attr_value.c_str()); 129 | } 130 | 131 | root.InsertEndChild(element); 132 | } 133 | } 134 | 135 | inline 136 | void 137 | archiver_xml::read_recursively(tinyxml2::XMLElement& root, ::tct::properties::properties& p) 138 | { 139 | // Attributes 140 | for (const tinyxml2::XMLAttribute* attribute = root.FirstAttribute(); attribute; attribute = attribute->Next()) 141 | p.set_attribute(attribute->Name(), attribute->Value()); 142 | 143 | // Iterate properties 144 | for (auto& [key, value] : p) { 145 | tinyxml2::XMLElement* element = root.FirstChildElement(key.c_str()); 146 | if (!element) 147 | continue; 148 | 149 | // Check if nested 150 | if (!element->GetText()) { 151 | // Find the nested properties 152 | properties* nested = p.get_nested_properties(key); 153 | if (!nested) 154 | throw std::runtime_error("Could not retrieve nested property \"" + key + "\"."); 155 | 156 | read_recursively(*element, *nested); 157 | } 158 | 159 | // Not nested 160 | else { 161 | if (element->GetText()) { 162 | const std::string value_str(element->GetText()); 163 | assert(value); 164 | value->from_string(element->GetText()); 165 | } 166 | 167 | // Attributes 168 | for (const tinyxml2::XMLAttribute* attribute = element->FirstAttribute(); attribute; attribute = attribute->Next()) 169 | value->set_attribute(attribute->Name(), attribute->Value()); 170 | } 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /cppproperties/cppproperties-config.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/cppproperties-targets.cmake") 2 | -------------------------------------------------------------------------------- /cppproperties/cppproperties.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * The top-level namespace. 5 | * 6 | * Everything in this library is enclosed in this namespace. 7 | */ 8 | namespace tct::cppproperties 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /cppproperties/exceptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace tct::properties 6 | { 7 | 8 | /** 9 | * Exception indicating that a non-existing property was accessed. 10 | */ 11 | struct property_nonexist : 12 | std::runtime_error 13 | { 14 | /** 15 | * Constructor. 16 | * 17 | * @param _property_name The name of the property that does not exist. 18 | */ 19 | explicit 20 | property_nonexist(const std::string& _property_name) : 21 | std::runtime_error("property \"" + _property_name + "\" does not exist."), 22 | property_name(_property_name) 23 | { 24 | } 25 | 26 | private: 27 | std::string property_name; 28 | }; 29 | 30 | /** 31 | * Exception indicating that a property already exists. 32 | */ 33 | struct property_exists : 34 | std::runtime_error 35 | { 36 | /** 37 | * Constructor. 38 | * 39 | * @param _property_name The name of the property that exists. 40 | */ 41 | explicit 42 | property_exists(const std::string& _property_name) : 43 | std::runtime_error("property \"" + _property_name + "\" exists already."), 44 | property_name(_property_name) 45 | { 46 | } 47 | 48 | private: 49 | std::string property_name; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /cppproperties/external.cmake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | # Qt 4 | if (CPPPROPERTIES_ENABLE_QT) 5 | find_package( 6 | Qt6 7 | REQUIRED 8 | COMPONENTS 9 | Core 10 | ) 11 | endif() 12 | if (CPPPROPERTIES_ENABLE_QT_WIDGETS) 13 | find_package( 14 | Qt6 15 | REQUIRED 16 | COMPONENTS 17 | Gui 18 | Widgets 19 | ) 20 | endif() 21 | 22 | # Boost 23 | if (CPPPROPERTIES_ENABLE_BOOST) 24 | find_package( 25 | Boost 26 | REQUIRED 27 | ) 28 | endif() 29 | 30 | # GPDS 31 | if (CPPPROPERTIES_ENABLE_GPDS) 32 | find_package( 33 | gpds 34 | REQUIRED 35 | ) 36 | endif() 37 | 38 | # JSON 39 | if (CPPPROPERTIES_ENABLE_JSON) 40 | find_package( 41 | nlohmann_json 42 | REQUIRED 43 | ) 44 | endif() 45 | 46 | # XML 47 | if (CPPPROPERTIES_ENABLE_XML) 48 | find_package( 49 | tinyxml2 50 | REQUIRED 51 | ) 52 | endif() 53 | -------------------------------------------------------------------------------- /cppproperties/properties.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "archiver.hpp" 11 | #include "exceptions.hpp" 12 | 13 | #define MAKE_PROPERTY(name, type) \ 14 | ::tct::properties::property& name = make_property(#name); 15 | 16 | #define MAKE_NESTED_PROPERTY(name, type) \ 17 | type& name = make_nested_property(#name); 18 | 19 | #define LINK_PROPERTY(name, ptr) \ 20 | make_linked_property(#name, ptr); 21 | 22 | #define LINK_PROPERTY_FUNCTIONS(name, type, setter, getter) \ 23 | make_linked_property_functions( \ 24 | #name, \ 25 | std::bind(&setter, this, std::placeholders::_1), \ 26 | std::bind(&getter, this) \ 27 | ); 28 | 29 | #define REGISTER_PROPERTY(type, f_to_string, f_from_string) \ 30 | template<> \ 31 | struct tct::properties::property : \ 32 | property_impl \ 33 | { \ 34 | using property_impl::operator=; \ 35 | using property_impl::operator==; \ 36 | \ 37 | property() \ 38 | { \ 39 | this->to_string = f_to_string; \ 40 | this->from_string = f_from_string; \ 41 | } \ 42 | }; 43 | 44 | // Include built-in properties 45 | #include "property.hpp" 46 | #include "property_ex.hpp" 47 | 48 | namespace tct::properties 49 | { 50 | /** 51 | * A container for zero or more properties. 52 | */ 53 | class properties : 54 | public property_base 55 | { 56 | public: 57 | /** 58 | * Default constructor. 59 | */ 60 | properties() = default; 61 | 62 | properties(const properties& other) = delete; 63 | properties(properties&& other) = delete; 64 | 65 | /** 66 | * Destructor. 67 | */ 68 | virtual 69 | ~properties() 70 | { 71 | for (auto& [key, value] : m_properties) 72 | delete value; 73 | } 74 | 75 | properties& operator=(const properties& rhs) = delete; 76 | properties& operator=(properties&& rhs) noexcept = delete; 77 | 78 | /** 79 | * Iterators 80 | * 81 | * @{ 82 | */ 83 | auto begin() { return m_properties.begin(); } 84 | auto begin() const { return m_properties.begin(); } 85 | auto end() { return m_properties.end(); } 86 | auto end() const { return m_properties.end(); } 87 | auto cbegin() const { return m_properties.cbegin(); } 88 | auto cend() const { return m_properties.cend(); } 89 | /** 90 | * @} 91 | */ 92 | 93 | /** 94 | * Create a new property. 95 | * 96 | * @tparam T The type of property. 97 | * @param name The name of the property. 98 | * @return A reference to the newly created property. 99 | */ 100 | template 101 | property& 102 | make_property(const std::string& name) 103 | { 104 | if (m_properties.contains(name)) 105 | throw property_exists(name); 106 | 107 | auto p = new property; 108 | m_properties.emplace(name, p); 109 | return *p; 110 | } 111 | 112 | /** 113 | * Create a nested property. 114 | * 115 | * @tparam T The type of property. 116 | * @param name The name of the property. 117 | * @return A reference to the newly created property. 118 | */ 119 | template 120 | requires std::derived_from 121 | T& 122 | make_nested_property(const std::string& name) 123 | { 124 | if (m_properties.contains(name)) 125 | throw property_exists(name); 126 | 127 | auto p = new T; 128 | m_properties.emplace(name, p); 129 | return *p; 130 | } 131 | 132 | /** 133 | * Create a linked property. 134 | * 135 | * @tparam T The type of property. 136 | * @param name The name of the property. 137 | * @param ptr Pointer to the value. 138 | * @return A reference to the newly created property. 139 | */ 140 | template 141 | void 142 | make_linked_property(const std::string& name, T* ptr) 143 | { 144 | if (m_properties.contains(name)) 145 | throw property_exists(name); 146 | 147 | if (!ptr) 148 | throw std::logic_error("ptr must not be null."); 149 | 150 | auto p = new property_link; 151 | p->data = ptr; 152 | m_properties.emplace(name, p); 153 | } 154 | 155 | /** 156 | * Create a linked functions property. 157 | * 158 | * @tparam T The type of property. 159 | * @param name The name of the property. 160 | * @param setter The setter function. 161 | * @param getter The getter function. 162 | */ 163 | template 164 | void 165 | make_linked_property_functions(const std::string& name, const setter& setter, const getter& getter) 166 | { 167 | if (m_properties.contains(name)) 168 | throw property_exists(name); 169 | 170 | if (!setter) 171 | throw std::logic_error("setter must not be null."); 172 | 173 | if (!getter) 174 | throw std::logic_error("setter must not be null."); 175 | 176 | auto p = new property_link_functions(setter, getter); 177 | m_properties.emplace(name, p); 178 | } 179 | 180 | /** 181 | * Get the number of properties. 182 | * 183 | * @return The number of properties. 184 | */ 185 | [[nodiscard]] 186 | std::size_t 187 | properties_count() const noexcept 188 | { 189 | return m_properties.size(); 190 | } 191 | 192 | /** 193 | * Set the value of a specific property. 194 | * 195 | * @tparam T The property type. 196 | * @param name The name of the property. 197 | * @param t The value to be set. 198 | */ 199 | template 200 | void 201 | set_property(const std::string& name, const T& t) 202 | { 203 | if (!m_properties.contains(name)) 204 | throw property_nonexist(name); 205 | 206 | property_cast(m_properties[name]) = t; 207 | } 208 | 209 | /** 210 | * Get the value of a specific property. 211 | * 212 | * @throw @p property_nonexist if no property exists with the specified name. 213 | * 214 | * @tparam T The property type. 215 | * @param name The name of the property. 216 | * @return The value of the property. 217 | */ 218 | template 219 | [[nodiscard]] 220 | const T& 221 | get_property(const std::string& name) const 222 | { 223 | try { 224 | return property_cast(m_properties.at(name)); 225 | } 226 | catch([[maybe_unused]] const std::out_of_range& e) { 227 | throw property_nonexist(name); 228 | } 229 | } 230 | 231 | /** 232 | * Get a group of nested properties. 233 | * 234 | * @note The returned pointer is guaranteed not to be null. 235 | * 236 | * @throw @p @p property_nonexist if no properties group exists with the specified name. 237 | * 238 | * @param The name of the properties group. 239 | * @return The corresponding properties group. 240 | */ 241 | [[nodiscard]] 242 | properties* 243 | get_nested_properties(const std::string& name) 244 | { 245 | auto it = m_properties.find(name); 246 | if (it == std::cend(m_properties)) 247 | throw property_nonexist(name); 248 | 249 | return dynamic_cast(it->second); 250 | } 251 | 252 | /** 253 | * Serialize properties to string. 254 | * 255 | * @param ar The archiver to use. 256 | * @return The serialized string. 257 | */ 258 | [[nodiscard]] 259 | std::string 260 | save(const archiver& ar) const 261 | { 262 | return ar.save(*this); 263 | } 264 | 265 | /** 266 | * Serialize properties to file. 267 | * 268 | * @param ar The archiver to use. 269 | * @param path The file path. 270 | * @return @p true if successful, @p false otherwise with optional error message. 271 | */ 272 | [[nodiscard("file i/o might fail")]] 273 | std::pair 274 | save(const archiver& ar, const std::filesystem::path& path) 275 | { 276 | return ar.save(*this, path); 277 | } 278 | 279 | /** 280 | * Deserialize properties from string. 281 | * 282 | * @param ar The archiver to use. 283 | * @param str The string to deserialize. 284 | */ 285 | void 286 | load(const archiver& ar, const std::string& str) 287 | { 288 | ar.load(*this, str); 289 | } 290 | 291 | /** 292 | * Deserialize properties from file. 293 | * 294 | * @param ar The archiver to use. 295 | * @path The file path. 296 | * @return @p true on success, @p false otherwise with optional error message. 297 | */ 298 | [[nodiscard("file i/o might fail")]] 299 | std::pair 300 | load(const archiver& ar, const std::filesystem::path& path) 301 | { 302 | return ar.load(*this, path); 303 | } 304 | 305 | private: 306 | std::map m_properties; 307 | }; 308 | } 309 | -------------------------------------------------------------------------------- /cppproperties/property.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace tct::properties 10 | { 11 | /** 12 | * Callback type. 13 | */ 14 | using callback = std::function; 15 | 16 | /** 17 | * Value setter type. 18 | */ 19 | template 20 | using setter = std::function; 21 | 22 | /** 23 | * Value getter type. 24 | */ 25 | template 26 | using getter = std::function; 27 | 28 | /** 29 | * Base class for any property implementation. 30 | */ 31 | struct property_base 32 | { 33 | /** 34 | * Default constructor. 35 | */ 36 | property_base() = default; 37 | 38 | /** 39 | * Copy constructor. 40 | */ 41 | property_base(const property_base&) = default; 42 | 43 | /** 44 | * Move constructor. 45 | */ 46 | property_base(property_base&&) noexcept = default; 47 | 48 | /** 49 | * Destructor. 50 | */ 51 | virtual 52 | ~property_base() = default; 53 | 54 | /** 55 | * Copy-assignment operator. 56 | * 57 | * @param rhs The right hand side object to copy-assign from. 58 | * @return Reference to the left hand side object. 59 | */ 60 | property_base& 61 | operator=(const property_base& rhs) = default; 62 | 63 | /** 64 | * Move-assignment operator. 65 | * 66 | * @param rhs The right hand side object to move-assign from. 67 | * @return Reference to the left hand side object. 68 | */ 69 | property_base& 70 | operator=(property_base&& rhs) noexcept = default; 71 | 72 | /** 73 | * Function object to serialize property to string. 74 | */ 75 | std::function 76 | to_string; 77 | 78 | /** 79 | * Function object to deserialize property from string. 80 | */ 81 | std::function 82 | from_string; 83 | 84 | /** 85 | * Set an attribute value. 86 | * 87 | * @note If the attribute key already exists it will be updated. 88 | * 89 | * @param key The attribute key. 90 | * @param value The attribute value. 91 | */ 92 | void 93 | set_attribute(const std::string& key, const std::string& value) 94 | { 95 | m_attributes.insert_or_assign(key, value); 96 | } 97 | 98 | /** 99 | * Get an attribute value. 100 | * 101 | * @param key The attribute key. 102 | * @return The attribute value (if any exists). 103 | */ 104 | [[nodiscard]] 105 | std::optional 106 | attribute(const std::string& key) const 107 | { 108 | const auto& it = m_attributes.find(key); 109 | if (it == std::cend(m_attributes)) 110 | return std::nullopt; 111 | 112 | return it->second; 113 | } 114 | 115 | /** 116 | * Get a key-value map of all attributes. 117 | */ 118 | [[nodiscard]] 119 | std::map 120 | attributes() const 121 | { 122 | return m_attributes; 123 | } 124 | 125 | private: 126 | std::map m_attributes; 127 | }; 128 | 129 | /** 130 | * @brief Standard property implementation. 131 | * 132 | * @tparam The property type. 133 | */ 134 | template 135 | struct property_impl : 136 | property_base 137 | { 138 | /** 139 | * The property value. 140 | */ 141 | T data = { }; 142 | 143 | /** 144 | * @brief Default constructor. 145 | * 146 | * @note This is only available if @p T is default constructible. 147 | */ 148 | property_impl() 149 | requires std::is_default_constructible_v = default; 150 | 151 | /** 152 | * @brief Copy constructor. 153 | * 154 | * @note This is only available if @p T is copy constructible. 155 | * 156 | * @param other The object to copy-construct from. 157 | */ 158 | property_impl(const property_impl& other) 159 | requires std::is_copy_constructible_v : 160 | property_base(other), 161 | data(other.data) 162 | { 163 | } 164 | 165 | /** 166 | * @brief Move constructor. 167 | * 168 | * @note This is only available if @p T is move constructible. 169 | * 170 | * @param other The object to move-construct from. 171 | */ 172 | property_impl(property_impl&& other) noexcept 173 | requires std::is_move_constructible_v : 174 | property_base(std::move(other)), 175 | data(std::move(other.data)) 176 | { 177 | } 178 | 179 | /** 180 | * @brief Copy assign the value. 181 | * 182 | * @note This is only available if @p T is copy assignable. 183 | * 184 | * @param t The value to copy-assign. 185 | * @return Reference to this property. 186 | */ 187 | property_impl& 188 | operator=(const T& t) 189 | requires std::is_copy_assignable_v 190 | { 191 | this->data = t; 192 | this->notify(); 193 | return *this; 194 | } 195 | 196 | /** 197 | * @brief Move assig the value. 198 | * 199 | * @note This is only available if @p T is move assignable. 200 | * 201 | * @param t The value to move-assign. 202 | * @return Reference to this property. 203 | */ 204 | property_impl& 205 | operator=(T&& t) noexcept 206 | requires std::is_move_assignable_v 207 | { 208 | this->data = std::move(t); 209 | this->notify(); 210 | return *this; 211 | } 212 | 213 | /** 214 | * @brief Copy assign from another property. 215 | * 216 | * @note This is only available if @p T is copy assignable. 217 | * 218 | * @param rhs The right hand side property to copy-assign from. 219 | * @return Reference to the left hand side property. 220 | */ 221 | property_impl& 222 | operator=(const property_impl& rhs) 223 | requires std::is_copy_assignable_v 224 | { 225 | data = rhs.data; 226 | return *this; 227 | } 228 | 229 | /** 230 | * @brief Move assign from another property. 231 | * 232 | * @note This is only available if @p T is move assignable. 233 | * 234 | * @param rhs The right hand side property to move-assign from. 235 | * @return Reference to the left hand side property. 236 | */ 237 | property_impl& 238 | operator=(property_impl&& rhs) noexcept 239 | requires std::is_move_assignable_v 240 | { 241 | data = std::move(rhs.data); 242 | return *this; 243 | } 244 | 245 | /** 246 | * @brief Compare the value. 247 | * 248 | * @param t The value to compare with. 249 | * @return @p true if the values are equal, @p false otherwise. 250 | */ 251 | bool 252 | operator==(const T& t) const 253 | { 254 | return this->data == t; 255 | } 256 | 257 | /** 258 | * Get the value. 259 | */ 260 | explicit 261 | operator T() 262 | const noexcept 263 | { 264 | return data; 265 | } 266 | 267 | /** 268 | * @brief Register an observer. 269 | * 270 | * @details The callback @p cb will be invoked if the value changes. 271 | * 272 | * @param cb The callback to register. 273 | */ 274 | void 275 | register_observer(const callback& cb) 276 | { 277 | m_observers.push_back(cb); 278 | } 279 | 280 | protected: 281 | /** 282 | * @brief Notify observers. 283 | * 284 | * @brief Notify all registered observers by invoking their callback. 285 | */ 286 | void 287 | notify() 288 | { 289 | std::for_each(std::begin(m_observers), std::end(m_observers), [](const callback& cb){ 290 | std::invoke(cb); 291 | }); 292 | } 293 | 294 | private: 295 | std::vector m_observers; 296 | }; 297 | 298 | /** 299 | * @brief Alias for standard property implementation. 300 | */ 301 | template 302 | struct property : 303 | property_impl 304 | { 305 | }; 306 | 307 | /** 308 | * Create a property which links to an existing value. 309 | * 310 | * @tparam T The type of the underlying value. 311 | */ 312 | template 313 | struct property_link : 314 | property_base 315 | { 316 | T* data = nullptr; 317 | 318 | property_link() 319 | { 320 | property_base::to_string = std::bind(&property_link::to_string, this); 321 | property_base::from_string = std::bind(&property_link::from_string, this, std::placeholders::_1); 322 | } 323 | 324 | private: 325 | [[nodiscard]] 326 | std::string 327 | to_string() const 328 | { 329 | property p; 330 | p.data = *data; 331 | return p.to_string(); 332 | } 333 | 334 | void 335 | from_string(const std::string& str) 336 | { 337 | property p; 338 | p.from_string(str); 339 | *data = p.data; 340 | } 341 | }; 342 | 343 | template 344 | struct property_link_functions : 345 | property_base 346 | { 347 | property_link_functions(const setter& setter, const getter& getter) : 348 | m_setter(setter), 349 | m_getter(getter) 350 | { 351 | property_base::to_string = std::bind(&property_link_functions::to_string, this); 352 | property_base::from_string = std::bind(&property_link_functions::from_string, this, std::placeholders::_1); 353 | } 354 | 355 | private: 356 | setter m_setter; 357 | getter m_getter; 358 | 359 | [[nodiscard]] 360 | std::string 361 | to_string() const 362 | { 363 | property p; 364 | p.data = m_getter(); 365 | return p.to_string(); 366 | } 367 | 368 | void 369 | from_string(const std::string& str) 370 | { 371 | property p; 372 | p.from_string(str); 373 | m_setter(p.data); 374 | } 375 | }; 376 | 377 | template 378 | constexpr 379 | T& 380 | property_cast(property_base* pb) 381 | { 382 | return dynamic_cast*>(pb)->data; 383 | } 384 | 385 | template 386 | constexpr 387 | const T& 388 | property_cast(const property_base* pb) 389 | { 390 | return dynamic_cast*>(pb)->data; 391 | } 392 | 393 | template 394 | std::ostream& 395 | operator<<(std::ostream& os, const property& p) { os << p.to_string(); return os; } 396 | 397 | } 398 | -------------------------------------------------------------------------------- /cppproperties/property_ex.hpp: -------------------------------------------------------------------------------- 1 | // C++ 2 | #include "property_ex_cpp.hpp" 3 | #include "property_ex_cpp_modern.hpp" 4 | 5 | // Boost 6 | #ifdef CPPPROPERTIES_ENABLE_BOOST 7 | # include "property_ex_boost.hpp" 8 | #endif 9 | 10 | // Qt 11 | #ifdef CPPPROPERTIES_ENABLE_QT 12 | # include "property_ex_qt.hpp" 13 | #endif 14 | -------------------------------------------------------------------------------- /cppproperties/property_ex_boost.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "property.hpp" 8 | 9 | REGISTER_PROPERTY( 10 | boost::uuids::uuid, 11 | [this]{ return boost::uuids::to_string(this->data); }, 12 | [this](const std::string& str){ this->data = boost::uuids::string_generator()(str); } 13 | ) 14 | -------------------------------------------------------------------------------- /cppproperties/property_ex_cpp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "property.hpp" 6 | 7 | /** 8 | * Property for `std::basic_string`. 9 | */ 10 | template 11 | struct tct::properties::property> : 12 | property_impl> 13 | { 14 | using property_impl>::operator=; 15 | using property_impl>::operator==; 16 | 17 | property() 18 | { 19 | this->to_string = [this](){ return this->data; }; 20 | this->from_string = [this](const std::string& str){ *this = str; }; 21 | } 22 | }; 23 | 24 | REGISTER_PROPERTY( 25 | bool, 26 | [this]{ return (this->data ? "true" : "false"); }, 27 | [this](const std::string& str){ this-> data = (str == "true" || str == "True"); } 28 | ) 29 | 30 | REGISTER_PROPERTY( 31 | int, 32 | [this]{ return std::to_string(this->data); }, 33 | [this](const std::string& str){ this->data = std::stoi(str); } 34 | ) 35 | 36 | REGISTER_PROPERTY( 37 | float, 38 | [this]{ return std::to_string(this->data); }, 39 | [this](const std::string& str){ this->data = std::stof(str); } 40 | ) 41 | 42 | REGISTER_PROPERTY( 43 | double, 44 | [this]{ return std::to_string(this->data); }, 45 | [this](const std::string& str){ this->data = std::stod(str); } 46 | ) 47 | -------------------------------------------------------------------------------- /cppproperties/property_ex_cpp_modern.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "property.hpp" 6 | 7 | /** 8 | * std::filesystem::path 9 | */ 10 | REGISTER_PROPERTY 11 | ( 12 | std::filesystem::path, 13 | [this]{ return this->data.string(); }, 14 | [this](const std::string& str) { *this = str; } 15 | ) -------------------------------------------------------------------------------- /cppproperties/property_ex_qt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "property.hpp" 9 | 10 | REGISTER_PROPERTY 11 | ( 12 | QString, 13 | [this]{ return this->data.toStdString(); }, 14 | [this](const std::string& str) { *this = QString::fromStdString(str); } 15 | ) 16 | 17 | REGISTER_PROPERTY 18 | ( 19 | QPoint, 20 | [this]() 21 | { 22 | return std::to_string(data.x()) + ";" + std::to_string(data.y()); 23 | }, 24 | [this](const std::string& str) 25 | { 26 | std::string::size_type separator_pos = str.find_first_of(';'); 27 | if (separator_pos == std::string::npos) 28 | throw std::runtime_error("Could not find separation character."); 29 | 30 | // Parse X 31 | if (const auto [p, ec] = std::from_chars(str.data(), str.data()+separator_pos, data.rx()); ec != std::errc()) 32 | throw std::runtime_error("could not parse string \"" + str + "\" into QPoint."); 33 | 34 | // Parse Y 35 | if (const auto [p, ec] = std::from_chars(str.data()+separator_pos+1, str.data()+str.size(), data.ry()); ec != std::errc()) 36 | throw std::runtime_error("could not parse string \"" + str + "\" into QPoint."); 37 | } 38 | ) 39 | -------------------------------------------------------------------------------- /cppproperties/qt_widgets/boolean.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "widget_base.hpp" 6 | 7 | namespace tct::properties::qt_widgets 8 | { 9 | 10 | class boolean : 11 | public QCheckBox, 12 | public widget_base 13 | { 14 | Q_OBJECT 15 | Q_DISABLE_COPY_MOVE(boolean) 16 | 17 | public: 18 | explicit 19 | boolean(tct::properties::property& p) : 20 | widget_base(p) 21 | { 22 | setChecked(p.data); 23 | 24 | connect(this, &QCheckBox::toggled, [this](const bool checked){ 25 | m_property = checked; 26 | }); 27 | } 28 | 29 | ~boolean() override = default; 30 | }; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /cppproperties/qt_widgets/factory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "../properties.hpp" 8 | #include "boolean.hpp" 9 | #include "integer.hpp" 10 | #include "nested.hpp" 11 | 12 | namespace tct::properties::qt_widgets 13 | { 14 | 15 | class factory 16 | { 17 | private: 18 | template 19 | [[nodiscard]] 20 | static 21 | bool 22 | is_type(property_base* pb) 23 | { 24 | return dynamic_cast*>(pb) != nullptr; 25 | } 26 | 27 | template 28 | [[nodiscard]] 29 | static 30 | std::unique_ptr 31 | build_widget(property_base* pb) 32 | { 33 | // Make sure the editor can handle this type 34 | static_assert(std::is_same_v); 35 | 36 | // Convert to property* 37 | property* p = dynamic_cast*>(pb); 38 | if (!p) 39 | return nullptr; 40 | 41 | return std::make_unique(*p); 42 | } 43 | 44 | public: 45 | [[nodiscard]] 46 | static 47 | std::unique_ptr 48 | build_widget(property_base* pb) 49 | { 50 | // Sanity check 51 | if (!pb) 52 | return nullptr; 53 | 54 | // Get the appropriate widget 55 | // ToDo: Improve this. Use self-registering factory type pattern 56 | { 57 | if (is_type(pb)) 58 | return build_widget(pb); 59 | 60 | if (is_type(pb)) 61 | return build_widget(pb); 62 | } 63 | 64 | return nullptr; 65 | } 66 | 67 | [[nodiscard]] 68 | static 69 | std::unique_ptr 70 | build_form(tct::properties::properties& p) 71 | { 72 | // Layout 73 | QFormLayout* layout = new QFormLayout; 74 | for (const auto& [property_name, property_value] : p) { 75 | // Get widget 76 | std::unique_ptr w; 77 | { 78 | // Nested? 79 | if (auto n = p.get_nested_properties(property_name); n) 80 | w = std::make_unique(); 81 | 82 | // Regular property 83 | else 84 | w = build_widget(property_value); 85 | } 86 | if (!w) 87 | w = std::make_unique(); 88 | 89 | // Add to layout 90 | layout->addRow(QString::fromStdString(property_name), w.release()); 91 | } 92 | 93 | // Widget 94 | auto w = std::make_unique(); 95 | w->setLayout(layout); 96 | 97 | return w; 98 | } 99 | }; 100 | 101 | } 102 | -------------------------------------------------------------------------------- /cppproperties/qt_widgets/integer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "widget_base.hpp" 6 | 7 | namespace tct::properties::qt_widgets 8 | { 9 | 10 | class integer : 11 | public QSpinBox, 12 | public widget_base 13 | { 14 | Q_OBJECT 15 | Q_DISABLE_COPY_MOVE(integer) 16 | 17 | public: 18 | explicit 19 | integer(tct::properties::property& p) : 20 | widget_base(p) 21 | { 22 | setValue(p.data); 23 | 24 | connect(this, qOverload(&QSpinBox::valueChanged), [this](const int value){ 25 | m_property = value; 26 | }); 27 | } 28 | 29 | ~integer() override = default; 30 | }; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /cppproperties/qt_widgets/nested.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "widget_base.hpp" 6 | 7 | namespace tct::properties::qt_widgets 8 | { 9 | 10 | class nested : 11 | public QGroupBox 12 | { 13 | Q_OBJECT 14 | Q_DISABLE_COPY_MOVE(nested) 15 | 16 | public: 17 | nested() = default; 18 | ~nested() override = default; 19 | }; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /cppproperties/qt_widgets/widget_base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../properties.hpp" 4 | 5 | namespace tct::properties::qt_widgets 6 | { 7 | 8 | template 9 | struct widget_base 10 | { 11 | using type = T; 12 | 13 | public: 14 | explicit 15 | widget_base(property& property) : 16 | m_property(property) 17 | { 18 | } 19 | 20 | virtual 21 | ~widget_base() = default; 22 | 23 | protected: 24 | property& m_property; 25 | }; 26 | 27 | } -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(basic) 2 | add_subdirectory(custom_types) 3 | if (CPPPROPERTIES_ENABLE_GPDS) 4 | add_subdirectory(gpds) 5 | endif() 6 | if (CPPPROPERTIES_ENABLE_JSON) 7 | add_subdirectory(json) 8 | endif() 9 | if (CPPPROPERTIES_ENABLE_XML) 10 | add_subdirectory(xml) 11 | endif() 12 | add_subdirectory(linked_properties) 13 | add_subdirectory(linked_property_functions) 14 | add_subdirectory(nested) 15 | add_subdirectory(notification) 16 | if (CPPPROPERTIES_ENABLE_QT_WIDGETS) 17 | add_subdirectory(qt_widgets) 18 | endif() 19 | add_subdirectory(serialization) 20 | -------------------------------------------------------------------------------- /examples/basic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-basic) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | -------------------------------------------------------------------------------- /examples/basic/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | #include "cppproperties/archiver_xml.hpp" 5 | 6 | struct shape : 7 | tct::properties::properties 8 | { 9 | MAKE_PROPERTY(locked, bool); 10 | MAKE_PROPERTY(x, int); 11 | MAKE_PROPERTY(y, int); 12 | MAKE_PROPERTY(name, std::string); 13 | }; 14 | 15 | int main() 16 | { 17 | shape s; 18 | 19 | // Set some property values 20 | s.locked = false; 21 | s.x = 24; 22 | s.y = 48; 23 | s.name = "My Shape"; 24 | 25 | // Print each property manually 26 | std::cout << "locked = " << s.locked << "\n"; 27 | std::cout << "x = " << s.x << "\n"; 28 | std::cout << "y = " << s.y << "\n"; 29 | std::cout << "name = " << s.name << "\n"; 30 | std::cout << std::endl; 31 | 32 | // Print properties automatically 33 | std::cout << "Properties:\n"; 34 | std::cout << s.save(tct::properties::archiver_xml()) << std::endl; 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /examples/custom_types/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-customtypes) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | -------------------------------------------------------------------------------- /examples/custom_types/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | 5 | /** 6 | * Define a color class. 7 | */ 8 | struct color 9 | { 10 | std::string name; 11 | uint8_t r, g, b; 12 | 13 | [[nodiscard]] std::string to_string() const 14 | { 15 | // ... 16 | return { }; 17 | } 18 | 19 | void from_string(const std::string& str) 20 | { 21 | // ... 22 | } 23 | }; 24 | 25 | /** 26 | * Register a property of type color. 27 | */ 28 | REGISTER_PROPERTY( 29 | color, 30 | [this](){ return data.to_string(); }, 31 | [this](const std::string& str){ this->data.from_string(str); } 32 | ) 33 | 34 | /** 35 | * Our shape class that uses properties. 36 | */ 37 | struct shape : 38 | tct::properties::properties 39 | { 40 | MAKE_PROPERTY(x, int); 41 | MAKE_PROPERTY(y, int); 42 | MAKE_PROPERTY(fg_color, color); 43 | MAKE_PROPERTY(bg_color, color); 44 | }; 45 | 46 | int main() 47 | { 48 | shape s; 49 | s.x = 13; 50 | s.y = 37; 51 | s.fg_color = { "blue", 0, 0, 255 }; 52 | s.bg_color = { "green", 0, 255, 0 }; 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /examples/gpds/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(../../cppproperties/external.cmake) 2 | 3 | set(TARGET example-gpds) 4 | 5 | add_executable(${TARGET}) 6 | 7 | target_sources( 8 | ${TARGET} 9 | PRIVATE 10 | main.cpp 11 | ) 12 | 13 | target_include_directories( 14 | ${TARGET} 15 | PRIVATE 16 | ${CMAKE_CURRENT_LIST_DIR}/../.. 17 | ) 18 | 19 | target_link_libraries( 20 | ${TARGET} 21 | PRIVATE 22 | cppproperties 23 | ) 24 | -------------------------------------------------------------------------------- /examples/gpds/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "cppproperties/properties.hpp" 7 | #include "cppproperties/archiver_gpds.hpp" 8 | 9 | struct shape : 10 | tct::properties::properties 11 | { 12 | MAKE_PROPERTY(locked, bool); 13 | MAKE_PROPERTY(x, int); 14 | MAKE_PROPERTY(y, int); 15 | MAKE_PROPERTY(name, std::string); 16 | }; 17 | 18 | int main() 19 | { 20 | shape s; 21 | s.locked = false; 22 | s.x = 24; 23 | s.y = 48; 24 | s.name = "My Shape"; 25 | s.set_attribute("foobar", "zbar"); 26 | s.name.set_attribute("name-attr", "test1234"); 27 | 28 | tct::properties::archiver_gpds ar; 29 | const gpds::container& c = ar.save(s); 30 | 31 | std::stringstream ss; 32 | gpds::archiver_xml gpds_ar; 33 | gpds_ar.save(ss, c, "properties"); 34 | 35 | gpds::container gpds_c; 36 | gpds_ar.load(ss, gpds_c, "properties"); 37 | shape s2; 38 | ar.load(s2, gpds_c); 39 | 40 | std::cout << s2.locked << "\n"; 41 | std::cout << s2.x << "\n"; 42 | std::cout << s2.y << "\n"; 43 | std::cout << s2.name << "\n"; 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /examples/json/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(../../cppproperties/external.cmake) 2 | 3 | set(TARGET example-json) 4 | 5 | add_executable(${TARGET}) 6 | 7 | target_sources( 8 | ${TARGET} 9 | PRIVATE 10 | main.cpp 11 | ) 12 | 13 | target_include_directories( 14 | ${TARGET} 15 | PRIVATE 16 | ${CMAKE_CURRENT_LIST_DIR}/../.. 17 | ) 18 | 19 | target_link_libraries( 20 | ${TARGET} 21 | PRIVATE 22 | cppproperties 23 | ) 24 | -------------------------------------------------------------------------------- /examples/json/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | #include "cppproperties/archiver_json.hpp" 5 | 6 | struct shape : 7 | tct::properties::properties 8 | { 9 | MAKE_PROPERTY(locked, bool); 10 | MAKE_PROPERTY(x, int); 11 | MAKE_PROPERTY(y, int); 12 | MAKE_PROPERTY(name, std::string); 13 | }; 14 | 15 | int main() 16 | { 17 | shape s; 18 | s.locked = false; 19 | s.x = 24; 20 | s.y = 48; 21 | s.name = "My Shape"; 22 | s.set_attribute("foobar", "zbar"); 23 | s.name.set_attribute("name-attr", "test1234"); 24 | 25 | tct::properties::archiver_json ar; 26 | std::string json_str = ar.save(s); 27 | 28 | std::cout << json_str << "\n"; 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /examples/linked_properties/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-linkedproperties) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | -------------------------------------------------------------------------------- /examples/linked_properties/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | #include "cppproperties/archiver_xml.hpp" 5 | 6 | struct base 7 | { 8 | int x = 0; 9 | int y = 0; 10 | }; 11 | 12 | struct derived : 13 | base, 14 | tct::properties::properties 15 | { 16 | derived() 17 | { 18 | LINK_PROPERTY(x, &x); 19 | LINK_PROPERTY(y, &y); 20 | } 21 | }; 22 | 23 | int main() 24 | { 25 | // Create an XML archiver 26 | tct::properties::archiver_xml ar; 27 | 28 | derived d; 29 | d.x = 42; 30 | d.y = 1337; 31 | 32 | std::cout << d.save(ar) << std::endl; 33 | 34 | derived d2; 35 | d2.load(ar, d.save(ar)); 36 | std::cout << d2.save(ar) << std::endl; 37 | } 38 | -------------------------------------------------------------------------------- /examples/linked_property_functions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-linkedpropertyfunctions) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | -------------------------------------------------------------------------------- /examples/linked_property_functions/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | #include "cppproperties/archiver_xml.hpp" 5 | 6 | struct base 7 | { 8 | public: 9 | void set_x(const int x) { m_x = x; } 10 | [[nodiscard]] int x() const { return m_x; } 11 | 12 | private: 13 | int m_x = 0; 14 | }; 15 | 16 | struct derived : 17 | base, 18 | tct::properties::properties 19 | { 20 | derived() 21 | { 22 | LINK_PROPERTY_FUNCTIONS(x, int, base::set_x, base::x) 23 | } 24 | }; 25 | 26 | int main() 27 | { 28 | // Create an XML archiver 29 | tct::properties::archiver_xml ar; 30 | 31 | derived d1; 32 | d1.set_x(42); 33 | 34 | derived d2; 35 | d2.load(ar, d1.save(ar)); 36 | 37 | std::cout << d1.save(ar) << std::endl; 38 | std::cout << d2.save(ar) << std::endl; 39 | } 40 | -------------------------------------------------------------------------------- /examples/nested/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-nested) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | -------------------------------------------------------------------------------- /examples/nested/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | #include "cppproperties/archiver_xml.hpp" 5 | 6 | struct color : 7 | tct::properties::properties 8 | { 9 | MAKE_PROPERTY(red, int); 10 | MAKE_PROPERTY(green, int); 11 | MAKE_PROPERTY(blue, int); 12 | 13 | void set(const std::string& rgb_string) 14 | { 15 | if (rgb_string.size() != 6) 16 | throw std::runtime_error(R"(RGB string is malformed. Needs to be of format "rrggbb" in hex format.)"); 17 | 18 | int hex = std::stoi(rgb_string, nullptr, 16); 19 | this->red = (hex / 0x10000); 20 | this->green = (hex / 0x100) % 0x100; 21 | this->blue = (hex % 0x100); 22 | } 23 | }; 24 | 25 | struct shape : 26 | tct::properties::properties 27 | { 28 | MAKE_PROPERTY(x, int); 29 | MAKE_PROPERTY(y, int); 30 | MAKE_NESTED_PROPERTY(fg_color, color); 31 | MAKE_NESTED_PROPERTY(bg_color, color); 32 | }; 33 | 34 | int main() 35 | { 36 | tct::properties::archiver_xml ar; 37 | 38 | shape s1; 39 | s1.x = 13; 40 | s1.y = 37; 41 | s1.fg_color.set("a1b2c3"); 42 | s1.fg_color.set("00a4f3"); 43 | 44 | // To XML 45 | const std::string xml_str = s1.save(ar); 46 | 47 | // From XML 48 | shape s2; 49 | s2.load(ar, xml_str); 50 | 51 | // Print 52 | if (xml_str != s2.save(ar)) { 53 | std::cout << "Error: XML strings do not match.\n"; 54 | } else 55 | std::cout << s2.save(ar) << std::endl; 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /examples/notification/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-notification) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | -------------------------------------------------------------------------------- /examples/notification/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | 5 | struct shape : 6 | tct::properties::properties 7 | { 8 | MAKE_PROPERTY(x, int); 9 | MAKE_PROPERTY(y, int); 10 | 11 | shape() 12 | { 13 | // Register observers 14 | x.register_observer([](){ std::cout << "x changed!\n"; }); 15 | y.register_observer([](){ std::cout << "y changed!\n"; }); 16 | } 17 | }; 18 | 19 | void shape_position_changed(const shape& s) 20 | { 21 | std::cout << "Shape position changed to: " << s.x << ", " << s.y << std::endl; 22 | } 23 | 24 | int main() 25 | { 26 | shape s; 27 | 28 | // Register another observer 29 | s.x.register_observer([&s](){ shape_position_changed(s); }); 30 | s.y.register_observer([&s](){ shape_position_changed(s); }); 31 | 32 | // Set some property values 33 | s.x = 24; 34 | s.y = 48; 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /examples/qt_widgets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-qt_widgets) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | 23 | # Note: This is also set in the cppproperties library target. However, that does not seem to pass to the 24 | # consuming target. Figure out why not. 25 | set_target_properties( 26 | ${TARGET} 27 | PROPERTIES 28 | AUTOMOC ON 29 | ) 30 | -------------------------------------------------------------------------------- /examples/qt_widgets/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "cppproperties/properties.hpp" 7 | #include "cppproperties/qt_widgets/factory.hpp" 8 | 9 | struct shape : 10 | tct::properties::properties 11 | { 12 | MAKE_PROPERTY(enabled, bool); 13 | MAKE_PROPERTY(x, int); 14 | MAKE_PROPERTY(y, int); 15 | 16 | shape() 17 | { 18 | enabled.register_observer([](){ std::cout << "enabled changed!\n"; }); 19 | x.register_observer([](){ std::cout << "x changed!\n"; }); 20 | y.register_observer([](){ std::cout << "x changed!\n"; }); 21 | } 22 | }; 23 | 24 | struct circle : 25 | shape 26 | { 27 | MAKE_PROPERTY(radius, int); 28 | 29 | circle() 30 | { 31 | radius.register_observer([](){ std::cout << "radius channged!\n"; }); 32 | } 33 | }; 34 | 35 | int main(int argc, char* argv[]) 36 | { 37 | QApplication a(argc, argv); 38 | 39 | circle s; 40 | 41 | // Set some property values 42 | s.x = 24; 43 | s.y = 48; 44 | s.radius = 14; 45 | 46 | // Create widget 47 | auto w = tct::properties::qt_widgets::factory::build_form(s); 48 | if (w) 49 | w->show(); 50 | 51 | return a.exec(); 52 | } 53 | -------------------------------------------------------------------------------- /examples/serialization/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET example-serialization) 2 | 3 | add_executable(${TARGET}) 4 | 5 | target_sources( 6 | ${TARGET} 7 | PRIVATE 8 | main.cpp 9 | ) 10 | 11 | target_include_directories( 12 | ${TARGET} 13 | PRIVATE 14 | ${CMAKE_CURRENT_LIST_DIR}/../.. 15 | ) 16 | 17 | target_link_libraries( 18 | ${TARGET} 19 | PRIVATE 20 | cppproperties 21 | ) 22 | -------------------------------------------------------------------------------- /examples/serialization/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | #include "cppproperties/archiver_xml.hpp" 5 | 6 | struct shape : 7 | tct::properties::properties 8 | { 9 | MAKE_PROPERTY(x, int); 10 | MAKE_PROPERTY(y, int); 11 | MAKE_PROPERTY(name, std::string); 12 | }; 13 | 14 | int main() 15 | { 16 | // Create an XML archiver 17 | tct::properties::archiver_xml ar; 18 | 19 | // Create object 20 | shape s1; 21 | s1.set_attribute("something", "something else"); 22 | s1.x = 24; 23 | s1.x.set_attribute("units", "px"); 24 | s1.y = 48; 25 | s1.y.set_attribute("units", "px"); 26 | s1.name = "My Shape"; 27 | 28 | // Serialize to XML file 29 | { 30 | const auto& [success, message] = s1.save(ar, "shape.xml"); 31 | if (not success) { 32 | std::cout << "Could not save to file: " << message << std::endl; 33 | return EXIT_FAILURE; 34 | } 35 | } 36 | 37 | // Create another object 38 | shape s2; 39 | 40 | // Deserialize from XML file 41 | { 42 | const auto& [success, message] = s2.load(ar, std::filesystem::path{"shape.xml"}); 43 | if (not success) { 44 | std::cout << "Could not save to file: " << message << std::endl; 45 | return EXIT_FAILURE; 46 | } 47 | } 48 | 49 | // Print both s1 and s2 as XML strings 50 | std::cout << s1.save(ar) << "\n"; 51 | std::cout << s2.save(ar) << "\n"; 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /examples/xml/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(../../cppproperties/external.cmake) 2 | 3 | set(TARGET example-xml) 4 | 5 | add_executable(${TARGET}) 6 | 7 | target_sources( 8 | ${TARGET} 9 | PRIVATE 10 | main.cpp 11 | ) 12 | 13 | target_include_directories( 14 | ${TARGET} 15 | PRIVATE 16 | ${CMAKE_CURRENT_LIST_DIR}/../.. 17 | ) 18 | 19 | target_link_libraries( 20 | ${TARGET} 21 | PRIVATE 22 | cppproperties 23 | ) 24 | -------------------------------------------------------------------------------- /examples/xml/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cppproperties/properties.hpp" 4 | #include "cppproperties/archiver_xml.hpp" 5 | 6 | struct shape : 7 | tct::properties::properties 8 | { 9 | MAKE_PROPERTY(locked, bool); 10 | MAKE_PROPERTY(x, int); 11 | MAKE_PROPERTY(y, int); 12 | MAKE_PROPERTY(name, std::string); 13 | }; 14 | 15 | int main() 16 | { 17 | shape s; 18 | s.locked = false; 19 | s.x = 24; 20 | s.y = 48; 21 | s.name = "My Shape"; 22 | s.set_attribute("foobar", "zbar"); 23 | s.name.set_attribute("name-attr", "test1234"); 24 | 25 | tct::properties::archiver_xml ar; 26 | std::string xml_str = ar.save(s); 27 | 28 | std::cout << xml_str << "\n"; 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://ci.simulton.com/buildStatus/icon?job=cpp-properties)](https://ci.simulton.com/job/cpp-properties/) 2 | 3 | This is just a summarizing readme providing the most vital information. The official documentation can be found at https://cppproperties.insane.engineer. 4 | 5 | # Introduction 6 | This is a C++20 library providing a property system to client classes. 7 | 8 | # Features 9 | The library is built with the following aspects in mind: 10 | - Modern C++ 11 | - Easy to use 12 | - Providing "raw access" to the properties just as if they were regular class members. 13 | - Easy registration of custom property types. 14 | - Easy integration of optional (de)serialization (XML & JSON already optionally built-in). 15 | - Observer interface for property change notifications. 16 | - Support for linked properties (properties in a base class not implementing this library). 17 | - GUI generator (Qt widgets) 18 | 19 | # Notes 20 | A couple of things to be aware of when using this library: 21 | - Requires a C++20 capable compiler 22 | - Properties are stored on the heap 23 | - The memory layout of `struct { MAKE_PROPERTY(a, int) };` is not the same as `struct { int a; };` 24 | - Property change notification observer callbacks are invoked by which ever thread modified the property value. 25 | 26 | # License 27 | This library is MIT licensed. 28 | 29 | - If JSON (de)serialization is enabled, [nlohmann::json](https://github.com/nlohmann/json) is used for JSON serialization. The json library itself is MIT licensed. 30 | - If XML (de)serialization is enabled, [tinyxml2](https://github.com/leethomason/tinyxml2) is used for XML serialization. The tinyxml2 library itself is zlib licensed. 31 | 32 | # Support types 33 | Any type can be registered as a property type using the `REGISTER_PROPERTY` macro. 34 | For convenience, a set of built-in types are already registered: 35 | - `bool` 36 | - `int` 37 | - `float` 38 | - `double` 39 | - `std::basic_string` (eg. `std::string`, `std::wstring`, ...) 40 | - `std::filesystem::path` 41 | 42 | If the cmake option `CPPPROPERTIES_ENABLE_BOOST` is set to `ON`, the following types are also built-in: 43 | - `boost:uuids::uuid` 44 | 45 | If the cmake option `CPPPROPERTIES_ENABLE_QT` is set to `ON`, the following types are also built-in: 46 | - `QString` 47 | - `QPoint` 48 | 49 | # Examples 50 | Start by reading the `Usage` section below. More examples can be found in the [examples](examples) directory. 51 | 52 | # Usage 53 | Basic usage only requires inheriting from `tct::properties::properties` and adding properties using `MAKE_PROPERTY()`: 54 | ```cpp 55 | struct shape : 56 | tct::properties::properties 57 | { 58 | MAKE_PROPERTY(x, float); 59 | MAKE_PROPERTY(y, float); 60 | }; 61 | ``` 62 | The defined properties may now be used as if they were just class members of type `float`: 63 | ```cpp 64 | int main(void) 65 | { 66 | shape s; 67 | s.x = 24.48f; 68 | s.y = -13.29f; 69 | 70 | // Print them 71 | std::cout << "s.x = " << s.x << std::endl; 72 | std::cout << "s.y = " << s.y << std::endl; 73 | } 74 | ``` 75 | 76 | ## Custom types 77 | Custom types may be used after registering them with `REGISTER_PROPERTY()`: 78 | ```cpp 79 | /** 80 | * Define a custom type 'color'. 81 | */ 82 | struct color 83 | { 84 | std::string name; 85 | uint8_t r, g, b; 86 | 87 | [[nodiscard]] std::string to_string() const 88 | { 89 | // ... 90 | return { }; 91 | } 92 | 93 | void from_string(const std::string& str) 94 | { 95 | // ... 96 | } 97 | }; 98 | 99 | /** 100 | * Register the property 101 | */ 102 | REGISTER_PROPERTY( 103 | color, 104 | [this](){ return data.to_string(); }, 105 | [this](const std::string& str){ this->data.from_string(str); } 106 | ) 107 | 108 | /** 109 | * Client class using properties. 110 | */ 111 | struct shape : 112 | tct::properties::properties 113 | { 114 | MAKE_PROPERTY(x, float); 115 | MAKE_PROPERTY(y, float); 116 | MAKE_PROPERTY(stroke_color color); 117 | MAKE_PROPERTY(fill_color color); 118 | }; 119 | ``` 120 | 121 | ## Notifications 122 | Properties allow registering observers to notify them upon changes of the property value. 123 | ```cpp 124 | struct shape : 125 | tct::properties::properties 126 | { 127 | MAKE_PROPERTY(x, float); 128 | MAKE_PROPERTY(y, float); 129 | 130 | shape() 131 | { 132 | x.register_observer([](){ std::cout << "x property changed!\n"; }); 133 | y.register_observer([](){ std::cout << "y property changed!\n"; }); 134 | } 135 | 136 | }; 137 | 138 | int main() 139 | { 140 | shape s; 141 | s.x = 42; // Prints "x property changed!"; 142 | s.y = 73; // Prints "y property changed!"; 143 | 144 | return 0; 145 | } 146 | ``` 147 | 148 | ## Serialization 149 | The library comes with built-in support for (de)serialization. Classes can be easily (de)serialization to/from XML: 150 | ```cpp 151 | struct shape : 152 | tct::properties::properties 153 | { 154 | MAKE_PROPERTY(x, float); 155 | MAKE_PROPERTY(y, float); 156 | } 157 | 158 | int main(void) 159 | { 160 | // Create a shape 161 | shape s; 162 | s.x = 13; 163 | s.y = 37; 164 | 165 | // Serialize to std::string using XML format 166 | const std::string& xml_string = s.to_xml(); 167 | std::cout << xml_string << std::endl; 168 | 169 | // Serialize to XML file 170 | s.to_xml_file("/path/to/shape.xml"); 171 | 172 | // Deserialize from std::string 173 | shape s2; 174 | s2.from_xml(xml_string); 175 | 176 | // Deserialize from XML file 177 | shape s3; 178 | s3.from_xml_file("/path/to/shape.xml"); 179 | 180 | return 0; 181 | } 182 | ``` 183 | 184 | ## Linked properties 185 | One is likely to encounter a scenario where a client class `derived` inherits from `tct::properties::properties` but also from another, existing base class `base`. 186 | In this case serializing an instance of `derived` will only contain the properties created with `MAKE_PROPERTY`. However, one might like (or need) to also include members of the `base` class although these members are not registered as properties in the `base` class. 187 | 188 | An example: 189 | ```cpp 190 | struct base 191 | { 192 | int x; 193 | int y; 194 | }; 195 | 196 | struct derived : 197 | public base, 198 | public tct::properties::properties 199 | { 200 | MAKE_PROPERTY(name, std::string); 201 | }; 202 | ``` 203 | Serializing instances of type `derived` will contain the `name` properties but not other vital information such as X & Y coordinates which are public members of `base`. In this cae, `LINK_PROPERTY()` may be used to include them in (de)serialization too: 204 | ```cpp 205 | struct base : 206 | { 207 | int x; 208 | int y; 209 | }; 210 | 211 | struct derived : 212 | public base, 213 | public tct::properties::properties 214 | { 215 | MAKE_PROPERTY(name, std::string); 216 | LINK_PROPERTY(x, &x); 217 | LINK_PROPERTY(y, &y); 218 | }; 219 | ``` 220 | 221 | ## Linked functions (getter/setter) 222 | This is similar to `Linked properties` but instead of directly accessing a base class member we use the corresponding getter & setters. This way, members from a base class only accessible via getters & setters can be included in (de)serialization. 223 | 224 | An example: 225 | ```cpp 226 | struct base 227 | { 228 | public: 229 | void set_x(const int x) { m_x = x; } 230 | [[nodiscard]] int x() const { return m_x; } 231 | 232 | private: 233 | int m_x = 0; 234 | }; 235 | 236 | struct derived : 237 | base, 238 | tct::properties::properties 239 | { 240 | derived() 241 | { 242 | LINK_PROPERTY_FUNCTIONS(x, int, base::set_x, base::x) 243 | } 244 | }; 245 | ``` 246 | 247 | ## Qt Widgets 248 | If `CPPPROPERTIES_ENABLE_QT_WIDGETS` is set to `ON`, Qt based widgets can be generated automatically for a property or a property group: 249 | ```cpp 250 | #include 251 | 252 | #include 253 | #include 254 | 255 | #include "cppproperties/properties.hpp" 256 | #include "cppproperties/qt_widgets/factory.hpp" 257 | 258 | struct shape : 259 | tct::properties::properties 260 | { 261 | MAKE_PROPERTY(enabled, bool); 262 | MAKE_PROPERTY(x, int); 263 | MAKE_PROPERTY(y, int); 264 | 265 | shape() 266 | { 267 | enabled.register_observer([](){ std::cout << "enabled changed!\n"; }); 268 | x.register_observer([](){ std::cout << "x changed!\n"; }); 269 | y.register_observer([](){ std::cout << "x changed!\n"; }); 270 | } 271 | }; 272 | 273 | struct circle : 274 | shape 275 | { 276 | MAKE_PROPERTY(radius, int); 277 | 278 | circle() 279 | { 280 | radius.register_observer([](){ std::cout << "radius channged!\n"; }); 281 | } 282 | }; 283 | 284 | int main(int argc, char* argv[]) 285 | { 286 | QApplication a(argc, argv); 287 | 288 | circle s; 289 | 290 | // Set some property values 291 | s.x = 24; 292 | s.y = 48; 293 | s.radius = 14; 294 | 295 | // Create widget 296 | auto w = tct::properties::qt_widgets::factory::build_form(s); 297 | if (w) 298 | w->show(); 299 | 300 | return a.exec(); 301 | } 302 | ``` 303 | 304 | # Testing 305 | This library provides a [doctest](https://github.com/onqtam/doctest) based test suite under `/test`. The corresponding cmake target is `tests`. 306 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET tests) 2 | 3 | add_executable(${TARGET}) 4 | 5 | add_subdirectory(test_suites) 6 | 7 | target_include_directories( 8 | ${TARGET} 9 | PRIVATE 10 | ${CMAKE_CURRENT_LIST_DIR}/.. 11 | 3rdparty/doctest 12 | ) 13 | 14 | target_sources( 15 | ${TARGET} 16 | PRIVATE 17 | test_main.cpp 18 | test.hpp 19 | ) 20 | 21 | target_link_libraries( 22 | ${TARGET} 23 | cppproperties 24 | ) 25 | 26 | 27 | ### 28 | # CTest 29 | ### 30 | enable_testing() 31 | 32 | add_test( 33 | NAME doctest 34 | COMMAND ${TARGET} 35 | ) 36 | -------------------------------------------------------------------------------- /test/test.hpp: -------------------------------------------------------------------------------- 1 | #include "cppproperties/properties.hpp" 2 | #include "doctest.hpp" 3 | 4 | struct test_shape : 5 | tct::properties::properties 6 | { 7 | MAKE_PROPERTY(name, std::string); 8 | MAKE_PROPERTY(x, int); 9 | MAKE_PROPERTY(y, int); 10 | }; 11 | 12 | struct non_copyable 13 | { 14 | non_copyable() = default; 15 | non_copyable(const non_copyable& other) = delete; 16 | non_copyable(non_copyable&& other) noexcept = delete; 17 | 18 | non_copyable& operator=(const non_copyable& rhs) = delete; 19 | non_copyable& operator=(non_copyable&& rhs) noexcept = delete; 20 | }; 21 | 22 | struct non_movable 23 | { 24 | non_movable() = default; 25 | non_movable(const non_movable& other) = delete; 26 | non_movable(non_movable&& other) noexcept = delete; 27 | 28 | non_movable& operator=(const non_movable& rhs) = delete; 29 | non_movable& operator=(non_movable&& rhs) noexcept = delete; 30 | }; 31 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include "doctest.hpp" 3 | 4 | -------------------------------------------------------------------------------- /test/test_suites/01_builtin_types.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | TEST_CASE("built-in types") 4 | { 5 | struct : 6 | tct::properties::properties 7 | { 8 | MAKE_PROPERTY(b, bool); 9 | MAKE_PROPERTY(i, int); 10 | MAKE_PROPERTY(f, float); 11 | MAKE_PROPERTY(d, double); 12 | } s; 13 | 14 | SUBCASE("get") 15 | { 16 | constexpr bool b_test = true; 17 | constexpr int i_test = 5; 18 | constexpr float f_test = 13.37f; 19 | constexpr double d_test = 17e-43; 20 | 21 | s.b = b_test; 22 | s.i = i_test; 23 | s.f = f_test; 24 | s.d = d_test; 25 | 26 | SUBCASE("raw") { 27 | REQUIRE_EQ(s.b, b_test); 28 | REQUIRE_EQ(s.i, i_test); 29 | REQUIRE_EQ(s.f, f_test); 30 | REQUIRE_EQ(s.d, d_test); 31 | } 32 | 33 | SUBCASE("getter") { 34 | REQUIRE_EQ(s.get_property("b"), b_test); 35 | REQUIRE_EQ(s.get_property("i"), i_test); 36 | REQUIRE_EQ(s.get_property("f"), f_test); 37 | REQUIRE_EQ(s.get_property("d"), d_test); 38 | } 39 | } 40 | 41 | SUBCASE("set") 42 | { 43 | constexpr bool b_test = false; 44 | constexpr int i_test = -15; 45 | constexpr float f_test = -1e-41f; 46 | constexpr double d_test = 17.043; 47 | 48 | SUBCASE("raw") { 49 | s.b = b_test; 50 | s.i = i_test; 51 | s.f = f_test; 52 | s.d = d_test; 53 | 54 | REQUIRE_EQ(s.b, b_test); 55 | REQUIRE_EQ(s.i, i_test); 56 | REQUIRE_EQ(s.f, f_test); 57 | REQUIRE_EQ(s.d, d_test); 58 | } 59 | 60 | SUBCASE("setter") { 61 | REQUIRE_NOTHROW(s.set_property("b", b_test)); 62 | REQUIRE_NOTHROW(s.set_property("i", i_test)); 63 | REQUIRE_NOTHROW(s.set_property("f", f_test)); 64 | REQUIRE_NOTHROW(s.set_property("d", d_test)); 65 | 66 | REQUIRE_EQ(s.get_property("b"), b_test); 67 | REQUIRE_EQ(s.get_property("i"), i_test); 68 | REQUIRE_EQ(s.get_property("f"), f_test); 69 | REQUIRE_EQ(s.get_property("d"), d_test); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/test_suites/02_builtin_types_cpp.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | TEST_CASE("built-in types") 4 | { 5 | struct : 6 | tct::properties::properties 7 | { 8 | MAKE_PROPERTY(str_01, std::string); 9 | } s; 10 | 11 | SUBCASE("get") 12 | { 13 | constexpr const char* str_01 = "Hello CppProperties!"; 14 | 15 | s.str_01 = str_01; 16 | 17 | SUBCASE("raw") { 18 | REQUIRE_EQ(s.str_01, std::string{str_01}); 19 | } 20 | 21 | SUBCASE("getter") { 22 | REQUIRE_EQ(s.get_property("str_01"), std::string(str_01)); 23 | } 24 | } 25 | 26 | SUBCASE("set") 27 | { 28 | SUBCASE("raw") { 29 | s.str_01 = "foo"; 30 | REQUIRE_EQ(s.str_01, std::string("foo")); 31 | } 32 | 33 | SUBCASE("setter") { 34 | REQUIRE_NOTHROW(s.set_property("str_01", std::string("bar"))); 35 | REQUIRE_EQ(s.str_01, std::string("bar")); 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /test/test_suites/03_nested.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | #include "cppproperties/archiver_xml.hpp" 4 | 5 | struct color : 6 | tct::properties::properties 7 | { 8 | MAKE_PROPERTY(red, int); 9 | MAKE_PROPERTY(green, int); 10 | MAKE_PROPERTY(blue, int); 11 | 12 | void set(const std::string& rgb_string) 13 | { 14 | if (rgb_string.size() != 6) 15 | throw std::runtime_error(R"(RGB string is malformed. Needs to be of format "rrggbb" in hex format.)"); 16 | 17 | int hex = std::stoi(rgb_string, nullptr, 16); 18 | this->red = (hex / 0x10000); 19 | this->green = (hex / 0x100) % 0x100; 20 | this->blue = (hex % 0x100); 21 | } 22 | }; 23 | 24 | struct shape : 25 | tct::properties::properties 26 | { 27 | MAKE_PROPERTY(x, int); 28 | MAKE_PROPERTY(y, int); 29 | MAKE_NESTED_PROPERTY(fg_color, color); 30 | MAKE_NESTED_PROPERTY(bg_color, color); 31 | }; 32 | 33 | TEST_CASE("nested") 34 | { 35 | // Helper function to remove all spaces from a string 36 | auto remove_spaces = [](std::string& str) 37 | { 38 | str.erase(remove_if(std::begin(str), std::end(str), isspace), str.end()); 39 | }; 40 | 41 | SUBCASE("to xml string") 42 | { 43 | std::string str_known_good = 44 | " \n" 45 | " \n" 46 | " 0\n" 47 | " 0\n" 48 | " 0\n" 49 | " \n" 50 | " \n" 51 | " 0\n" 52 | " 0\n" 53 | " 0\n" 54 | " \n" 55 | " 0\n" 56 | " 0\n" 57 | "\n"; 58 | 59 | shape s; 60 | std::string str = s.save(tct::properties::archiver_xml()); 61 | 62 | // Remove spaces for easier comparison 63 | remove_spaces(str_known_good); 64 | remove_spaces(str); 65 | 66 | REQUIRE_EQ(str, str_known_good); 67 | } 68 | 69 | SUBCASE("copy from xml string") 70 | { 71 | shape s1; 72 | s1.x = 13; 73 | s1.y = 37; 74 | s1.fg_color.set("012345"); 75 | s1.bg_color.set("6789ab"); 76 | 77 | // Create an XML archiver 78 | tct::properties::archiver_xml ar; 79 | 80 | shape s2; 81 | s2.load(ar, s1.save(ar)); 82 | 83 | REQUIRE_EQ(s1.save(ar), s2.save(ar)); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /test/test_suites/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources( 2 | ${TARGET} 3 | PRIVATE 4 | 01_builtin_types.cpp 5 | 02_builtin_types_cpp.cpp 6 | attributes.cpp 7 | properties.cpp 8 | property.cpp 9 | isolation.cpp 10 | iterable.cpp 11 | 12 | $<$:${CMAKE_CURRENT_LIST_DIR}/boost.cpp> 13 | $<$:${CMAKE_CURRENT_LIST_DIR}/qt.cpp> 14 | 15 | $<$:${CMAKE_CURRENT_LIST_DIR}/serialization_json.cpp> 16 | 17 | $<$:${CMAKE_CURRENT_LIST_DIR}/linked_properties.cpp> 18 | $<$:${CMAKE_CURRENT_LIST_DIR}/03_nested.cpp> 19 | ) 20 | -------------------------------------------------------------------------------- /test/test_suites/attributes.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | TEST_SUITE("attributes") 4 | { 5 | TEST_CASE("Overwrite") 6 | { 7 | test_shape s; 8 | 9 | // Set once 10 | REQUIRE_NOTHROW(s.set_attribute("units", "px")); 11 | REQUIRE_EQ(s.attribute("units"), "px"); 12 | 13 | // Overwrite with same value 14 | REQUIRE_NOTHROW(s.set_attribute("units", "px")); 15 | REQUIRE_EQ(s.attribute("units"), "px"); 16 | 17 | // Overwrite with different value 18 | REQUIRE_NOTHROW(s.set_attribute("units", "mm")); 19 | REQUIRE_EQ(s.attribute("units"), "mm"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/test_suites/boost.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | #include 4 | 5 | TEST_SUITE("boost") 6 | { 7 | struct foo : 8 | tct::properties::properties 9 | { 10 | MAKE_PROPERTY(id, boost::uuids::uuid); 11 | }; 12 | 13 | TEST_CASE("boost::uuids::uuid") 14 | { 15 | SUBCASE("raw") 16 | { 17 | foo f; 18 | f.id = boost::uuids::string_generator()("0ae3f611-7541-4a1a-a6a3-b0ecd2e4ab67"); 19 | REQUIRE_EQ(f.id, boost::uuids::string_generator()("0ae3f611-7541-4a1a-a6a3-b0ecd2e4ab67")); 20 | 21 | const std::string& id_str = boost::uuids::to_string(f.get_property("id")); 22 | REQUIRE_EQ(id_str, "0ae3f611-7541-4a1a-a6a3-b0ecd2e4ab67"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/test_suites/isolation.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | TEST_SUITE("isolation") 4 | { 5 | // Ensure that properties are not accidentally shared between objects. 6 | TEST_CASE("") 7 | { 8 | test_shape s1; 9 | test_shape s2; 10 | 11 | s1.x = 42; 12 | s2.x = -18; 13 | CHECK_EQ(s1.x, 42); 14 | CHECK_EQ(s2.x, -18); 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /test/test_suites/iterable.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | TEST_SUITE("iterable") 4 | { 5 | TEST_CASE("forward (const)") 6 | { 7 | test_shape dut; 8 | 9 | auto it = dut.cbegin(); 10 | REQUIRE_NE(it, dut.cend()); 11 | 12 | REQUIRE_EQ(it->first, "name"); 13 | it++; 14 | 15 | REQUIRE_EQ(it->first, "x"); 16 | it++; 17 | 18 | REQUIRE_EQ(it->first, "y"); 19 | it++; 20 | 21 | REQUIRE_EQ(it, dut.cend()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/test_suites/linked_properties.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | #include "cppproperties/archiver_xml.hpp" 3 | 4 | struct base 5 | { 6 | std::string name; 7 | int x = 0; 8 | 9 | bool operator==(const base& rhs) const 10 | { 11 | if (name != rhs.name) 12 | return false; 13 | 14 | if (x != rhs.x) 15 | return false; 16 | 17 | return true; 18 | } 19 | }; 20 | 21 | struct derived : 22 | base, 23 | tct::properties::properties 24 | { 25 | derived() 26 | { 27 | LINK_PROPERTY(name, &name); 28 | LINK_PROPERTY(x, &x); 29 | } 30 | 31 | bool operator==(const derived& rhs) const 32 | { 33 | return base::operator==(rhs); 34 | } 35 | }; 36 | 37 | TEST_SUITE("linked properties") 38 | { 39 | TEST_CASE("copy") 40 | { 41 | tct::properties::archiver_xml ar; 42 | 43 | derived d1; 44 | d1.name = "Hello CppProperties!"; 45 | d1.x = 42; 46 | 47 | derived d2; 48 | d2.load(ar, d1.save(ar)); 49 | 50 | // Check whether the copy was successfully 51 | CHECK_EQ(d1, d2); 52 | 53 | // Make sure that our comparison operators are not fucked 54 | d2.x = -284; 55 | CHECK(d1 != d2); 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /test/test_suites/properties.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | using namespace tct::properties; 4 | 5 | struct p_copyable : 6 | properties 7 | { 8 | MAKE_PROPERTY(str, std::string); 9 | 10 | p_copyable() = default; 11 | p_copyable(const p_copyable& other) = delete; 12 | p_copyable(p_copyable&& other) = delete; 13 | 14 | p_copyable& operator=(const p_copyable& rhs) 15 | { 16 | str = rhs.str; 17 | return *this; 18 | } 19 | 20 | p_copyable& operator=(p_copyable&& rhs) noexcept = delete; 21 | }; 22 | 23 | struct p_movable : 24 | properties 25 | { 26 | MAKE_PROPERTY(str, std::string); 27 | 28 | p_movable() = default; 29 | p_movable(const p_movable& other) = delete; 30 | p_movable(p_movable&& other) noexcept = delete; 31 | 32 | p_movable& operator=(const p_movable& rhs) = delete; 33 | 34 | p_movable& operator=(p_movable&& rhs) noexcept 35 | { 36 | str = std::move(rhs.str); 37 | return *this; 38 | } 39 | }; 40 | 41 | struct p_non_movable : 42 | properties 43 | { 44 | MAKE_PROPERTY(p, non_movable); 45 | }; 46 | 47 | TEST_SUITE("properties") 48 | { 49 | TEST_CASE("copy ctor") 50 | { 51 | /* 52 | using t = p_copyable; 53 | REQUIRE(std::is_copy_constructible_v); 54 | 55 | t t1; 56 | t1.str = "Hello World!"; 57 | 58 | t t2(t1); 59 | REQUIRE_EQ(t1.str, "Hello World!"); 60 | REQUIRE_EQ(t2.str, "Hello World!"); 61 | 62 | // Modify t2 63 | t2.str = "test"; 64 | REQUIRE_EQ(t1.str, "Hello World!"); 65 | REQUIRE_EQ(t2.str, "test"); 66 | */ 67 | } 68 | 69 | TEST_CASE("move ctor") 70 | { 71 | using t = p_movable; 72 | //REQUIRE(std::is_move_constructible_v); 73 | 74 | t t1; 75 | t1.str = "Hello World!"; 76 | 77 | //t t2(std::move(t1)); 78 | //REQUIRE_EQ(t1.properties_count(), 0); 79 | //REQUIRE_EQ(t2.properties_count(), 1); 80 | //REQUIRE_EQ(t2.str, "Hello World!"); 81 | } 82 | 83 | TEST_CASE("copy assignment") 84 | { 85 | using t = p_copyable; 86 | REQUIRE(std::is_copy_assignable_v); 87 | 88 | t t1; 89 | t1.str = "Hello World!"; 90 | 91 | t t2; 92 | t2 = t1; 93 | 94 | // Check equality 95 | REQUIRE_EQ(t1.str, "Hello World!"); 96 | REQUIRE_EQ(t2.str, "Hello World!"); 97 | 98 | // Modify t2 99 | t2.str = "test"; 100 | 101 | // Ensure that t1 remained unmodified 102 | REQUIRE_EQ(t1.str, "Hello World!"); 103 | REQUIRE_EQ(t2.str, "test"); 104 | } 105 | 106 | TEST_CASE("move assignment") 107 | { 108 | using t = p_movable; 109 | REQUIRE(std::is_move_assignable_v); 110 | 111 | t t1; 112 | t1.str = "Hello World!"; 113 | 114 | t t2; 115 | t2 = std::move(t1); 116 | 117 | // Check equality 118 | REQUIRE_EQ(t1.str, ""); 119 | REQUIRE_EQ(t2.str, "Hello World!"); 120 | 121 | // Modify t2 122 | t2.str = "test"; 123 | REQUIRE_EQ(t1.str, ""); 124 | REQUIRE_EQ(t2.str, "test"); 125 | 126 | // Modify t1 127 | t1.str = "foo"; 128 | REQUIRE_EQ(t1.str, "foo"); 129 | REQUIRE_EQ(t2.str, "test"); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/test_suites/property.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | using namespace tct::properties; 4 | 5 | TEST_SUITE("property") 6 | { 7 | TEST_CASE("copy ctor") 8 | { 9 | SUBCASE("copyable") 10 | { 11 | using t = property; 12 | REQUIRE(std::is_copy_constructible_v); 13 | 14 | t t1; 15 | t1 = "Hello World!"; 16 | 17 | t t2(t1); 18 | REQUIRE_EQ(t2, "Hello World!"); 19 | } 20 | 21 | SUBCASE("non-copyable") 22 | { 23 | using t = property; 24 | REQUIRE(not std::is_copy_constructible_v); 25 | 26 | t t1; 27 | 28 | //t t2(t1); // Compile error 29 | } 30 | } 31 | 32 | TEST_CASE("copy assignment") 33 | { 34 | SUBCASE("copyable") 35 | { 36 | using t = property; 37 | REQUIRE(std::is_copy_assignable_v); 38 | 39 | t t1; 40 | t1 = "Hello World!"; 41 | 42 | t t2; 43 | t2 = t1; 44 | 45 | REQUIRE_EQ(t2, "Hello World!"); 46 | } 47 | 48 | SUBCASE("non-copyable") 49 | { 50 | using t = non_copyable; 51 | REQUIRE(not std::is_copy_assignable_v); 52 | 53 | t t1; 54 | t t2; 55 | 56 | //t2 = t1; // Compile error 57 | } 58 | } 59 | 60 | TEST_CASE("move ctor") 61 | { 62 | SUBCASE("movable") 63 | { 64 | using t = property; 65 | REQUIRE(std::is_move_constructible_v); 66 | 67 | t t1; 68 | t1 = "Hello World!"; 69 | 70 | t t2(t1); 71 | REQUIRE_EQ(t2, "Hello World!"); 72 | } 73 | 74 | SUBCASE("non-movable") 75 | { 76 | using t = property; 77 | REQUIRE(not std::is_move_constructible_v); 78 | 79 | t t1; 80 | 81 | //t t2(t1); // Compile error 82 | } 83 | } 84 | 85 | TEST_CASE("move assignment") 86 | { 87 | SUBCASE("movable") 88 | { 89 | using t = property; 90 | REQUIRE(std::is_move_assignable_v); 91 | 92 | t t1; 93 | t1 = "Hello World!"; 94 | 95 | t t2; 96 | t2 = std::move(t1); 97 | 98 | REQUIRE_EQ(t2, "Hello World!"); 99 | } 100 | 101 | SUBCASE("non-movable") 102 | { 103 | using t = non_movable; 104 | REQUIRE(not std::is_move_assignable_v); 105 | 106 | t t1; 107 | t t2; 108 | 109 | // t2 = t1; // Compile error 110 | } 111 | } 112 | 113 | TEST_CASE("value copy assignment") 114 | { 115 | using dt = std::string; 116 | using t = property
; 117 | REQUIRE(std::is_copy_assignable_v
); 118 | 119 | t t1; 120 | t1 = "Hello World!"; 121 | REQUIRE_EQ(t1.data, "Hello World!"); 122 | } 123 | 124 | TEST_CASE("value move assignment") 125 | { 126 | using dt = std::string; 127 | using t = property
; 128 | REQUIRE(std::is_move_assignable_v
); 129 | 130 | t t1; 131 | std::string str("Hello World!"); 132 | t1 = std::move(str); 133 | REQUIRE_EQ(t1.data, "Hello World!"); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/test_suites/qt.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | TEST_SUITE("qt") 4 | { 5 | struct foo : 6 | tct::properties::properties 7 | { 8 | MAKE_PROPERTY(name, QString); 9 | }; 10 | 11 | TEST_CASE("QString") 12 | { 13 | SUBCASE("raw") 14 | { 15 | foo f; 16 | f.name = "Hello World!"; 17 | REQUIRE_EQ(f.name, QString("Hello World!")); 18 | } 19 | 20 | SUBCASE("getter/setter") 21 | { 22 | foo f; 23 | f.set_property("name", QString("Test 123")); 24 | REQUIRE_EQ(f.get_property("name"), QString("Test 123")); 25 | } 26 | 27 | SUBCASE("to string") 28 | { 29 | foo f; 30 | f.name = "Hello World!"; 31 | std::string str; 32 | REQUIRE_NOTHROW(str = f.name.to_string()); 33 | REQUIRE_EQ(str, "Hello World!"); 34 | } 35 | 36 | SUBCASE("from string") 37 | { 38 | foo f; 39 | REQUIRE_NOTHROW(f.name.from_string("Hello World!")); 40 | REQUIRE_EQ(f.name, QString{"Hello World!"}); 41 | } 42 | } 43 | 44 | TEST_CASE("QPoint") 45 | { 46 | struct foo : 47 | tct::properties::properties 48 | { 49 | MAKE_PROPERTY(point, QPoint); 50 | }; 51 | 52 | SUBCASE("raw") 53 | { 54 | foo f; 55 | f.point = { -17, 24 }; 56 | REQUIRE_EQ(f.point.data.x(), -17); 57 | REQUIRE_EQ(f.point.data.y(), 24); 58 | } 59 | 60 | SUBCASE("to string") 61 | { 62 | foo f; 63 | f.point = { -17, 24 }; 64 | 65 | const std::string& str = f.point.to_string(); 66 | REQUIRE_EQ(str, "-17;24"); 67 | } 68 | 69 | SUBCASE("from string") 70 | { 71 | foo f; 72 | 73 | REQUIRE_NOTHROW(f.point.from_string("14;-38")); 74 | REQUIRE_EQ(f.point.data.x(), 14); 75 | REQUIRE_EQ(f.point.data.y(), -38); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /test/test_suites/serialization_json.cpp: -------------------------------------------------------------------------------- 1 | #include "../test.hpp" 2 | 3 | #include "cppproperties/archiver_json.hpp" 4 | 5 | #include 6 | 7 | TEST_SUITE("serialization - json") 8 | { 9 | tct::properties::archiver_json ar; 10 | 11 | TEST_CASE("serialize") 12 | { 13 | test_shape dut; 14 | 15 | const std::string& json_str = ar.save(dut); 16 | 17 | //std::cout << json_str << std::endl; 18 | } 19 | } 20 | --------------------------------------------------------------------------------