Imporve documentation and build script
This commit is contained in:
parent
a768408493
commit
104e362762
|
@ -16,8 +16,11 @@ set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_
|
|||
# set project name for IDEs like Qt Creator
|
||||
project(${META_PROJECT_NAME})
|
||||
|
||||
# ensure testing is enabled at this level (and not only for particular sub directories)
|
||||
enable_testing()
|
||||
|
||||
# allow bundling c++utilities
|
||||
set(BUNDLED_CPP_UTILITIES_PATH OFF CACHE FILEPATH "specifies the (relative) path to the c++utilities sources for building it together with ${META_PROJECT_NAME}")
|
||||
set(BUNDLED_CPP_UTILITIES_PATH OFF CACHE PATH "specifies the (relative) path to the c++utilities sources for building it together with ${META_PROJECT_NAME}")
|
||||
if(NOT BUNDLED_CPP_UTILITIES_PATH)
|
||||
message(STATUS "Using system c++utilities")
|
||||
elseif(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${BUNDLED_CPP_UTILITIES_PATH}" OR IS_DIRECTORY "${BUNDLED_CPP_UTILITIES_PATH}")
|
||||
|
|
33
README.md
33
README.md
|
@ -1,4 +1,4 @@
|
|||
# reflective-rapidjson
|
||||
# Reflective RapidJSON
|
||||
|
||||
The main goal of this project is to provide a code generator for serializing/deserializing C++ objects to/from JSON
|
||||
using Clang and RapidJSON.
|
||||
|
@ -131,9 +131,34 @@ makes use of the `reflective-rapidjson`.
|
|||
|
||||
### How to build
|
||||
Install all required dependencies and ensure the CMake script finds them. It is possible to build `c++utilities`
|
||||
together `reflective-rapidjson` to ease the build process. The following build script makes use of this. To use
|
||||
system `c++utilities`, just skip any lines with `c++utilities` in it.
|
||||
together with `reflective-rapidjson` to simplify the build process. The following build script makes use of this.
|
||||
To use system `c++utilities`, just skip any lines with "`c++utilities`" in the following examples.
|
||||
|
||||
Get sources, eg. using Git:
|
||||
```
|
||||
TODO
|
||||
cd $SOURCES
|
||||
git clone https://github.com/Martchus/cpp-utilities.git c++utilities
|
||||
git clone https://github.com/Martchus/reflective-rapidjson.git
|
||||
```
|
||||
|
||||
If you don't want to build the development version, just checkout the desired version tag.
|
||||
|
||||
Here is an example for building with CMake and GNU Make:
|
||||
```
|
||||
cd $BUILD_DIR
|
||||
# generate Makefile
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE:STRING=Release \
|
||||
-DCMAKE_INSTALL_PREFIX:PATH="/final/install/prefix" \
|
||||
-DBUNDLED_CPP_UTILITIES_PATH:PATH="$SOURCES/c++utilities" \
|
||||
"$SOURCES/reflective-rapidjson"
|
||||
# build library and generators
|
||||
make
|
||||
# run test (optional, requires CppUnit)
|
||||
make check
|
||||
# generates API documentation (optional, reqquires Doxygen)
|
||||
make apidoc
|
||||
# install header files, libraries and generator
|
||||
make install DESTDIR="/temporary/install/location"
|
||||
```
|
||||
Add eg. `-j$(nproc)` to `make` arguments for using all cores.
|
||||
|
|
|
@ -34,23 +34,27 @@ set(TEST_SRC_FILES
|
|||
tests/jsongenerator.cpp
|
||||
)
|
||||
|
||||
# find c++utilities
|
||||
# find c++utilities and link against the library
|
||||
find_package(c++utilities 4.11.0 REQUIRED)
|
||||
use_cpp_utilities()
|
||||
|
||||
# find libclang
|
||||
# find Clang for LibTooling; adding clangTooling should be sufficient as it pulls all transitive dependencies
|
||||
find_package(Clang REQUIRED)
|
||||
list(APPEND PRIVATE_LIBRARIES clangTooling)
|
||||
|
||||
# also add reflective_rapidjson
|
||||
# also add reflective_rapidjson which is header-only but might pull additional include dirs for RapidJSON
|
||||
list(APPEND PRIVATE_LIBRARIES reflective_rapidjson)
|
||||
|
||||
# trigger code generator for tests
|
||||
# trigger code generator because the tests already contain structs to be (de)serialized
|
||||
include(ReflectionGenerator)
|
||||
add_reflection_generator_invocation(
|
||||
INPUT_FILES tests/structs.h tests/cppunit.cpp
|
||||
GENERATORS json
|
||||
OUTPUT_LISTS TEST_HEADER_FILES
|
||||
INPUT_FILES
|
||||
tests/structs.h # used by test cases
|
||||
tests/cppunit.cpp # just for testing multiple input files and the "empty file" case
|
||||
GENERATORS
|
||||
json
|
||||
OUTPUT_LISTS
|
||||
TEST_HEADER_FILES
|
||||
)
|
||||
|
||||
# include modules to apply configuration
|
||||
|
|
|
@ -26,9 +26,20 @@ set(DOC_FILES
|
|||
README.md
|
||||
)
|
||||
|
||||
# find c++utilities
|
||||
# find c++utilities, but only add the include dirs because we're not depending on the actual library
|
||||
find_package(c++utilities 4.11.0 REQUIRED)
|
||||
use_cpp_utilities()
|
||||
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS ${CPP_UTILITIES_INCLUDE_DIRS})
|
||||
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS ${CPP_UTILITIES_INCLUDE_DIRS})
|
||||
list(APPEND CMAKE_MODULE_PATH ${CPP_UTILITIES_MODULE_DIRS})
|
||||
|
||||
# find RapidJSON, also add only the include dirs because RapidJSON is a header-only library
|
||||
find_package(RapidJSON)
|
||||
if(RapidJSON_FOUND)
|
||||
list(APPEND PUBLIC_SHARED_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS})
|
||||
list(APPEND PUBLIC_STATIC_INCLUDE_DIRS ${RAPIDJSON_INCLUDE_DIRS})
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find RapidJSON. Since this is the only reflection application supported at this time it makes no sense to continue.")
|
||||
endif()
|
||||
|
||||
# include modules to apply configuration
|
||||
include(BasicConfig)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <rapidjson/rapidjson.h>
|
||||
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
|
@ -23,7 +23,10 @@ namespace ReflectiveRapidJSON {
|
|||
|
||||
template <typename Type> struct JsonSerializable;
|
||||
|
||||
inline RAPIDJSON_NAMESPACE::StringBuffer documentToString(RAPIDJSON_NAMESPACE::Document &document)
|
||||
/*!
|
||||
* \brief Serializes the specified JSON \a document.
|
||||
*/
|
||||
inline RAPIDJSON_NAMESPACE::StringBuffer serializeJsonDocToString(RAPIDJSON_NAMESPACE::Document &document)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::StringBuffer buffer;
|
||||
RAPIDJSON_NAMESPACE::Writer<RAPIDJSON_NAMESPACE::StringBuffer> writer(buffer);
|
||||
|
@ -31,7 +34,10 @@ inline RAPIDJSON_NAMESPACE::StringBuffer documentToString(RAPIDJSON_NAMESPACE::D
|
|||
return buffer;
|
||||
}
|
||||
|
||||
inline RAPIDJSON_NAMESPACE::Document parseDocumentFromString(const char *json, std::size_t jsonSize)
|
||||
/*!
|
||||
* \brief Parses the specified JSON string.
|
||||
*/
|
||||
inline RAPIDJSON_NAMESPACE::Document parseJsonDocFromString(const char *json, std::size_t jsonSize)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kObjectType);
|
||||
const RAPIDJSON_NAMESPACE::ParseResult parseRes = document.Parse(json, jsonSize);
|
||||
|
@ -45,22 +51,35 @@ namespace Reflector {
|
|||
|
||||
// define functions to "push" values to a RapidJSON array or object
|
||||
|
||||
/*!
|
||||
* \brief Pushes the \a reflectable which has a custom type to the specified array.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
/*!
|
||||
* \brief Pushes the \a reflectable which has a custom type to the specified object.
|
||||
* \remarks The definition of this function must be provided by the code generator or Boost.Hana.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator);
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified integer/float/boolean to the specified array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>>...>
|
||||
inline void push(Type reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.PushBack(reflectable, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified C-string to the specified array.
|
||||
*/
|
||||
template <>
|
||||
inline void push<const char *>(
|
||||
const char *reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
|
@ -68,13 +87,9 @@ inline void push<const char *>(
|
|||
value.PushBack(RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void push<std::string>(
|
||||
const std::string &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.PushBack(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified constant C-string to the specified array.
|
||||
*/
|
||||
template <>
|
||||
inline void push<const char *const &>(
|
||||
const char *const &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
|
@ -82,6 +97,19 @@ inline void push<const char *const &>(
|
|||
value.PushBack(RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified std::string to the specified array.
|
||||
*/
|
||||
template <>
|
||||
inline void push<std::string>(
|
||||
const std::string &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.PushBack(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified iteratable (eg. std::vector, std::list) to the specified array.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
|
@ -94,6 +122,9 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAP
|
|||
value.PushBack(array, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified \a reflectable which has a custom type as member to the specified object.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
|
@ -106,6 +137,9 @@ inline void push(
|
|||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), object, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified integer/float/boolean as member to the specified object.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>>...>
|
||||
inline void push(
|
||||
Type reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
|
@ -113,13 +147,9 @@ inline void push(
|
|||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), reflectable, allocator);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void push<std::string>(const std::string &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value,
|
||||
RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified C-string as member to the specified object.
|
||||
*/
|
||||
template <>
|
||||
inline void push<const char *>(
|
||||
const char *reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value, RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
|
@ -127,6 +157,9 @@ inline void push<const char *>(
|
|||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified constant C-string as member to the specified object.
|
||||
*/
|
||||
template <>
|
||||
inline void push<const char *const &>(const char *const &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value,
|
||||
RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
|
@ -134,6 +167,19 @@ inline void push<const char *const &>(const char *const &reflectable, const char
|
|||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), RAPIDJSON_NAMESPACE::StringRef(reflectable), allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified std::string as member to the specified object.
|
||||
*/
|
||||
template <>
|
||||
inline void push<std::string>(const std::string &reflectable, const char *name, RAPIDJSON_NAMESPACE::Value::Object &value,
|
||||
RAPIDJSON_NAMESPACE::Document::AllocatorType &allocator)
|
||||
{
|
||||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified iteratable without size() method as member to the specified object.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::HasSize<Type>>,
|
||||
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
|
@ -148,6 +194,9 @@ void push(
|
|||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), array, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the specified iteratable with size() method (eg. std::vector, std::list) as member to the specified object.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::HasSize<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void push(
|
||||
|
@ -162,6 +211,9 @@ void push(
|
|||
value.AddMember(RAPIDJSON_NAMESPACE::StringRef(name), array, allocator);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pushes the \a reflectable which has a custom type to the specified array.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
|
@ -175,18 +227,28 @@ void push(const Type &reflectable, RAPIDJSON_NAMESPACE::Value::Array &value, RAP
|
|||
|
||||
// define functions to "pull" values from a RapidJSON array or object
|
||||
|
||||
/*!
|
||||
* \brief Pulls the \a reflectable which has a custom type from the specified value.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(
|
||||
Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the \a reflectable which has a custom type from the specified object.
|
||||
* \remarks The definition of this function must be provided by the code generator or Boost.Hana.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value,
|
||||
JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the \a reflectable which has a custom type from the specified value which is supposed and checked to contain an object.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::DisableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>,
|
||||
Traits::All<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>>...>
|
||||
|
@ -201,6 +263,9 @@ void pull(Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_N
|
|||
pull<Type>(reflectable, value.GetObject(), errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the integer/float/boolean from the specified value iterator which is supposed and checked to contain the right type.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>>...>
|
||||
inline void pull(
|
||||
Type &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JsonDeserializationErrors *errors)
|
||||
|
@ -215,6 +280,9 @@ inline void pull(
|
|||
++value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the integer/float/boolean from the specified value which is supposed and checked to contain the right type.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>, std::is_pointer<Type>>...>
|
||||
inline void pull(
|
||||
Type &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
|
@ -228,6 +296,9 @@ inline void pull(
|
|||
reflectable = value.Get<Type>();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the std::string from the specified value iterator which is supposed and checked to contain a string.
|
||||
*/
|
||||
template <>
|
||||
inline void pull<std::string>(std::string &reflectable, RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value,
|
||||
JsonDeserializationErrors *errors)
|
||||
|
@ -242,6 +313,9 @@ inline void pull<std::string>(std::string &reflectable, RAPIDJSON_NAMESPACE::Gen
|
|||
++value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the std::string from the specified value which is supposed and checked to contain a string.
|
||||
*/
|
||||
template <>
|
||||
inline void pull<std::string>(
|
||||
std::string &reflectable, const RAPIDJSON_NAMESPACE::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
|
@ -255,9 +329,15 @@ inline void pull<std::string>(
|
|||
reflectable = value.GetString();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the speciified \a reflectable which is an iteratable from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors);
|
||||
|
||||
/*!
|
||||
* \brief Pulls the speciified \a reflectable which is an iteratable without reserve() method from the specified value iterator which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsReservable<Type>>,
|
||||
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
|
@ -273,6 +353,9 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
|
|||
++value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the speciified \a reflectable which is an iteratable with reserve() method from the specified value iterator which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::IsReservable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ValueIterator &value, JsonDeserializationErrors *errors)
|
||||
|
@ -289,6 +372,9 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
|
|||
++value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the speciified \a reflectable which is an iteratable without reserve() method from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsReservable<Type>>,
|
||||
Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
|
@ -303,6 +389,9 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
pull(reflectable, value.GetArray(), errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the speciified \a reflectable which is an iteratable with reserve() method from the specified value which is checked to contain an array.
|
||||
*/
|
||||
template <typename Type,
|
||||
Traits::EnableIf<Traits::IsIteratable<Type>, Traits::IsReservable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>> &value, JsonDeserializationErrors *errors)
|
||||
|
@ -318,6 +407,9 @@ void pull(Type &reflectable, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::
|
|||
pull(reflectable, array, errors);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the speciified \a reflectable which is an iteratable from the specified array. The \a reflectable is cleared before.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIf<Traits::IsIteratable<Type>, Traits::Not<Traits::IsSpecializationOf<Type, std::basic_string>>>...>
|
||||
void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstArray array, JsonDeserializationErrors *errors)
|
||||
{
|
||||
|
@ -342,6 +434,11 @@ void pull(Type &reflectable, rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<c
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Pulls the speciified member of \a reflectable which has a custom type from the specified object.
|
||||
* \remarks It is checked whether the object actually contains the member. If not, the missing member is ignored. So currently all members
|
||||
* are optional.
|
||||
*/
|
||||
template <typename Type>
|
||||
inline void pull(Type &reflectable, const char *name, const rapidjson::GenericValue<RAPIDJSON_NAMESPACE::UTF8<char>>::ConstObject &value,
|
||||
JsonDeserializationErrors *errors)
|
||||
|
@ -370,44 +467,59 @@ inline void pull(Type &reflectable, const char *name, const rapidjson::GenericVa
|
|||
|
||||
// define functions providing high-level JSON serialization
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which has a custom type.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_base_of<JsonSerializable<Type>, Type>>...>
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson(const Type &reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kObjectType);
|
||||
RAPIDJSON_NAMESPACE::Document::Object object(document.GetObject());
|
||||
push(reflectable, object, document.GetAllocator());
|
||||
return documentToString(document);
|
||||
return serializeJsonDocToString(document);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which is an integer, float or boolean.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>>...>
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson(Type reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kNumberType);
|
||||
document.Set(reflectable, document.GetAllocator());
|
||||
return documentToString(document);
|
||||
return serializeJsonDocToString(document);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which is an std::string.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::string>>...>
|
||||
RAPIDJSON_NAMESPACE::StringBuffer toJson(const std::string &reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
|
||||
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable.data(), reflectable.size()), document.GetAllocator());
|
||||
return documentToString(document);
|
||||
return serializeJsonDocToString(document);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Serializes the specified \a reflectable which is a C-string.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, const char *>>...> RAPIDJSON_NAMESPACE::StringBuffer toJson(const char *reflectable)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document document(RAPIDJSON_NAMESPACE::kStringType);
|
||||
document.SetString(RAPIDJSON_NAMESPACE::StringRef(reflectable), document.GetAllocator());
|
||||
return documentToString(document);
|
||||
return serializeJsonDocToString(document);
|
||||
}
|
||||
|
||||
// define functions providing high-level JSON deserialization
|
||||
|
||||
/*!
|
||||
* \brief Deserializes the specified JSON to \tparam Type which is a custom type.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_base_of<JsonSerializable<Type>, Type>>...>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors = nullptr)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseDocumentFromString(json, jsonSize));
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
|
||||
if (!doc.IsObject()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(doc.GetType());
|
||||
|
@ -420,10 +532,13 @@ Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors
|
|||
return res;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Deserializes the specified JSON to \tparam Type which is an integer, float or boolean.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_integral<Type>, std::is_floating_point<Type>>...>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseDocumentFromString(json, jsonSize));
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
|
||||
if (!doc.Is<Type>()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(doc.GetType());
|
||||
|
@ -434,10 +549,13 @@ Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors
|
|||
return doc.Get<Type>();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Deserializes the specified JSON to \tparam Type which is a std::string.
|
||||
*/
|
||||
template <typename Type, Traits::EnableIfAny<std::is_same<Type, std::string>>...>
|
||||
Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors *errors)
|
||||
{
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseDocumentFromString(json, jsonSize));
|
||||
RAPIDJSON_NAMESPACE::Document doc(parseJsonDocFromString(json, jsonSize));
|
||||
if (!doc.IsString()) {
|
||||
if (errors) {
|
||||
errors->reportTypeMismatch<Type>(doc.GetType());
|
||||
|
@ -448,6 +566,9 @@ Type fromJson(const char *json, std::size_t jsonSize, JsonDeserializationErrors
|
|||
return doc.GetString();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Deserializes the specified JSON from an std::string to \tparam Type.
|
||||
*/
|
||||
template <typename Type> Type fromJson(const std::string &json)
|
||||
{
|
||||
return fromJson<Type>(json.data(), json.size());
|
||||
|
|
|
@ -31,11 +31,6 @@ using namespace ReflectiveRapidJSON;
|
|||
|
||||
// define some structs for testing serialization
|
||||
struct TestObjectHana : public JsonSerializable<TestObjectHana> {
|
||||
//TestObjectHana(){};
|
||||
//TestObjectHana(const TestObjectHana &)
|
||||
//{
|
||||
// std::cout << "copied!!" << std::endl;
|
||||
//};
|
||||
BOOST_HANA_DEFINE_STRUCT(TestObjectHana, (int, number), (double, number2), (vector<int>, numbers), (string, text), (bool, boolean));
|
||||
};
|
||||
|
||||
|
@ -50,15 +45,13 @@ struct NestingArrayHana : public JsonSerializable<NestingArrayHana> {
|
|||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \brief The ReflectorTests class tests RapidJSON wrapper which is used to ease code generation.
|
||||
* \remarks In this tests, no reflection or code generation is involved yet.
|
||||
* \brief The JsonReflectorBoostHanaTests class tests the integration of Boost.Hana with the RapidJSON wrapper.
|
||||
* \remarks In these tests, the reflection is provided through Boost.Hana so the code generator is not involved.
|
||||
*/
|
||||
class JSONReflectorBoostHanaTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(JSONReflectorBoostHanaTests);
|
||||
CPPUNIT_TEST(testSerializePrimitives);
|
||||
class JsonReflectorBoostHanaTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(JsonReflectorBoostHanaTests);
|
||||
CPPUNIT_TEST(testSerializeSimpleObjects);
|
||||
CPPUNIT_TEST(testSerializeNestedObjects);
|
||||
CPPUNIT_TEST(testDeserializePrimitives);
|
||||
CPPUNIT_TEST(testDeserializeSimpleObjects);
|
||||
CPPUNIT_TEST(testDeserializeNestedObjects);
|
||||
CPPUNIT_TEST(testHandlingTypeMismatch);
|
||||
|
@ -79,51 +72,20 @@ public:
|
|||
private:
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(JSONReflectorBoostHanaTests);
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(JsonReflectorBoostHanaTests);
|
||||
|
||||
void JSONReflectorBoostHanaTests::setUp()
|
||||
void JsonReflectorBoostHanaTests::setUp()
|
||||
{
|
||||
}
|
||||
|
||||
void JSONReflectorBoostHanaTests::tearDown()
|
||||
void JsonReflectorBoostHanaTests::tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests serializing strings, numbers, arrays and boolean.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializePrimitives()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
Document::AllocatorType &alloc = doc.GetAllocator();
|
||||
doc.SetArray();
|
||||
Document::Array array(doc.GetArray());
|
||||
|
||||
// string
|
||||
Reflector::push<string>("foo"s, array, alloc);
|
||||
Reflector::push<const char *>("bar", array, alloc);
|
||||
// number
|
||||
Reflector::push<int>(25, array, alloc);
|
||||
Reflector::push<double>(12.5, array, alloc);
|
||||
// array
|
||||
Reflector::push<vector<const char *>>({ "foo1", "bar1" }, array, alloc);
|
||||
Reflector::push<list<const char *>>({ "foo2", "bar2" }, array, alloc);
|
||||
Reflector::push<initializer_list<const char *>>({ "foo3", "bar3" }, array, alloc);
|
||||
// boolean
|
||||
Reflector::push<bool>(true, array, alloc);
|
||||
Reflector::push<bool>(false, array, alloc);
|
||||
|
||||
StringBuffer strbuf;
|
||||
Writer<StringBuffer> jsonWriter(strbuf);
|
||||
doc.Accept(jsonWriter);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"[\"foo\",\"bar\",25,12.5,[\"foo1\",\"bar1\"],[\"foo2\",\"bar2\"],[\"foo3\",\"bar3\"],true,false]"s, string(strbuf.GetString()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests serializing objects.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializeSimpleObjects()
|
||||
void JsonReflectorBoostHanaTests::testSerializeSimpleObjects()
|
||||
{
|
||||
TestObjectHana testObj;
|
||||
testObj.number = 42;
|
||||
|
@ -138,7 +100,7 @@ void JSONReflectorBoostHanaTests::testSerializeSimpleObjects()
|
|||
/*!
|
||||
* \brief Tests serializing nested object and arrays.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testSerializeNestedObjects()
|
||||
void JsonReflectorBoostHanaTests::testSerializeNestedObjects()
|
||||
{
|
||||
NestingObjectHana nestingObj;
|
||||
nestingObj.name = "nesting";
|
||||
|
@ -161,43 +123,10 @@ void JSONReflectorBoostHanaTests::testSerializeNestedObjects()
|
|||
string(nestingArray.toJson().GetString()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests deserializing strings, numbers (int, float, double) and boolean.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializePrimitives()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
|
||||
doc.Parse("[\"a\", 5, 5e6, \"test\", true, 4.125, false]");
|
||||
auto array = doc.GetArray().begin();
|
||||
|
||||
string str1, str2;
|
||||
int int1 = 0;
|
||||
bool bool1 = false, bool2 = true;
|
||||
float float1 = 0.0;
|
||||
double double1 = 0.0;
|
||||
JsonDeserializationErrors errors;
|
||||
Reflector::pull(str1, array, &errors);
|
||||
Reflector::pull(int1, array, &errors);
|
||||
Reflector::pull(float1, array, &errors);
|
||||
Reflector::pull(str2, array, &errors);
|
||||
Reflector::pull(bool1, array, &errors);
|
||||
Reflector::pull(double1, array, &errors);
|
||||
Reflector::pull(bool2, array, &errors);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("a"s, str1);
|
||||
CPPUNIT_ASSERT_EQUAL(5, int1);
|
||||
CPPUNIT_ASSERT_EQUAL(5e6f, float1);
|
||||
CPPUNIT_ASSERT_EQUAL("test"s, str2);
|
||||
CPPUNIT_ASSERT_EQUAL(true, bool1);
|
||||
CPPUNIT_ASSERT_EQUAL(4.125, double1);
|
||||
CPPUNIT_ASSERT_EQUAL(false, bool2);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests deserializing simple objects.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializeSimpleObjects()
|
||||
void JsonReflectorBoostHanaTests::testDeserializeSimpleObjects()
|
||||
{
|
||||
const TestObjectHana testObj(
|
||||
TestObjectHana::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"));
|
||||
|
@ -212,7 +141,7 @@ void JSONReflectorBoostHanaTests::testDeserializeSimpleObjects()
|
|||
/*!
|
||||
* \brief Tests deserializing nested objects and arrays.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testDeserializeNestedObjects()
|
||||
void JsonReflectorBoostHanaTests::testDeserializeNestedObjects()
|
||||
{
|
||||
const NestingObjectHana nestingObj(NestingObjectHana::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"));
|
||||
|
@ -244,7 +173,7 @@ void JSONReflectorBoostHanaTests::testDeserializeNestedObjects()
|
|||
/*!
|
||||
* \brief Tests whether JsonDeserializationError is thrown on type mismatch.
|
||||
*/
|
||||
void JSONReflectorBoostHanaTests::testHandlingTypeMismatch()
|
||||
void JsonReflectorBoostHanaTests::testHandlingTypeMismatch()
|
||||
{
|
||||
JsonDeserializationErrors errors;
|
||||
NestingArrayHana::fromJson("{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,"
|
||||
|
|
|
@ -129,11 +129,11 @@ inline void pull<NestingArray>(NestingArray &reflectable, const GenericValue<UTF
|
|||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \brief The ReflectorTests class tests RapidJSON wrapper which is used to ease code generation.
|
||||
* \remarks In this tests, no reflection or code generation is involved yet.
|
||||
* \brief The JsonReflectorTests class tests RapidJSON wrapper which is used to ease code generation.
|
||||
* \remarks In these tests, the required reflection code is provided by hand so the generator isn't involved yet.
|
||||
*/
|
||||
class JSONReflectorTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(JSONReflectorTests);
|
||||
class JsonReflectorTests : public TestFixture {
|
||||
CPPUNIT_TEST_SUITE(JsonReflectorTests);
|
||||
CPPUNIT_TEST(testSerializePrimitives);
|
||||
CPPUNIT_TEST(testSerializeSimpleObjects);
|
||||
CPPUNIT_TEST(testSerializeNestedObjects);
|
||||
|
@ -161,20 +161,20 @@ public:
|
|||
private:
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(JSONReflectorTests);
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(JsonReflectorTests);
|
||||
|
||||
void JSONReflectorTests::setUp()
|
||||
void JsonReflectorTests::setUp()
|
||||
{
|
||||
}
|
||||
|
||||
void JSONReflectorTests::tearDown()
|
||||
void JsonReflectorTests::tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests serializing strings, numbers, arrays and boolean.
|
||||
*/
|
||||
void JSONReflectorTests::testSerializePrimitives()
|
||||
void JsonReflectorTests::testSerializePrimitives()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
Document::AllocatorType &alloc = doc.GetAllocator();
|
||||
|
@ -205,7 +205,7 @@ void JSONReflectorTests::testSerializePrimitives()
|
|||
/*!
|
||||
* \brief Tests serializing objects.
|
||||
*/
|
||||
void JSONReflectorTests::testSerializeSimpleObjects()
|
||||
void JsonReflectorTests::testSerializeSimpleObjects()
|
||||
{
|
||||
TestObject testObj;
|
||||
testObj.number = 42;
|
||||
|
@ -220,7 +220,7 @@ void JSONReflectorTests::testSerializeSimpleObjects()
|
|||
/*!
|
||||
* \brief Tests serializing nested object and arrays.
|
||||
*/
|
||||
void JSONReflectorTests::testSerializeNestedObjects()
|
||||
void JsonReflectorTests::testSerializeNestedObjects()
|
||||
{
|
||||
NestingObject nestingObj;
|
||||
nestingObj.name = "nesting";
|
||||
|
@ -246,7 +246,7 @@ void JSONReflectorTests::testSerializeNestedObjects()
|
|||
/*!
|
||||
* \brief Tests deserializing strings, numbers (int, float, double) and boolean.
|
||||
*/
|
||||
void JSONReflectorTests::testDeserializePrimitives()
|
||||
void JsonReflectorTests::testDeserializePrimitives()
|
||||
{
|
||||
Document doc(kArrayType);
|
||||
|
||||
|
@ -279,7 +279,7 @@ void JSONReflectorTests::testDeserializePrimitives()
|
|||
/*!
|
||||
* \brief Tests deserializing simple objects.
|
||||
*/
|
||||
void JSONReflectorTests::testDeserializeSimpleObjects()
|
||||
void JsonReflectorTests::testDeserializeSimpleObjects()
|
||||
{
|
||||
const TestObject testObj(
|
||||
TestObject::fromJson("{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}"));
|
||||
|
@ -294,7 +294,7 @@ void JSONReflectorTests::testDeserializeSimpleObjects()
|
|||
/*!
|
||||
* \brief Tests deserializing nested objects and arrays.
|
||||
*/
|
||||
void JSONReflectorTests::testDeserializeNestedObjects()
|
||||
void JsonReflectorTests::testDeserializeNestedObjects()
|
||||
{
|
||||
const NestingObject nestingObj(NestingObject::fromJson("{\"name\":\"nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,"
|
||||
"\"numbers\":[1,2,3,4],\"text\":\"test\",\"boolean\":false}}"));
|
||||
|
@ -325,7 +325,7 @@ void JSONReflectorTests::testDeserializeNestedObjects()
|
|||
/*!
|
||||
* \brief Tests whether RAPIDJSON_NAMESPACE::ParseResult is thrown correctly when passing invalid JSON to fromJSON().
|
||||
*/
|
||||
void JSONReflectorTests::testHandlingParseError()
|
||||
void JsonReflectorTests::testHandlingParseError()
|
||||
{
|
||||
try {
|
||||
NestingObject::fromJson("{\"name\":nesting\",\"testObj\":{\"number\":42,\"number2\":3.141592653589793,\"numbers\":[1,2,3,4],\"text\":"
|
||||
|
@ -340,7 +340,7 @@ void JSONReflectorTests::testHandlingParseError()
|
|||
/*!
|
||||
* \brief Tests whether JsonDeserializationError is thrown on type mismatch.
|
||||
*/
|
||||
void JSONReflectorTests::testHandlingTypeMismatch()
|
||||
void JsonReflectorTests::testHandlingTypeMismatch()
|
||||
{
|
||||
JsonDeserializationErrors errors;
|
||||
NestingArray::fromJson("{\"name\":\"nesting2\",\"testObjects\":[{\"number\":42,\"number2\":3.141592653589793,"
|
||||
|
|
Loading…
Reference in New Issue