weh
This commit is contained in:
parent
693fa17d10
commit
500138ce67
2
.clangd
2
.clangd
|
@ -1,2 +1,2 @@
|
|||
CompileFlags:
|
||||
Add: [-std=c++2b, -Wunused-function]
|
||||
Add: [-std=c++20, -Wunused-function]
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1717112898,
|
||||
"narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=",
|
||||
"lastModified": 1717430266,
|
||||
"narHash": "sha256-EWy2Qbkl/HUwmO8KDBzgDQf+4rl+fLiPFvp3nUSWcxc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0",
|
||||
"rev": "d125f0e4d85f1517b639d4a8f848175da46fcd3e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
26
flake.nix
26
flake.nix
|
@ -46,7 +46,12 @@
|
|||
];
|
||||
};
|
||||
|
||||
stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.llvmPackages_18.stdenv;
|
||||
stdenv = pkgs.llvmPackages_18.stdenv;
|
||||
|
||||
darwinPkgs = nixpkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin; [
|
||||
apple_sdk.frameworks.CoreFoundation
|
||||
apple_sdk.frameworks.MediaPlayer
|
||||
]);
|
||||
in
|
||||
with pkgs; {
|
||||
packages = rec {
|
||||
|
@ -72,7 +77,7 @@
|
|||
]);
|
||||
|
||||
buildInputs = [
|
||||
boost185
|
||||
coost
|
||||
fmt
|
||||
];
|
||||
|
||||
|
@ -105,19 +110,22 @@
|
|||
ninja
|
||||
pkg-config
|
||||
|
||||
boost185
|
||||
coost
|
||||
fmt
|
||||
glib
|
||||
libcpr
|
||||
tomlplusplus
|
||||
]
|
||||
++ (lib.optionals pkgs.hostPlatform.isLinux [playerctl]);
|
||||
++ (lib.optionals pkgs.hostPlatform.isLinux [playerctl])
|
||||
++ darwinPkgs;
|
||||
|
||||
buildInputs = [
|
||||
boost185
|
||||
libcpr
|
||||
tomlplusplus
|
||||
];
|
||||
buildInputs =
|
||||
[
|
||||
coost
|
||||
libcpr
|
||||
tomlplusplus
|
||||
]
|
||||
++ darwinPkgs;
|
||||
|
||||
name = "C++";
|
||||
};
|
||||
|
|
13
include/rfl/bson.hpp
Normal file
13
include/rfl/bson.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef RFL_BSON_HPP_
|
||||
#define RFL_BSON_HPP_
|
||||
|
||||
#include "../rfl.hpp"
|
||||
#include "bson/Parser.hpp"
|
||||
#include "bson/Reader.hpp"
|
||||
#include "bson/Writer.hpp"
|
||||
#include "bson/load.hpp"
|
||||
#include "bson/read.hpp"
|
||||
#include "bson/save.hpp"
|
||||
#include "bson/write.hpp"
|
||||
|
||||
#endif
|
50
include/rfl/bson/Parser.hpp
Normal file
50
include/rfl/bson/Parser.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef RFL_BSON_PARSER_HPP_
|
||||
#define RFL_BSON_PARSER_HPP_
|
||||
|
||||
#include <bson/bson.h>
|
||||
|
||||
#include "../parsing/Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
|
||||
namespace rfl::parsing {
|
||||
|
||||
/// bson_oid_t needs to be treated as a special case, otherwise it will be read
|
||||
/// as a struct.
|
||||
template <class R, class W, class ProcessorsType>
|
||||
requires AreReaderAndWriter<R, W, bson_oid_t>
|
||||
struct Parser<R, W, ProcessorsType, bson_oid_t> {
|
||||
using InputVarType = typename R::InputVarType;
|
||||
using OutputVarType = typename W::OutputVarType;
|
||||
|
||||
using ParentType = Parent<W>;
|
||||
|
||||
static Result<bson_oid_t> read(const R& _r,
|
||||
const InputVarType& _var) noexcept {
|
||||
return _r.template to_basic_type<bson_oid_t>(_var);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
static void write(const W& _w, const bson_oid_t& _oid,
|
||||
const P& _parent) noexcept {
|
||||
ParentType::add_value(_w, _oid, _parent);
|
||||
}
|
||||
|
||||
static schema::Type to_schema(
|
||||
std::map<std::string, schema::Type>* _definitions) {
|
||||
static_assert(rfl::always_false_v<R>,
|
||||
"bson_oid_t cannot be expressed inside a JSON schema.");
|
||||
return schema::Type{schema::Type::String{}};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rfl::parsing
|
||||
|
||||
namespace rfl::bson {
|
||||
|
||||
template <class T, class ProcessorsType>
|
||||
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
|
||||
|
||||
} // namespace rfl::bson
|
||||
|
||||
#endif
|
217
include/rfl/bson/Reader.hpp
Normal file
217
include/rfl/bson/Reader.hpp
Normal file
|
@ -0,0 +1,217 @@
|
|||
#ifndef RFL_BSON_READER_HPP_
|
||||
#define RFL_BSON_READER_HPP_
|
||||
|
||||
#include <bson/bson.h>
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../Box.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace bson {
|
||||
|
||||
/// Please refer to https://mongoc.org/libbson/current/api.html
|
||||
struct Reader {
|
||||
struct BSONValue {
|
||||
bson_value_t val_;
|
||||
};
|
||||
|
||||
struct BSONInputArray {
|
||||
BSONValue* val_;
|
||||
};
|
||||
|
||||
struct BSONInputObject {
|
||||
BSONValue* val_;
|
||||
};
|
||||
|
||||
struct BSONInputVar {
|
||||
BSONValue* val_;
|
||||
};
|
||||
|
||||
using InputArrayType = BSONInputArray;
|
||||
using InputObjectType = BSONInputObject;
|
||||
using InputVarType = BSONInputVar;
|
||||
|
||||
template <class T>
|
||||
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
|
||||
T::from_bson_obj(var);
|
||||
});
|
||||
|
||||
rfl::Result<InputVarType> get_field(
|
||||
const std::string& _name, const InputObjectType& _obj) const noexcept {
|
||||
bson_t b;
|
||||
bson_iter_t iter;
|
||||
const auto doc = _obj.val_->val_.value.v_doc;
|
||||
if (bson_init_static(&b, doc.data, doc.data_len)) {
|
||||
if (bson_iter_init(&iter, &b)) {
|
||||
while (bson_iter_next(&iter)) {
|
||||
auto key = std::string(bson_iter_key(&iter));
|
||||
if (key == _name) {
|
||||
return to_input_var(&iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Error("No field named '" + _name + "' was found.");
|
||||
}
|
||||
|
||||
bool is_empty(const InputVarType& _var) const noexcept {
|
||||
return _var.val_->val_.value_type == BSON_TYPE_NULL;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
|
||||
const auto btype = _var.val_->val_.value_type;
|
||||
const auto value = _var.val_->val_.value;
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
switch (btype) {
|
||||
case BSON_TYPE_UTF8:
|
||||
return std::string(value.v_utf8.str, value.v_utf8.len);
|
||||
|
||||
case BSON_TYPE_SYMBOL:
|
||||
return std::string(value.v_symbol.symbol, value.v_symbol.len);
|
||||
|
||||
default:
|
||||
return rfl::Error(
|
||||
"Could not cast to string. The type must be UTF8 or symbol.");
|
||||
}
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
if (btype != BSON_TYPE_BOOL) {
|
||||
return rfl::Error("Could not cast to boolean.");
|
||||
}
|
||||
return value.v_bool;
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
|
||||
std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
switch (btype) {
|
||||
case BSON_TYPE_DOUBLE:
|
||||
return static_cast<T>(value.v_double);
|
||||
|
||||
case BSON_TYPE_INT32:
|
||||
return static_cast<T>(value.v_int32);
|
||||
|
||||
case BSON_TYPE_INT64:
|
||||
return static_cast<T>(value.v_int64);
|
||||
|
||||
case BSON_TYPE_DATE_TIME:
|
||||
return static_cast<T>(value.v_datetime);
|
||||
|
||||
default:
|
||||
return rfl::Error(
|
||||
"Could not cast to numeric value. The type must be double, "
|
||||
"int32, int64 or date_time.");
|
||||
}
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
|
||||
if (btype != BSON_TYPE_OID) {
|
||||
return rfl::Error("Could not cast to OID.");
|
||||
}
|
||||
return value.v_oid;
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
}
|
||||
|
||||
rfl::Result<InputArrayType> to_array(
|
||||
const InputVarType& _var) const noexcept {
|
||||
const auto btype = _var.val_->val_.value_type;
|
||||
if (btype != BSON_TYPE_ARRAY && btype != BSON_TYPE_DOCUMENT) {
|
||||
return Error("Could not cast to an array.");
|
||||
}
|
||||
return InputArrayType{_var.val_};
|
||||
}
|
||||
|
||||
template <class ArrayReader>
|
||||
std::optional<Error> read_array(const ArrayReader& _array_reader,
|
||||
const InputArrayType& _arr) const noexcept {
|
||||
bson_t b;
|
||||
bson_iter_t iter;
|
||||
const auto doc = _arr.val_->val_.value.v_doc;
|
||||
if (bson_init_static(&b, doc.data, doc.data_len)) {
|
||||
if (bson_iter_init(&iter, &b)) {
|
||||
while (bson_iter_next(&iter)) {
|
||||
const auto err = _array_reader.read(to_input_var(&iter));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class ObjectReader>
|
||||
std::optional<Error> read_object(const ObjectReader& _object_reader,
|
||||
const InputObjectType& _obj) const noexcept {
|
||||
bson_t b;
|
||||
bson_iter_t iter;
|
||||
const auto doc = _obj.val_->val_.value.v_doc;
|
||||
if (bson_init_static(&b, doc.data, doc.data_len)) {
|
||||
if (bson_iter_init(&iter, &b)) {
|
||||
while (bson_iter_next(&iter)) {
|
||||
const char* k = bson_iter_key(&iter);
|
||||
_object_reader.read(std::string_view(k), to_input_var(&iter));
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
rfl::Result<InputObjectType> to_object(
|
||||
const InputVarType& _var) const noexcept {
|
||||
const auto btype = _var.val_->val_.value_type;
|
||||
if (btype != BSON_TYPE_DOCUMENT) {
|
||||
return Error("Could not cast to a document.");
|
||||
}
|
||||
return InputObjectType{_var.val_};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> use_custom_constructor(
|
||||
const InputVarType& _var) const noexcept {
|
||||
try {
|
||||
return T::from_bson_obj(_var);
|
||||
} catch (std::exception& e) {
|
||||
return rfl::Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct BSONValues {
|
||||
std::vector<rfl::Box<BSONValue>> vec_;
|
||||
~BSONValues() {
|
||||
for (auto& v : vec_) {
|
||||
bson_value_destroy(&(v->val_));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
InputVarType to_input_var(bson_iter_t* _iter) const noexcept {
|
||||
values_->vec_.emplace_back(rfl::Box<BSONValue>::make());
|
||||
auto* last_value = values_->vec_.back().get();
|
||||
bson_value_copy(bson_iter_value(_iter), &last_value->val_);
|
||||
return InputVarType{last_value};
|
||||
}
|
||||
|
||||
private:
|
||||
/// Contains the values inside the object.
|
||||
rfl::Ref<BSONValues> values_;
|
||||
};
|
||||
|
||||
} // namespace bson
|
||||
} // namespace rfl
|
||||
|
||||
#endif // JSON_PARSER_HPP_
|
227
include/rfl/bson/Writer.hpp
Normal file
227
include/rfl/bson/Writer.hpp
Normal file
|
@ -0,0 +1,227 @@
|
|||
#ifndef RFL_BSON_WRITER_HPP_
|
||||
#define RFL_BSON_WRITER_HPP_
|
||||
|
||||
#include <bson/bson.h>
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "../Box.hpp"
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace bson {
|
||||
|
||||
/// Please refer to https://mongoc.org/libbson/current/api.html
|
||||
class Writer {
|
||||
struct BSONType {
|
||||
bson_t val_;
|
||||
};
|
||||
|
||||
struct IsArray {
|
||||
bson_array_builder_t* ptr_;
|
||||
};
|
||||
|
||||
struct IsObject {
|
||||
bson_t* ptr_;
|
||||
};
|
||||
|
||||
struct IsRoot {};
|
||||
|
||||
using ParentType = std::variant<IsArray, IsObject, IsRoot>;
|
||||
|
||||
public:
|
||||
struct BSONOutputArray {
|
||||
BSONOutputArray(bson_array_builder_t* _val, ParentType _parent)
|
||||
: parent_(_parent), val_(_val) {}
|
||||
ParentType parent_;
|
||||
bson_array_builder_t* val_;
|
||||
};
|
||||
|
||||
struct BSONOutputObject {
|
||||
BSONOutputObject(bson_t* _val, ParentType _parent)
|
||||
: parent_(_parent), val_(_val) {}
|
||||
ParentType parent_;
|
||||
bson_t* val_;
|
||||
};
|
||||
|
||||
struct BSONOutputVar {};
|
||||
|
||||
using OutputArrayType = BSONOutputArray;
|
||||
using OutputObjectType = BSONOutputObject;
|
||||
using OutputVarType = BSONOutputVar;
|
||||
|
||||
Writer(bson_t* _doc) : doc_(_doc) {}
|
||||
|
||||
~Writer() = default;
|
||||
|
||||
OutputArrayType array_as_root(const size_t _size) const noexcept {
|
||||
bson_array_builder_t* val = bson_array_builder_new();
|
||||
return OutputArrayType(val, IsRoot{});
|
||||
}
|
||||
|
||||
OutputObjectType object_as_root(const size_t _size) const noexcept {
|
||||
return OutputObjectType(doc_, IsRoot{});
|
||||
}
|
||||
|
||||
OutputVarType null_as_root() const noexcept {
|
||||
// Appears to be unsupported by the BSON C API.
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType value_as_root(const T& _var) const noexcept {
|
||||
static_assert(rfl::always_false_v<T>,
|
||||
"BSON only allows arrays or objects as its root.");
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_array(const size_t _size,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
bson_array_builder_t* val;
|
||||
bson_array_builder_append_array_builder_begin(_parent->val_, &val);
|
||||
return OutputArrayType(val, IsArray{_parent->val_});
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
bson_array_builder_t* val;
|
||||
bson_append_array_builder_begin(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()), &val);
|
||||
return OutputArrayType(val, IsObject{_parent->val_});
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_array(
|
||||
const size_t _size, OutputArrayType* _parent) const noexcept {
|
||||
subdocs_->emplace_back(rfl::Box<BSONType>());
|
||||
bson_array_builder_append_document_begin(_parent->val_,
|
||||
&(subdocs_->back()->val_));
|
||||
return OutputObjectType(&subdocs_->back()->val_, IsArray{_parent->val_});
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
subdocs_->emplace_back(rfl::Box<BSONType>());
|
||||
bson_append_document_begin(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()),
|
||||
&(subdocs_->back()->val_));
|
||||
return OutputObjectType(&subdocs_->back()->val_, IsObject{_parent->val_});
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_array(const T& _var,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
bson_array_builder_append_utf8(_parent->val_, _var.c_str(),
|
||||
static_cast<int>(_var.size()));
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
bson_array_builder_append_bool(_parent->val_, _var);
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
bson_array_builder_append_double(_parent->val_,
|
||||
static_cast<double>(_var));
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
bson_array_builder_append_int64(_parent->val_,
|
||||
static_cast<std::int64_t>(_var));
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
|
||||
bson_array_builder_append_oid(_parent->val_, &_var);
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_object(const std::string_view& _name,
|
||||
const T& _var,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
bson_append_utf8(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()), _var.c_str(),
|
||||
static_cast<int>(_var.size()));
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
bson_append_bool(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()), _var);
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
bson_append_double(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()),
|
||||
static_cast<double>(_var));
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
bson_append_int64(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()),
|
||||
static_cast<std::int64_t>(_var));
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bson_oid_t>()) {
|
||||
bson_append_oid(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()), &_var);
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
|
||||
bson_array_builder_append_null(_parent->val_);
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_object(const std::string_view& _name,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
bson_append_null(_parent->val_, _name.data(),
|
||||
static_cast<int>(_name.size()));
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
void end_array(OutputArrayType* _arr) const noexcept {
|
||||
const auto handle = [&](const auto _parent) {
|
||||
using Type = std::remove_cvref_t<decltype(_parent)>;
|
||||
if constexpr (std::is_same<Type, IsArray>()) {
|
||||
bson_array_builder_append_array_builder_end(_parent.ptr_, _arr->val_);
|
||||
} else if constexpr (std::is_same<Type, IsObject>()) {
|
||||
bson_append_array_builder_end(_parent.ptr_, _arr->val_);
|
||||
} else if constexpr (std::is_same<Type, IsRoot>()) {
|
||||
bson_array_builder_build(_arr->val_, doc_);
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
|
||||
}
|
||||
};
|
||||
std::visit(handle, _arr->parent_);
|
||||
}
|
||||
|
||||
void end_object(OutputObjectType* _obj) const noexcept {
|
||||
const auto handle = [&](const auto _parent) {
|
||||
using Type = std::remove_cvref_t<decltype(_parent)>;
|
||||
if constexpr (std::is_same<Type, IsArray>()) {
|
||||
bson_array_builder_append_document_end(_parent.ptr_, _obj->val_);
|
||||
} else if constexpr (std::is_same<Type, IsObject>()) {
|
||||
bson_append_document_end(_parent.ptr_, _obj->val_);
|
||||
} else if constexpr (std::is_same<Type, IsRoot>()) {
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
|
||||
}
|
||||
};
|
||||
std::visit(handle, _obj->parent_);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Pointer to the main document. In BSON, documents are what are usually
|
||||
/// called objects.
|
||||
bson_t* const doc_;
|
||||
|
||||
/// Contain all of the subdocuments.
|
||||
const rfl::Ref<std::vector<rfl::Box<BSONType>>> subdocs_;
|
||||
};
|
||||
|
||||
} // namespace bson
|
||||
} // namespace rfl
|
||||
|
||||
#endif // BSON_PARSER_HPP_
|
22
include/rfl/bson/load.hpp
Normal file
22
include/rfl/bson/load.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef RFL_BSON_LOAD_HPP_
|
||||
#define RFL_BSON_LOAD_HPP_
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/load_bytes.hpp"
|
||||
#include "read.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace bson {
|
||||
|
||||
template <class T, class... Ps>
|
||||
Result<T> load(const std::string& _fname) {
|
||||
const auto read_bytes = [](const auto& _bytes) {
|
||||
return read<T, Ps...>(_bytes);
|
||||
};
|
||||
return rfl::io::load_bytes(_fname).and_then(read_bytes);
|
||||
}
|
||||
|
||||
} // namespace bson
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
61
include/rfl/bson/read.hpp
Normal file
61
include/rfl/bson/read.hpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
#ifndef RFL_BSON_READ_HPP_
|
||||
#define RFL_BSON_READ_HPP_
|
||||
|
||||
#include <bson/bson.h>
|
||||
|
||||
#include <istream>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../internal/wrap_in_rfl_array_t.hpp"
|
||||
#include "Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace bson {
|
||||
|
||||
using InputObjectType = typename Reader::InputObjectType;
|
||||
using InputVarType = typename Reader::InputVarType;
|
||||
|
||||
/// Parses an object from a BSON var.
|
||||
template <class T, class... Ps>
|
||||
Result<internal::wrap_in_rfl_array_t<T>> read(const InputVarType& _obj) {
|
||||
const auto r = Reader();
|
||||
return Parser<T, Processors<Ps...>>::read(r, _obj);
|
||||
}
|
||||
|
||||
/// Parses an BSON object using reflection.
|
||||
template <class T, class... Ps>
|
||||
auto read(const uint8_t* _bytes, const size_t _size) {
|
||||
Reader::BSONValue value;
|
||||
value.val_.value.v_doc.data_len = static_cast<uint32_t>(_size);
|
||||
value.val_.value.v_doc.data = const_cast<uint8_t*>(_bytes);
|
||||
value.val_.value_type = BSON_TYPE_DOCUMENT;
|
||||
auto doc = InputVarType{&value};
|
||||
return read<T, Ps...>(doc);
|
||||
}
|
||||
|
||||
/// Parses an BSON object using reflection.
|
||||
template <class T, class... Ps>
|
||||
auto read(const char* _bytes, const size_t _size) {
|
||||
return read<T, Ps...>(reinterpret_cast<const uint8_t*>(_bytes), _size);
|
||||
}
|
||||
|
||||
/// Parses an object from BSON using reflection.
|
||||
template <class T, class... Ps>
|
||||
auto read(const std::vector<char>& _bytes) {
|
||||
return read<T, Ps...>(_bytes.data(), _bytes.size());
|
||||
}
|
||||
|
||||
/// Parses an object from a stream.
|
||||
template <class T, class... Ps>
|
||||
auto read(std::istream& _stream) {
|
||||
std::istreambuf_iterator<char> begin(_stream), end;
|
||||
auto bytes = std::vector<char>(begin, end);
|
||||
return read<T, Ps...>(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
} // namespace bson
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
26
include/rfl/bson/save.hpp
Normal file
26
include/rfl/bson/save.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef RFL_BSON_SAVE_HPP_
|
||||
#define RFL_BSON_SAVE_HPP_
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/save_bytes.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace bson {
|
||||
|
||||
template <class... Ps>
|
||||
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
|
||||
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
|
||||
return write<Ps...>(_obj, _stream);
|
||||
};
|
||||
return rfl::io::save_bytes(_fname, _obj, write_func);
|
||||
}
|
||||
|
||||
} // namespace bson
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
61
include/rfl/bson/write.hpp
Normal file
61
include/rfl/bson/write.hpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
#ifndef RFL_BSON_WRITE_HPP_
|
||||
#define RFL_BSON_WRITE_HPP_
|
||||
|
||||
#include <bson/bson.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../parsing/Parent.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace bson {
|
||||
|
||||
/// Returns BSON bytes. Careful: It is the responsibility of the caller to call
|
||||
/// bson_free on the returned pointer.
|
||||
template <class... Ps>
|
||||
std::pair<uint8_t*, size_t> to_buffer(const auto& _obj) noexcept {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
bson_t* doc = nullptr;
|
||||
uint8_t* buf = nullptr;
|
||||
size_t buflen = 0;
|
||||
bson_writer_t* bson_writer =
|
||||
bson_writer_new(&buf, &buflen, 0, bson_realloc_ctx, NULL);
|
||||
bson_writer_begin(bson_writer, &doc);
|
||||
const auto rfl_writer = Writer(doc);
|
||||
Parser<T, Processors<Ps...>>::write(rfl_writer, _obj,
|
||||
typename ParentType::Root{});
|
||||
bson_writer_end(bson_writer);
|
||||
const auto len = bson_writer_get_length(bson_writer);
|
||||
bson_writer_destroy(bson_writer);
|
||||
return std::make_pair(buf, len);
|
||||
}
|
||||
|
||||
/// Returns BSON bytes.
|
||||
template <class... Ps>
|
||||
std::vector<char> write(const auto& _obj) noexcept {
|
||||
auto [buf, len] = to_buffer<Ps...>(_obj);
|
||||
const auto result = std::vector<char>(reinterpret_cast<char*>(buf),
|
||||
reinterpret_cast<char*>(buf) + len);
|
||||
bson_free(buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Writes a BSON into an ostream.
|
||||
template <class... Ps>
|
||||
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
|
||||
auto [buf, len] = to_buffer<Ps...>(_obj);
|
||||
_stream.write(reinterpret_cast<const char*>(buf), len);
|
||||
bson_free(buf);
|
||||
return _stream;
|
||||
}
|
||||
|
||||
} // namespace bson
|
||||
} // namespace rfl
|
||||
|
||||
#endif // BSON_PARSER_HPP_
|
13
include/rfl/cbor.hpp
Normal file
13
include/rfl/cbor.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef RFL_CBOR_HPP_
|
||||
#define RFL_CBOR_HPP_
|
||||
|
||||
#include "../rfl.hpp"
|
||||
#include "cbor/Parser.hpp"
|
||||
#include "cbor/Reader.hpp"
|
||||
#include "cbor/Writer.hpp"
|
||||
#include "cbor/load.hpp"
|
||||
#include "cbor/read.hpp"
|
||||
#include "cbor/save.hpp"
|
||||
#include "cbor/write.hpp"
|
||||
|
||||
#endif
|
45
include/rfl/cbor/Parser.hpp
Normal file
45
include/rfl/cbor/Parser.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifndef RFL_CBOR_PARSER_HPP_
|
||||
#define RFL_CBOR_PARSER_HPP_
|
||||
|
||||
#include "../parsing/Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace parsing {
|
||||
|
||||
/// CBOR requires us to explicitly set the number of fields in advance. Because
|
||||
/// of that, we require all of the fields and then set them to nullptr, if
|
||||
/// necessary.
|
||||
template <class ProcessorsType, class... FieldTypes>
|
||||
requires AreReaderAndWriter<cbor::Reader, cbor::Writer,
|
||||
NamedTuple<FieldTypes...>>
|
||||
struct Parser<cbor::Reader, cbor::Writer, NamedTuple<FieldTypes...>,
|
||||
ProcessorsType>
|
||||
: public NamedTupleParser<cbor::Reader, cbor::Writer,
|
||||
/*_ignore_empty_containers=*/false,
|
||||
/*_all_required=*/true, ProcessorsType,
|
||||
FieldTypes...> {
|
||||
};
|
||||
|
||||
template <class ProcessorsType, class... Ts>
|
||||
requires AreReaderAndWriter<cbor::Reader, cbor::Writer, std::tuple<Ts...>>
|
||||
struct Parser<cbor::Reader, cbor::Writer, std::tuple<Ts...>, ProcessorsType>
|
||||
: public TupleParser<cbor::Reader, cbor::Writer,
|
||||
/*_ignore_empty_containers=*/false,
|
||||
/*_all_required=*/true, ProcessorsType, Ts...> {
|
||||
};
|
||||
|
||||
} // namespace parsing
|
||||
} // namespace rfl
|
||||
|
||||
namespace rfl {
|
||||
namespace cbor {
|
||||
|
||||
template <class T, class ProcessorsType>
|
||||
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
|
||||
|
||||
}
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
259
include/rfl/cbor/Reader.hpp
Normal file
259
include/rfl/cbor/Reader.hpp
Normal file
|
@ -0,0 +1,259 @@
|
|||
#ifndef RFL_CBOR_READER_HPP_
|
||||
#define RFL_CBOR_READER_HPP_
|
||||
|
||||
#include <cbor.h>
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <source_location>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../Box.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace cbor {
|
||||
|
||||
/// Please refer to https://intel.github.io/tinycbor/current/index.html
|
||||
struct Reader {
|
||||
struct CBORInputArray {
|
||||
CborValue* val_;
|
||||
};
|
||||
|
||||
struct CBORInputObject {
|
||||
CborValue* val_;
|
||||
};
|
||||
|
||||
struct CBORInputVar {
|
||||
CborValue* val_;
|
||||
};
|
||||
|
||||
using InputArrayType = CBORInputArray;
|
||||
using InputObjectType = CBORInputObject;
|
||||
using InputVarType = CBORInputVar;
|
||||
|
||||
template <class T>
|
||||
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
|
||||
T::from_cbor_obj(var);
|
||||
});
|
||||
|
||||
rfl::Result<InputVarType> get_field(
|
||||
const std::string& _name, const InputObjectType& _obj) const noexcept {
|
||||
CborValue val;
|
||||
auto buffer = std::vector<char>();
|
||||
auto err = cbor_value_enter_container(_obj.val_, &val);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
size_t length = 0;
|
||||
err = cbor_value_get_map_length(_obj.val_, &length);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (!cbor_value_is_text_string(&val)) {
|
||||
return Error("Expected the key to be a string value.");
|
||||
}
|
||||
err = get_string(&val, &buffer);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
err = cbor_value_advance(&val);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
if (_name == buffer.data()) {
|
||||
return to_input_var(&val);
|
||||
}
|
||||
err = cbor_value_advance(&val);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
}
|
||||
return Error("No field named '" + _name + "' was found.");
|
||||
}
|
||||
|
||||
bool is_empty(const InputVarType& _var) const noexcept {
|
||||
return cbor_value_is_null(_var.val_);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
if (!cbor_value_is_text_string(_var.val_)) {
|
||||
return Error("Could not cast to string.");
|
||||
}
|
||||
std::vector<char> buffer;
|
||||
const auto err = get_string(_var.val_, &buffer);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
return std::string(buffer.data());
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
if (!cbor_value_is_boolean(_var.val_)) {
|
||||
return rfl::Error("Could not cast to boolean.");
|
||||
}
|
||||
bool result = false;
|
||||
const auto err = cbor_value_get_boolean(_var.val_, &result);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
return result;
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
|
||||
std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
if (cbor_value_is_integer(_var.val_)) {
|
||||
std::int64_t result = 0;
|
||||
const auto err = cbor_value_get_int64(_var.val_, &result);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
return static_cast<T>(result);
|
||||
} else if (cbor_value_is_float(_var.val_)) {
|
||||
float result = 0.0;
|
||||
const auto err = cbor_value_get_float(_var.val_, &result);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
return static_cast<T>(result);
|
||||
} else if (cbor_value_is_double(_var.val_)) {
|
||||
double result = 0.0;
|
||||
const auto err = cbor_value_get_double(_var.val_, &result);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
return static_cast<T>(result);
|
||||
}
|
||||
return rfl::Error(
|
||||
"Could not cast to numeric value. The type must be integral, float "
|
||||
"or double.");
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
}
|
||||
|
||||
rfl::Result<InputArrayType> to_array(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (!cbor_value_is_array(_var.val_)) {
|
||||
return Error("Could not cast to an array.");
|
||||
}
|
||||
return InputArrayType{_var.val_};
|
||||
}
|
||||
|
||||
rfl::Result<InputObjectType> to_object(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (!cbor_value_is_map(_var.val_)) {
|
||||
return Error("Could not cast to an object.");
|
||||
}
|
||||
return InputObjectType{_var.val_};
|
||||
}
|
||||
|
||||
template <class ArrayReader>
|
||||
std::optional<Error> read_array(const ArrayReader& _array_reader,
|
||||
const InputArrayType& _arr) const noexcept {
|
||||
CborValue val;
|
||||
auto buffer = std::vector<char>();
|
||||
auto err = cbor_value_enter_container(_arr.val_, &val);
|
||||
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
size_t length = 0;
|
||||
err = cbor_value_get_array_length(_arr.val_, &length);
|
||||
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
const auto err2 = _array_reader.read(to_input_var(&val));
|
||||
if (err2) {
|
||||
return err2;
|
||||
}
|
||||
err = cbor_value_advance(&val);
|
||||
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class ObjectReader>
|
||||
std::optional<Error> read_object(const ObjectReader& _object_reader,
|
||||
const InputObjectType& _obj) const noexcept {
|
||||
size_t length = 0;
|
||||
auto err = cbor_value_get_map_length(_obj.val_, &length);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
|
||||
CborValue val;
|
||||
err = cbor_value_enter_container(_obj.val_, &val);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
|
||||
auto buffer = std::vector<char>();
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
err = get_string(&val, &buffer);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
err = cbor_value_advance(&val);
|
||||
if (err != CborNoError) {
|
||||
return Error(cbor_error_string(err));
|
||||
}
|
||||
const auto name = std::string_view(buffer.data(), buffer.size() - 1);
|
||||
_object_reader.read(name, InputVarType{&val});
|
||||
cbor_value_advance(&val);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> use_custom_constructor(
|
||||
const InputVarType& _var) const noexcept {
|
||||
try {
|
||||
return T::from_cbor_obj(_var);
|
||||
} catch (std::exception& e) {
|
||||
return rfl::Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CborError get_string(const CborValue* _ptr,
|
||||
std::vector<char>* _buffer) const noexcept {
|
||||
size_t length = 0;
|
||||
auto err = cbor_value_get_string_length(_ptr, &length);
|
||||
if (err != CborNoError && err != CborErrorOutOfMemory) {
|
||||
return err;
|
||||
}
|
||||
_buffer->resize(length + 1);
|
||||
(*_buffer)[length] = '\0';
|
||||
return cbor_value_copy_text_string(_ptr, _buffer->data(), &length, NULL);
|
||||
}
|
||||
|
||||
InputVarType to_input_var(CborValue* _ptr) const noexcept {
|
||||
values_->emplace_back(rfl::Box<CborValue>::make(*_ptr));
|
||||
auto* last_value = values_->back().get();
|
||||
return InputVarType{last_value};
|
||||
}
|
||||
|
||||
private:
|
||||
/// Contains the values inside the object.
|
||||
rfl::Box<std::vector<rfl::Box<CborValue>>> values_;
|
||||
};
|
||||
|
||||
} // namespace cbor
|
||||
} // namespace rfl
|
||||
|
||||
#endif // JSON_PARSER_HPP_
|
164
include/rfl/cbor/Writer.hpp
Normal file
164
include/rfl/cbor/Writer.hpp
Normal file
|
@ -0,0 +1,164 @@
|
|||
#ifndef RFL_CBOR_WRITER_HPP_
|
||||
#define RFL_CBOR_WRITER_HPP_
|
||||
|
||||
#include <cbor.h>
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "../Box.hpp"
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace cbor {
|
||||
|
||||
class Writer {
|
||||
public:
|
||||
struct CBOROutputArray {
|
||||
CborEncoder* encoder_;
|
||||
CborEncoder* parent_;
|
||||
};
|
||||
|
||||
struct CBOROutputObject {
|
||||
CborEncoder* encoder_;
|
||||
CborEncoder* parent_;
|
||||
};
|
||||
|
||||
struct CBOROutputVar {};
|
||||
|
||||
using OutputArrayType = CBOROutputArray;
|
||||
using OutputObjectType = CBOROutputObject;
|
||||
using OutputVarType = CBOROutputVar;
|
||||
|
||||
Writer(CborEncoder* _encoder) : encoder_(_encoder) {}
|
||||
|
||||
~Writer() = default;
|
||||
|
||||
OutputArrayType array_as_root(const size_t _size) const noexcept {
|
||||
return new_array(_size, encoder_);
|
||||
}
|
||||
|
||||
OutputObjectType object_as_root(const size_t _size) const noexcept {
|
||||
return new_object(_size, encoder_);
|
||||
}
|
||||
|
||||
OutputVarType null_as_root() const noexcept {
|
||||
cbor_encode_null(encoder_);
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType value_as_root(const T& _var) const noexcept {
|
||||
return new_value(_var, encoder_);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_array(const size_t _size,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return new_array(_size, _parent->encoder_);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
|
||||
return new_array(_size, _parent->encoder_);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_array(
|
||||
const size_t _size, OutputArrayType* _parent) const noexcept {
|
||||
return new_object(_size, _parent->encoder_);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
|
||||
return new_object(_size, _parent->encoder_);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_array(const T& _var,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return new_value(_var, _parent->encoder_);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_object(const std::string_view& _name,
|
||||
const T& _var,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
|
||||
return new_value(_var, _parent->encoder_);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
|
||||
cbor_encode_null(_parent->encoder_);
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_object(const std::string_view& _name,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
cbor_encode_text_string(_parent->encoder_, _name.data(), _name.size());
|
||||
cbor_encode_null(_parent->encoder_);
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
void end_array(OutputArrayType* _arr) const noexcept {
|
||||
cbor_encoder_close_container(_arr->parent_, _arr->encoder_);
|
||||
}
|
||||
|
||||
void end_object(OutputObjectType* _obj) const noexcept {
|
||||
cbor_encoder_close_container(_obj->parent_, _obj->encoder_);
|
||||
}
|
||||
|
||||
private:
|
||||
OutputArrayType new_array(const size_t _size,
|
||||
CborEncoder* _parent) const noexcept {
|
||||
subencoders_->emplace_back(rfl::Box<CborEncoder>::make());
|
||||
cbor_encoder_create_array(_parent, subencoders_->back().get(), _size);
|
||||
return OutputArrayType{subencoders_->back().get(), _parent};
|
||||
}
|
||||
|
||||
OutputObjectType new_object(const size_t _size,
|
||||
CborEncoder* _parent) const noexcept {
|
||||
subencoders_->emplace_back(rfl::Box<CborEncoder>::make());
|
||||
cbor_encoder_create_map(_parent, subencoders_->back().get(), _size);
|
||||
return OutputObjectType{subencoders_->back().get(), _parent};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType new_value(const T& _var, CborEncoder* _parent) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
cbor_encode_text_string(_parent, _var.c_str(), _var.size());
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
cbor_encode_boolean(_parent, _var);
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
cbor_encode_double(_parent, static_cast<double>(_var));
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
cbor_encode_int(_parent, static_cast<std::int64_t>(_var));
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
private:
|
||||
/// The underlying TinyCBOR encoder.
|
||||
CborEncoder* const encoder_;
|
||||
|
||||
/// Contain all of the subobjects and subarrays.
|
||||
const rfl::Box<std::vector<rfl::Box<CborEncoder>>> subencoders_;
|
||||
};
|
||||
|
||||
} // namespace cbor
|
||||
} // namespace rfl
|
||||
|
||||
#endif // CBOR_PARSER_HPP_
|
23
include/rfl/cbor/load.hpp
Normal file
23
include/rfl/cbor/load.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef RFL_CBOR_LOAD_HPP_
|
||||
#define RFL_CBOR_LOAD_HPP_
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../io/load_bytes.hpp"
|
||||
#include "read.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace cbor {
|
||||
|
||||
template <class T, class... Ps>
|
||||
Result<T> load(const std::string& _fname) {
|
||||
const auto read_bytes = [](const auto& _bytes) {
|
||||
return read<T, Ps...>(_bytes);
|
||||
};
|
||||
return rfl::io::load_bytes(_fname).and_then(read_bytes);
|
||||
}
|
||||
|
||||
} // namespace cbor
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
57
include/rfl/cbor/read.hpp
Normal file
57
include/rfl/cbor/read.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef RFL_CBOR_READ_HPP_
|
||||
#define RFL_CBOR_READ_HPP_
|
||||
|
||||
#include <cbor.h>
|
||||
|
||||
#include <istream>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../internal/wrap_in_rfl_array_t.hpp"
|
||||
#include "Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace cbor {
|
||||
|
||||
using InputObjectType = typename Reader::InputObjectType;
|
||||
using InputVarType = typename Reader::InputVarType;
|
||||
|
||||
/// Parses an object from a CBOR var.
|
||||
template <class T, class... Ps>
|
||||
auto read(const InputVarType& _obj) {
|
||||
const auto r = Reader();
|
||||
return Parser<T, Processors<Ps...>>::read(r, _obj);
|
||||
}
|
||||
|
||||
/// Parses an object from CBOR using reflection.
|
||||
template <class T, class... Ps>
|
||||
Result<internal::wrap_in_rfl_array_t<T>> read(const char* _bytes,
|
||||
const size_t _size) {
|
||||
CborParser parser;
|
||||
CborValue value;
|
||||
cbor_parser_init(reinterpret_cast<const uint8_t*>(_bytes), _size, 0, &parser,
|
||||
&value);
|
||||
auto doc = InputVarType{&value};
|
||||
auto result = read<T, Ps...>(doc);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Parses an object from CBOR using reflection.
|
||||
template <class T, class... Ps>
|
||||
auto read(const std::vector<char>& _bytes) {
|
||||
return read<T, Ps...>(_bytes.data(), _bytes.size());
|
||||
}
|
||||
|
||||
/// Parses an object from a stream.
|
||||
template <class T, class... Ps>
|
||||
auto read(std::istream& _stream) {
|
||||
std::istreambuf_iterator<char> begin(_stream), end;
|
||||
auto bytes = std::vector<char>(begin, end);
|
||||
return read<T, Ps...>(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
} // namespace cbor
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
26
include/rfl/cbor/save.hpp
Normal file
26
include/rfl/cbor/save.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef RFL_CBOR_SAVE_HPP_
|
||||
#define RFL_CBOR_SAVE_HPP_
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/save_bytes.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace cbor {
|
||||
|
||||
template <class... Ps>
|
||||
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
|
||||
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
|
||||
return write<Ps...>(_obj, _stream);
|
||||
};
|
||||
return rfl::io::save_bytes(_fname, _obj, write_func);
|
||||
}
|
||||
|
||||
} // namespace cbor
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
59
include/rfl/cbor/write.hpp
Normal file
59
include/rfl/cbor/write.hpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifndef RFL_CBOR_WRITE_HPP_
|
||||
#define RFL_CBOR_WRITE_HPP_
|
||||
|
||||
#include <cbor.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../parsing/Parent.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace cbor {
|
||||
|
||||
template <class... Ps>
|
||||
void write_into_buffer(const auto& _obj, CborEncoder* _encoder,
|
||||
std::vector<char>* _buffer) noexcept {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
cbor_encoder_init(_encoder, reinterpret_cast<uint8_t*>(_buffer->data()),
|
||||
_buffer->size(), 0);
|
||||
const auto writer = Writer(_encoder);
|
||||
Parser<T, Processors<Ps...>>::write(writer, _obj,
|
||||
typename ParentType::Root{});
|
||||
}
|
||||
|
||||
/// Returns CBOR bytes.
|
||||
template <class... Ps>
|
||||
std::vector<char> write(const auto& _obj) noexcept {
|
||||
std::vector<char> buffer(4096);
|
||||
CborEncoder encoder;
|
||||
write_into_buffer<Ps...>(_obj, &encoder, &buffer);
|
||||
const auto total_bytes_needed =
|
||||
buffer.size() + cbor_encoder_get_extra_bytes_needed(&encoder);
|
||||
if (total_bytes_needed != buffer.size()) {
|
||||
buffer.resize(total_bytes_needed);
|
||||
write_into_buffer<Ps...>(_obj, &encoder, &buffer);
|
||||
}
|
||||
const auto length = cbor_encoder_get_buffer_size(
|
||||
&encoder, reinterpret_cast<uint8_t*>(buffer.data()));
|
||||
buffer.resize(length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// Writes a CBOR into an ostream.
|
||||
template <class... Ps>
|
||||
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
|
||||
auto buffer = write<Ps...>(_obj);
|
||||
_stream.write(buffer.data(), buffer.size());
|
||||
return _stream;
|
||||
}
|
||||
|
||||
} // namespace cbor
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
13
include/rfl/flexbuf.hpp
Normal file
13
include/rfl/flexbuf.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef RFL_FLEXBUF_HPP_
|
||||
#define RFL_FLEXBUF_HPP_
|
||||
|
||||
#include "../rfl.hpp"
|
||||
#include "flexbuf/Parser.hpp"
|
||||
#include "flexbuf/Reader.hpp"
|
||||
#include "flexbuf/Writer.hpp"
|
||||
#include "flexbuf/load.hpp"
|
||||
#include "flexbuf/read.hpp"
|
||||
#include "flexbuf/save.hpp"
|
||||
#include "flexbuf/write.hpp"
|
||||
|
||||
#endif
|
17
include/rfl/flexbuf/Parser.hpp
Normal file
17
include/rfl/flexbuf/Parser.hpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#ifndef FLEXBUF_PARSER_HPP_
|
||||
#define FLEXBUF_PARSER_HPP_
|
||||
|
||||
#include "../parsing/Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace flexbuf {
|
||||
|
||||
template <class T, class ProcessorsType>
|
||||
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
|
||||
|
||||
}
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
145
include/rfl/flexbuf/Reader.hpp
Normal file
145
include/rfl/flexbuf/Reader.hpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#ifndef FLEXBUF_READER_HPP_
|
||||
#define FLEXBUF_READER_HPP_
|
||||
|
||||
#include <flatbuffers/flexbuffers.h>
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace flexbuf {
|
||||
|
||||
struct Reader {
|
||||
using InputArrayType = flexbuffers::Vector;
|
||||
using InputObjectType = flexbuffers::Map;
|
||||
using InputVarType = flexbuffers::Reference;
|
||||
|
||||
template <class T, class = void>
|
||||
struct has_from_flexbuf : std::false_type {};
|
||||
|
||||
template <class T>
|
||||
struct has_from_flexbuf<
|
||||
T, std::enable_if_t<std::is_invocable_r<T, decltype(T::from_flexbuf),
|
||||
InputVarType>::value>>
|
||||
: std::true_type {};
|
||||
|
||||
template <class T>
|
||||
struct has_from_flexbuf<
|
||||
T, std::enable_if_t<std::is_invocable_r<
|
||||
rfl::Result<T>, decltype(T::from_flexbuf), InputVarType>::value>>
|
||||
: std::true_type {};
|
||||
|
||||
template <class T>
|
||||
static constexpr bool has_custom_constructor = has_from_flexbuf<T>::value;
|
||||
|
||||
rfl::Result<InputVarType> get_field(
|
||||
const std::string& _name, const InputObjectType& _obj) const noexcept {
|
||||
const auto keys = _obj.Keys();
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
if (_name == keys[i].AsString().c_str()) {
|
||||
return _obj.Values()[i];
|
||||
}
|
||||
}
|
||||
return rfl::Error("Map does not contain any element called '" + _name +
|
||||
"'.");
|
||||
}
|
||||
|
||||
bool is_empty(const InputVarType& _var) const noexcept {
|
||||
return _var.IsNull();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
if (!_var.IsString()) {
|
||||
return rfl::Error("Could not cast to string.");
|
||||
}
|
||||
return std::string(_var.AsString().c_str());
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
if (!_var.IsBool()) {
|
||||
return rfl::Error("Could not cast to boolean.");
|
||||
}
|
||||
return _var.AsBool();
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
if (!_var.IsNumeric()) {
|
||||
return rfl::Error("Could not cast to double.");
|
||||
}
|
||||
return static_cast<T>(_var.AsDouble());
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
if (!_var.IsNumeric()) {
|
||||
return rfl::Error("Could not cast to int.");
|
||||
}
|
||||
return static_cast<T>(_var.AsInt64());
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
}
|
||||
|
||||
template <class ArrayReader>
|
||||
std::optional<Error> read_array(const ArrayReader& _array_reader,
|
||||
const InputArrayType& _arr) const noexcept {
|
||||
const auto size = _arr.size();
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
const auto err = _array_reader.read(InputVarType(_arr[i]));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class ObjectReader>
|
||||
std::optional<Error> read_object(const ObjectReader& _object_reader,
|
||||
const InputObjectType& _obj) const noexcept {
|
||||
const auto keys = _obj.Keys();
|
||||
const auto values = _obj.Values();
|
||||
const auto num_values = std::min(keys.size(), values.size());
|
||||
|
||||
for (size_t i = 0; i < num_values; ++i) {
|
||||
_object_reader.read(std::string_view(keys[i].AsString().c_str()),
|
||||
values[i]);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
rfl::Result<InputArrayType> to_array(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (!_var.IsVector()) {
|
||||
return rfl::Error("Could not cast to Vector.");
|
||||
}
|
||||
return _var.AsVector();
|
||||
}
|
||||
|
||||
rfl::Result<InputObjectType> to_object(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (!_var.IsMap()) {
|
||||
return rfl::Error("Could not cast to Map!");
|
||||
}
|
||||
return _var.AsMap();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> use_custom_constructor(
|
||||
const InputVarType& _var) const noexcept {
|
||||
try {
|
||||
return T::from_flexbuf(_var);
|
||||
} catch (std::exception& e) {
|
||||
return rfl::Error(e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace flexbuf
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
176
include/rfl/flexbuf/Writer.hpp
Normal file
176
include/rfl/flexbuf/Writer.hpp
Normal file
|
@ -0,0 +1,176 @@
|
|||
#ifndef FLEXBUF_WRITER_HPP_
|
||||
#define FLEXBUF_WRITER_HPP_
|
||||
|
||||
#include <flatbuffers/flexbuffers.h>
|
||||
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace flexbuf {
|
||||
|
||||
struct Writer {
|
||||
struct OutputArray {
|
||||
size_t start_;
|
||||
};
|
||||
|
||||
struct OutputObject {
|
||||
size_t start_;
|
||||
};
|
||||
|
||||
struct OutputVar {};
|
||||
|
||||
using OutputArrayType = OutputArray;
|
||||
using OutputObjectType = OutputObject;
|
||||
using OutputVarType = OutputVar;
|
||||
|
||||
Writer(const Ref<flexbuffers::Builder>& _fbb) : fbb_(_fbb) {}
|
||||
|
||||
~Writer() = default;
|
||||
|
||||
OutputArrayType array_as_root(const size_t _size) const noexcept {
|
||||
return new_array();
|
||||
}
|
||||
|
||||
OutputObjectType object_as_root(const size_t _size) const noexcept {
|
||||
return new_object();
|
||||
}
|
||||
|
||||
OutputVarType null_as_root() const noexcept {
|
||||
fbb_->Null();
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType value_as_root(const T& _var) const noexcept {
|
||||
return insert_value(_var);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_array(const size_t _size,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return new_array();
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return new_array(_name);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_array(
|
||||
const size_t _size, OutputArrayType* _parent) const noexcept {
|
||||
return new_object();
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return new_object(_name);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_array(const T& _var,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return insert_value(_var);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_object(const std::string_view& _name,
|
||||
const T& _var,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return insert_value(_name, _var);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
|
||||
fbb_->Null();
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_object(const std::string_view& _name,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
fbb_->Null(_name.data());
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
void end_array(OutputArrayType* _arr) const noexcept {
|
||||
fbb_->EndVector(_arr->start_, false, false);
|
||||
}
|
||||
|
||||
void end_object(OutputObjectType* _obj) const noexcept {
|
||||
fbb_->EndMap(_obj->start_);
|
||||
}
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
OutputVarType insert_value(const std::string_view& _name,
|
||||
const T& _var) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
fbb_->String(_name.data(), _var);
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
fbb_->Bool(_name.data(), _var);
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
fbb_->Double(_name.data(), _var);
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
fbb_->Int(_name.data(), _var);
|
||||
} else {
|
||||
static_assert(always_false_v<T>, "Unsupported type");
|
||||
}
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType insert_value(const T& _var) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
fbb_->String(_var);
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
fbb_->Bool(_var);
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
fbb_->Double(_var);
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
fbb_->Int(_var);
|
||||
} else {
|
||||
static_assert(always_false_v<T>, "Unsupported type");
|
||||
}
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputArrayType new_array(const std::string_view& _name) const noexcept {
|
||||
const auto start = fbb_->StartVector(_name.data());
|
||||
return OutputArrayType{start};
|
||||
}
|
||||
|
||||
OutputArrayType new_array() const noexcept {
|
||||
const auto start = fbb_->StartVector();
|
||||
return OutputArrayType{start};
|
||||
}
|
||||
|
||||
OutputObjectType new_object(const std::string_view& _name) const noexcept {
|
||||
const auto start = fbb_->StartMap(_name.data());
|
||||
return OutputObjectType{start};
|
||||
}
|
||||
|
||||
OutputObjectType new_object() const noexcept {
|
||||
const auto start = fbb_->StartMap();
|
||||
return OutputObjectType{start};
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<flexbuffers::Builder> fbb_;
|
||||
};
|
||||
|
||||
} // namespace flexbuf
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
22
include/rfl/flexbuf/load.hpp
Normal file
22
include/rfl/flexbuf/load.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef RFL_FLEXBUF_LOAD_HPP_
|
||||
#define RFL_FLEXBUF_LOAD_HPP_
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/load_bytes.hpp"
|
||||
#include "read.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace flexbuf {
|
||||
|
||||
template <class T, class... Ps>
|
||||
Result<T> load(const std::string& _fname) {
|
||||
const auto read_bytes = [](const auto& _bytes) {
|
||||
return read<T, Ps...>(_bytes);
|
||||
};
|
||||
return rfl::io::load_bytes(_fname).and_then(read_bytes);
|
||||
}
|
||||
|
||||
} // namespace flexbuf
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
50
include/rfl/flexbuf/read.hpp
Normal file
50
include/rfl/flexbuf/read.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef FLEXBUF_READ_HPP_
|
||||
#define FLEXBUF_READ_HPP_
|
||||
|
||||
#include <flatbuffers/flexbuffers.h>
|
||||
|
||||
#include <istream>
|
||||
#include <vector>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace flexbuf {
|
||||
|
||||
using InputVarType = typename Reader::InputVarType;
|
||||
|
||||
/// Parses an object from flexbuf var.
|
||||
template <class T, class... Ps>
|
||||
auto read(const InputVarType& _obj) {
|
||||
const auto r = Reader();
|
||||
return Parser<T, Processors<Ps...>>::read(r, _obj);
|
||||
}
|
||||
|
||||
/// Parses an object from flexbuf using reflection.
|
||||
template <class T, class... Ps>
|
||||
auto read(const char* _bytes, const size_t _size) {
|
||||
const InputVarType root =
|
||||
flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(_bytes), _size);
|
||||
return read<T, Ps...>(root);
|
||||
}
|
||||
|
||||
/// Parses an object from flexbuf using reflection.
|
||||
template <class T, class... Ps>
|
||||
auto read(const std::vector<char>& _bytes) {
|
||||
return read<T, Ps...>(_bytes.data(), _bytes.size());
|
||||
}
|
||||
|
||||
/// Parses an object directly from a stream.
|
||||
template <class T, class... Ps>
|
||||
auto read(std::istream& _stream) {
|
||||
std::istreambuf_iterator<char> begin(_stream), end;
|
||||
const auto bytes = std::vector<char>(begin, end);
|
||||
return read<T, Ps...>(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
} // namespace flexbuf
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
26
include/rfl/flexbuf/save.hpp
Normal file
26
include/rfl/flexbuf/save.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef RFL_FLEXBUF_SAVE_HPP_
|
||||
#define RFL_FLEXBUF_SAVE_HPP_
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/save_bytes.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace flexbuf {
|
||||
|
||||
template <class... Ps>
|
||||
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
|
||||
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
|
||||
return write<Ps...>(_obj, _stream);
|
||||
};
|
||||
return rfl::io::save_bytes(_fname, _obj, write_func);
|
||||
}
|
||||
|
||||
} // namespace flexbuf
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
50
include/rfl/flexbuf/write.hpp
Normal file
50
include/rfl/flexbuf/write.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef FLEXBUF_WRITE_HPP_
|
||||
#define FLEXBUF_WRITE_HPP_
|
||||
|
||||
#include <flatbuffers/flexbuffers.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../Ref.hpp"
|
||||
#include "../parsing/Parent.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace flexbuf {
|
||||
|
||||
template <class... Ps>
|
||||
std::vector<uint8_t> to_buffer(const auto& _obj) {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
const auto fbb = Ref<flexbuffers::Builder>::make();
|
||||
auto w = Writer(fbb);
|
||||
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
|
||||
fbb->Finish();
|
||||
return fbb->GetBuffer();
|
||||
}
|
||||
|
||||
/// Writes an object to flexbuf.
|
||||
template <class... Ps>
|
||||
std::vector<char> write(const auto& _obj) {
|
||||
const auto buffer = to_buffer<Ps...>(_obj);
|
||||
const auto data = reinterpret_cast<const char*>(buffer.data());
|
||||
return std::vector<char>(data, data + buffer.size());
|
||||
}
|
||||
|
||||
/// Writes an object to an ostream.
|
||||
template <class... Ps>
|
||||
std::ostream& write(const auto& _obj, std::ostream& _stream) {
|
||||
const auto buffer = to_buffer<Ps...>(_obj);
|
||||
const auto data = reinterpret_cast<const char*>(buffer.data());
|
||||
_stream.write(data, buffer.size());
|
||||
return _stream;
|
||||
}
|
||||
|
||||
} // namespace flexbuf
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
14
include/rfl/json.hpp
Normal file
14
include/rfl/json.hpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
#ifndef RFL_JSON_HPP_
|
||||
#define RFL_JSON_HPP_
|
||||
|
||||
#include "../rfl.hpp"
|
||||
#include "json/Parser.hpp"
|
||||
#include "json/Reader.hpp"
|
||||
#include "json/Writer.hpp"
|
||||
#include "json/load.hpp"
|
||||
#include "json/read.hpp"
|
||||
#include "json/save.hpp"
|
||||
#include "json/to_schema.hpp"
|
||||
#include "json/write.hpp"
|
||||
|
||||
#endif
|
15
include/rfl/json/Parser.hpp
Normal file
15
include/rfl/json/Parser.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef RFL_JSON_PARSER_HPP_
|
||||
#define RFL_JSON_PARSER_HPP_
|
||||
|
||||
#include "../parsing/Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
|
||||
namespace rfl::json {
|
||||
|
||||
template <class T, class ProcessorsType>
|
||||
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
|
||||
|
||||
} // namespace rfl::json
|
||||
|
||||
#endif
|
154
include/rfl/json/Reader.hpp
Normal file
154
include/rfl/json/Reader.hpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
#ifndef RFL_JSON_READER_HPP_
|
||||
#define RFL_JSON_READER_HPP_
|
||||
|
||||
#include <yyjson.h>
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace json {
|
||||
|
||||
struct Reader {
|
||||
struct YYJSONInputArray {
|
||||
YYJSONInputArray(yyjson_val* _val) : val_(_val) {}
|
||||
yyjson_val* val_;
|
||||
};
|
||||
|
||||
struct YYJSONInputObject {
|
||||
YYJSONInputObject(yyjson_val* _val) : val_(_val) {}
|
||||
yyjson_val* val_;
|
||||
};
|
||||
|
||||
struct YYJSONInputVar {
|
||||
YYJSONInputVar() : val_(nullptr) {}
|
||||
YYJSONInputVar(yyjson_val* _val) : val_(_val) {}
|
||||
yyjson_val* val_;
|
||||
};
|
||||
|
||||
using InputArrayType = YYJSONInputArray;
|
||||
using InputObjectType = YYJSONInputObject;
|
||||
using InputVarType = YYJSONInputVar;
|
||||
|
||||
template <class T>
|
||||
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
|
||||
T::from_json_obj(var);
|
||||
});
|
||||
|
||||
rfl::Result<InputVarType> get_field(
|
||||
const std::string& _name, const InputObjectType _obj) const noexcept {
|
||||
const auto var = InputVarType(yyjson_obj_get(_obj.val_, _name.c_str()));
|
||||
if (!var.val_) {
|
||||
return rfl::Error("Object contains no field named '" + _name + "'.");
|
||||
}
|
||||
return var;
|
||||
}
|
||||
|
||||
bool is_empty(const InputVarType _var) const noexcept {
|
||||
return !_var.val_ || yyjson_is_null(_var.val_);
|
||||
}
|
||||
|
||||
template <class ArrayReader>
|
||||
std::optional<Error> read_array(const ArrayReader& _array_reader,
|
||||
const InputArrayType& _arr) const noexcept {
|
||||
yyjson_val* val;
|
||||
yyjson_arr_iter iter;
|
||||
yyjson_arr_iter_init(_arr.val_, &iter);
|
||||
while ((val = yyjson_arr_iter_next(&iter))) {
|
||||
const auto err = _array_reader.read(InputVarType(val));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class ObjectReader>
|
||||
std::optional<Error> read_object(const ObjectReader& _object_reader,
|
||||
const InputObjectType& _obj) const noexcept {
|
||||
yyjson_obj_iter iter;
|
||||
yyjson_obj_iter_init(_obj.val_, &iter);
|
||||
yyjson_val* key;
|
||||
while ((key = yyjson_obj_iter_next(&iter))) {
|
||||
const auto name = std::string_view(yyjson_get_str(key));
|
||||
_object_reader.read(name, InputVarType(yyjson_obj_iter_get_val(key)));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> to_basic_type(const InputVarType _var) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
const auto r = yyjson_get_str(_var.val_);
|
||||
if (r == NULL) {
|
||||
return rfl::Error("Could not cast to string.");
|
||||
}
|
||||
return std::string(r);
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
if (!yyjson_is_bool(_var.val_)) {
|
||||
return rfl::Error("Could not cast to boolean.");
|
||||
}
|
||||
return yyjson_get_bool(_var.val_);
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
if (!yyjson_is_num(_var.val_)) {
|
||||
return rfl::Error("Could not cast to double.");
|
||||
}
|
||||
return static_cast<T>(yyjson_get_num(_var.val_));
|
||||
} else if constexpr (std::is_unsigned<std::remove_cvref_t<T>>()) {
|
||||
if (!yyjson_is_int(_var.val_)) {
|
||||
return rfl::Error("Could not cast to int.");
|
||||
}
|
||||
return static_cast<T>(yyjson_get_uint(_var.val_));
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
if (!yyjson_is_int(_var.val_)) {
|
||||
return rfl::Error("Could not cast to int.");
|
||||
}
|
||||
return static_cast<T>(yyjson_get_sint(_var.val_));
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
}
|
||||
|
||||
rfl::Result<InputArrayType> to_array(const InputVarType _var) const noexcept {
|
||||
if (!yyjson_is_arr(_var.val_)) {
|
||||
return rfl::Error("Could not cast to array!");
|
||||
}
|
||||
return InputArrayType(_var.val_);
|
||||
}
|
||||
|
||||
rfl::Result<InputObjectType> to_object(
|
||||
const InputVarType _var) const noexcept {
|
||||
if (!yyjson_is_obj(_var.val_)) {
|
||||
return rfl::Error("Could not cast to object!");
|
||||
}
|
||||
return InputObjectType(_var.val_);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> use_custom_constructor(
|
||||
const InputVarType _var) const noexcept {
|
||||
try {
|
||||
return T::from_json_obj(_var);
|
||||
} catch (std::exception& e) {
|
||||
return rfl::Error(e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace json
|
||||
} // namespace rfl
|
||||
|
||||
#endif // JSON_PARSER_HPP_
|
169
include/rfl/json/Writer.hpp
Normal file
169
include/rfl/json/Writer.hpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
#ifndef RFL_JSON_WRITER_HPP_
|
||||
#define RFL_JSON_WRITER_HPP_
|
||||
|
||||
#include <yyjson.h>
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace json {
|
||||
|
||||
class Writer {
|
||||
public:
|
||||
struct YYJSONOutputArray {
|
||||
YYJSONOutputArray(yyjson_mut_val* _val) : val_(_val) {}
|
||||
yyjson_mut_val* val_;
|
||||
};
|
||||
|
||||
struct YYJSONOutputObject {
|
||||
YYJSONOutputObject(yyjson_mut_val* _val) : val_(_val) {}
|
||||
yyjson_mut_val* val_;
|
||||
};
|
||||
|
||||
struct YYJSONOutputVar {
|
||||
YYJSONOutputVar(yyjson_mut_val* _val) : val_(_val) {}
|
||||
|
||||
YYJSONOutputVar(YYJSONOutputArray _arr) : val_(_arr.val_) {}
|
||||
|
||||
YYJSONOutputVar(YYJSONOutputObject _obj) : val_(_obj.val_) {}
|
||||
|
||||
yyjson_mut_val* val_;
|
||||
};
|
||||
|
||||
using OutputArrayType = YYJSONOutputArray;
|
||||
using OutputObjectType = YYJSONOutputObject;
|
||||
using OutputVarType = YYJSONOutputVar;
|
||||
|
||||
Writer(yyjson_mut_doc* _doc) : doc_(_doc) {}
|
||||
|
||||
~Writer() = default;
|
||||
|
||||
OutputArrayType array_as_root(const size_t _size) const noexcept {
|
||||
const auto arr = yyjson_mut_arr(doc_);
|
||||
yyjson_mut_doc_set_root(doc_, arr);
|
||||
return OutputArrayType(arr);
|
||||
}
|
||||
|
||||
OutputObjectType object_as_root(const size_t _size) const noexcept {
|
||||
const auto obj = yyjson_mut_obj(doc_);
|
||||
yyjson_mut_doc_set_root(doc_, obj);
|
||||
return OutputObjectType(obj);
|
||||
}
|
||||
|
||||
OutputVarType null_as_root() const noexcept {
|
||||
const auto null = yyjson_mut_null(doc_);
|
||||
yyjson_mut_doc_set_root(doc_, null);
|
||||
return OutputVarType(null);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType value_as_root(const T& _var) const noexcept {
|
||||
const auto val = from_basic_type(_var);
|
||||
yyjson_mut_doc_set_root(doc_, val.val_);
|
||||
return OutputVarType(val);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_array(const size_t _size,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
const auto arr = yyjson_mut_arr(doc_);
|
||||
yyjson_mut_arr_add_val(_parent->val_, arr);
|
||||
return OutputArrayType(arr);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
const auto arr = yyjson_mut_arr(doc_);
|
||||
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
|
||||
arr);
|
||||
return OutputArrayType(arr);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_array(
|
||||
const size_t _size, OutputArrayType* _parent) const noexcept {
|
||||
const auto obj = yyjson_mut_obj(doc_);
|
||||
yyjson_mut_arr_add_val(_parent->val_, obj);
|
||||
return OutputObjectType(obj);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
const auto obj = yyjson_mut_obj(doc_);
|
||||
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
|
||||
obj);
|
||||
return OutputObjectType(obj);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_array(const T& _var,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
const auto val = from_basic_type(_var);
|
||||
yyjson_mut_arr_add_val(_parent->val_, val.val_);
|
||||
return OutputVarType(val);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_object(const std::string_view& _name,
|
||||
const T& _var,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
const auto val = from_basic_type(_var);
|
||||
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
|
||||
val.val_);
|
||||
return OutputVarType(val);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
|
||||
const auto null = yyjson_mut_null(doc_);
|
||||
yyjson_mut_arr_add_val(_parent->val_, null);
|
||||
return OutputVarType(null);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_object(const std::string_view& _name,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
const auto null = yyjson_mut_null(doc_);
|
||||
yyjson_mut_obj_add(_parent->val_, yyjson_mut_strcpy(doc_, _name.data()),
|
||||
null);
|
||||
return OutputVarType(null);
|
||||
}
|
||||
|
||||
void end_array(OutputArrayType* _arr) const noexcept {}
|
||||
|
||||
void end_object(OutputObjectType* _obj) const noexcept {}
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
OutputVarType from_basic_type(const T& _var) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
return OutputVarType(yyjson_mut_strcpy(doc_, _var.c_str()));
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
return OutputVarType(yyjson_mut_bool(doc_, _var));
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
return OutputVarType(yyjson_mut_real(doc_, static_cast<double>(_var)));
|
||||
} else if constexpr (std::is_unsigned<std::remove_cvref_t<T>>()) {
|
||||
return OutputVarType(yyjson_mut_uint(doc_, static_cast<uint64_t>(_var)));
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
return OutputVarType(yyjson_mut_int(doc_, static_cast<int64_t>(_var)));
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
yyjson_mut_doc* doc_;
|
||||
};
|
||||
|
||||
} // namespace json
|
||||
} // namespace rfl
|
||||
|
||||
#endif // JSON_PARSER_HPP_
|
22
include/rfl/json/load.hpp
Normal file
22
include/rfl/json/load.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef RFL_JSON_LOAD_HPP_
|
||||
#define RFL_JSON_LOAD_HPP_
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/load_string.hpp"
|
||||
#include "read.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace json {
|
||||
|
||||
template <class T, class... Ps>
|
||||
Result<T> load(const std::string& _fname) {
|
||||
const auto read_string = [](const auto& _str) {
|
||||
return read<T, Ps...>(_str);
|
||||
};
|
||||
return rfl::io::load_string(_fname).and_then(read_string);
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
52
include/rfl/json/read.hpp
Normal file
52
include/rfl/json/read.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef RFL_JSON_READ_HPP_
|
||||
#define RFL_JSON_READ_HPP_
|
||||
|
||||
#include <yyjson.h>
|
||||
|
||||
#include <istream>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../internal/wrap_in_rfl_array_t.hpp"
|
||||
#include "Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace json {
|
||||
|
||||
using InputObjectType = typename Reader::InputObjectType;
|
||||
using InputVarType = typename Reader::InputVarType;
|
||||
|
||||
/// Parses an object from a JSON var.
|
||||
template <class T, class... Ps>
|
||||
auto read(const InputVarType& _obj) {
|
||||
const auto r = Reader();
|
||||
return Parser<T, Processors<Ps...>>::read(r, _obj);
|
||||
}
|
||||
|
||||
/// Parses an object from JSON using reflection.
|
||||
template <class T, class... Ps>
|
||||
Result<internal::wrap_in_rfl_array_t<T>> read(const std::string& _json_str) {
|
||||
yyjson_doc* doc = yyjson_read(_json_str.c_str(), _json_str.size(), 0);
|
||||
if (!doc) {
|
||||
return Error("Could not parse document");
|
||||
}
|
||||
yyjson_val* root = yyjson_doc_get_root(doc);
|
||||
const auto r = Reader();
|
||||
auto res = Parser<T, Processors<Ps...>>::read(r, InputVarType(root));
|
||||
yyjson_doc_free(doc);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Parses an object from a stringstream.
|
||||
template <class T, class... Ps>
|
||||
auto read(std::istream& _stream) {
|
||||
const auto json_str = std::string(std::istreambuf_iterator<char>(_stream),
|
||||
std::istreambuf_iterator<char>());
|
||||
return read<T, Ps...>(json_str);
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
29
include/rfl/json/save.hpp
Normal file
29
include/rfl/json/save.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#ifndef RFL_JSON_SAVE_HPP_
|
||||
#define RFL_JSON_SAVE_HPP_
|
||||
|
||||
#include <yyjson.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/save_string.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace json {
|
||||
|
||||
template <class... Ps>
|
||||
Result<Nothing> save(const std::string& _fname, const auto& _obj,
|
||||
const yyjson_write_flag _flag = 0) {
|
||||
const auto write_func = [_flag](const auto& _obj, auto& _stream) -> auto& {
|
||||
return write<Ps...>(_obj, _stream, _flag);
|
||||
};
|
||||
return rfl::io::save_string(_fname, _obj, write_func);
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
25
include/rfl/json/schema/JSONSchema.hpp
Normal file
25
include/rfl/json/schema/JSONSchema.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef RFL_JSON_SCHEMA_JSONSCHEMA_HPP_
|
||||
#define RFL_JSON_SCHEMA_JSONSCHEMA_HPP_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "../../Flatten.hpp"
|
||||
#include "../../Literal.hpp"
|
||||
#include "../../Rename.hpp"
|
||||
#include "Type.hpp"
|
||||
|
||||
namespace rfl::json::schema {
|
||||
|
||||
template <class T>
|
||||
struct JSONSchema {
|
||||
Rename<"$schema", Literal<"https://json-schema.org/draft/2020-12/schema">>
|
||||
schema;
|
||||
Flatten<T> root;
|
||||
std::map<std::string, Type> definitions;
|
||||
};
|
||||
|
||||
} // namespace rfl::json::schema
|
||||
|
||||
#endif
|
147
include/rfl/json/schema/Type.hpp
Normal file
147
include/rfl/json/schema/Type.hpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
#ifndef RFL_JSON_SCHEMA_TYPE_HPP_
|
||||
#define RFL_JSON_SCHEMA_TYPE_HPP_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "../../Literal.hpp"
|
||||
#include "../../Rename.hpp"
|
||||
|
||||
namespace rfl::json::schema {
|
||||
|
||||
/// The JSON representation of internal::schema::Type.
|
||||
struct Type {
|
||||
struct Boolean {
|
||||
std::optional<std::string> description;
|
||||
Literal<"boolean"> type;
|
||||
};
|
||||
|
||||
struct Integer {
|
||||
Literal<"integer"> type;
|
||||
std::optional<std::string> description;
|
||||
};
|
||||
|
||||
struct Number {
|
||||
Literal<"number"> type;
|
||||
std::optional<std::string> description;
|
||||
};
|
||||
|
||||
struct String {
|
||||
Literal<"string"> type;
|
||||
std::optional<std::string> description;
|
||||
};
|
||||
|
||||
using NumericType = std::variant<Integer, Number>;
|
||||
|
||||
struct AllOf {
|
||||
std::optional<std::string> description;
|
||||
std::vector<Type> allOf;
|
||||
};
|
||||
|
||||
struct AnyOf {
|
||||
std::optional<std::string> description;
|
||||
std::vector<Type> anyOf;
|
||||
};
|
||||
|
||||
struct ExclusiveMaximum {
|
||||
std::optional<std::string> description;
|
||||
std::variant<double, int> exclusiveMaximum;
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct ExclusiveMinimum {
|
||||
std::optional<std::string> description;
|
||||
std::variant<double, int> exclusiveMinimum;
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct FixedSizeTypedArray {
|
||||
Literal<"array"> type;
|
||||
std::optional<std::string> description;
|
||||
rfl::Ref<Type> items;
|
||||
size_t minContains;
|
||||
size_t maxContains;
|
||||
};
|
||||
|
||||
struct Maximum {
|
||||
std::optional<std::string> description;
|
||||
std::variant<double, int> maximum;
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct Minimum {
|
||||
std::optional<std::string> description;
|
||||
std::variant<double, int> minimum;
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct Null {
|
||||
Literal<"null"> type;
|
||||
std::optional<std::string> description;
|
||||
};
|
||||
|
||||
struct Object {
|
||||
Literal<"object"> type;
|
||||
std::optional<std::string> description;
|
||||
std::map<std::string, Type> properties;
|
||||
std::vector<std::string> required;
|
||||
};
|
||||
|
||||
struct OneOf {
|
||||
std::optional<std::string> description;
|
||||
std::vector<Type> oneOf;
|
||||
};
|
||||
|
||||
struct Reference {
|
||||
Rename<"$ref", std::optional<std::string>> ref;
|
||||
std::optional<std::string> description;
|
||||
};
|
||||
|
||||
struct Regex {
|
||||
Literal<"string"> type;
|
||||
std::optional<std::string> description;
|
||||
std::string pattern;
|
||||
};
|
||||
|
||||
struct StringEnum {
|
||||
Literal<"string"> type;
|
||||
std::optional<std::string> description;
|
||||
rfl::Rename<"enum", std::vector<std::string>> values;
|
||||
};
|
||||
|
||||
struct StringMap {
|
||||
Literal<"object"> type;
|
||||
std::optional<std::string> description;
|
||||
rfl::Ref<Type> additionalProperties;
|
||||
};
|
||||
|
||||
struct Tuple {
|
||||
Literal<"array"> type;
|
||||
std::optional<std::string> description;
|
||||
std::vector<Type> prefixItems;
|
||||
bool items = false;
|
||||
};
|
||||
|
||||
struct TypedArray {
|
||||
Literal<"array"> type;
|
||||
std::optional<std::string> description;
|
||||
rfl::Ref<Type> items;
|
||||
};
|
||||
|
||||
using ReflectionType =
|
||||
std::variant<AllOf, AnyOf, Boolean, ExclusiveMaximum, ExclusiveMinimum,
|
||||
FixedSizeTypedArray, Integer, Maximum, Minimum, Number, Null,
|
||||
Object, OneOf, Reference, Regex, String, StringEnum,
|
||||
StringMap, Tuple, TypedArray>;
|
||||
|
||||
const auto& reflection() const { return value; }
|
||||
|
||||
ReflectionType value;
|
||||
};
|
||||
|
||||
} // namespace rfl::json::schema
|
||||
|
||||
#endif
|
262
include/rfl/json/to_schema.hpp
Normal file
262
include/rfl/json/to_schema.hpp
Normal file
|
@ -0,0 +1,262 @@
|
|||
#ifndef RFL_JSON_TOSCHEMA_HPP_
|
||||
#define RFL_JSON_TOSCHEMA_HPP_
|
||||
|
||||
#include <yyjson.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
#include "../Literal.hpp"
|
||||
#include "../Processors.hpp"
|
||||
#include "../parsing/schema/Type.hpp"
|
||||
#include "../parsing/schema/ValidationType.hpp"
|
||||
#include "../parsing/schema/make.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
#include "schema/JSONSchema.hpp"
|
||||
#include "schema/Type.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl::json {
|
||||
|
||||
inline schema::Type type_to_json_schema_type(
|
||||
const parsing::schema::Type& _type);
|
||||
|
||||
inline bool is_optional(const parsing::schema::Type& _t) {
|
||||
const auto handle = [](const auto& _v) -> bool {
|
||||
using T = std::remove_cvref_t<decltype(_v)>;
|
||||
return std::is_same<T, parsing::schema::Type::Optional>();
|
||||
};
|
||||
return std::visit(handle, _t.variant_);
|
||||
}
|
||||
|
||||
inline std::string numeric_type_to_string(const parsing::schema::Type& _type) {
|
||||
const auto handle_variant = [](const auto& _t) -> std::string {
|
||||
using T = std::remove_cvref_t<decltype(_t)>;
|
||||
using Type = parsing::schema::Type;
|
||||
if constexpr (std::is_same<T, Type::Int32>() ||
|
||||
std::is_same<T, Type::Int64>() ||
|
||||
std::is_same<T, Type::UInt32>() ||
|
||||
std::is_same<T, Type::UInt64>() ||
|
||||
std::is_same<T, Type::Integer>()) {
|
||||
return schema::Type::Integer{}.type.str();
|
||||
} else {
|
||||
return schema::Type::Number{}.type.str();
|
||||
}
|
||||
};
|
||||
return std::visit(handle_variant, _type.variant_);
|
||||
}
|
||||
|
||||
inline schema::Type handle_validation_type(
|
||||
const parsing::schema::Type& _type,
|
||||
const parsing::schema::ValidationType& _validation_type) {
|
||||
const auto handle_variant = [&](const auto& _v) -> schema::Type {
|
||||
using T = std::remove_cvref_t<decltype(_v)>;
|
||||
using ValidationType = parsing::schema::ValidationType;
|
||||
if constexpr (std::is_same<T, ValidationType::AllOf>()) {
|
||||
auto all_of = std::vector<schema::Type>();
|
||||
for (const auto& t : _v.types_) {
|
||||
all_of.emplace_back(handle_validation_type(_type, t));
|
||||
}
|
||||
return schema::Type{.value = schema::Type::AllOf{.allOf = all_of}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::AnyOf>()) {
|
||||
auto any_of = std::vector<schema::Type>();
|
||||
for (const auto& t : _v.types_) {
|
||||
any_of.emplace_back(handle_validation_type(_type, t));
|
||||
}
|
||||
return schema::Type{.value = schema::Type::AnyOf{.anyOf = any_of}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::OneOf>()) {
|
||||
auto one_of = std::vector<schema::Type>();
|
||||
for (const auto& t : _v.types_) {
|
||||
one_of.emplace_back(handle_validation_type(_type, t));
|
||||
}
|
||||
return schema::Type{.value = schema::Type::OneOf{.oneOf = one_of}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::Regex>()) {
|
||||
return schema::Type{.value = schema::Type::Regex{.pattern = _v.pattern_}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::Size>()) {
|
||||
return type_to_json_schema_type(_type);
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::ExclusiveMaximum>()) {
|
||||
return schema::Type{.value = schema::Type::ExclusiveMaximum{
|
||||
.exclusiveMaximum = _v.value_,
|
||||
.type = numeric_type_to_string(_type)}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::ExclusiveMinimum>()) {
|
||||
return schema::Type{.value = schema::Type::ExclusiveMinimum{
|
||||
.exclusiveMinimum = _v.value_,
|
||||
.type = numeric_type_to_string(_type)}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::Maximum>()) {
|
||||
return schema::Type{
|
||||
.value = schema::Type::Maximum{
|
||||
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::Minimum>()) {
|
||||
return schema::Type{
|
||||
.value = schema::Type::Minimum{
|
||||
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::EqualTo>()) {
|
||||
const auto maximum = schema::Type{
|
||||
.value = schema::Type::Maximum{
|
||||
.maximum = _v.value_, .type = numeric_type_to_string(_type)}};
|
||||
const auto minimum = schema::Type{
|
||||
.value = schema::Type::Minimum{
|
||||
.minimum = _v.value_, .type = numeric_type_to_string(_type)}};
|
||||
return schema::Type{.value =
|
||||
schema::Type::AllOf{.allOf = {maximum, minimum}}};
|
||||
|
||||
} else if constexpr (std::is_same<T, ValidationType::NotEqualTo>()) {
|
||||
const auto excl_maximum =
|
||||
schema::Type{.value = schema::Type::ExclusiveMaximum{
|
||||
.exclusiveMaximum = _v.value_,
|
||||
.type = numeric_type_to_string(_type)}};
|
||||
const auto excl_minimum =
|
||||
schema::Type{.value = schema::Type::ExclusiveMinimum{
|
||||
.exclusiveMinimum = _v.value_,
|
||||
.type = numeric_type_to_string(_type)}};
|
||||
return schema::Type{
|
||||
.value = schema::Type::AnyOf{.anyOf = {excl_maximum, excl_minimum}}};
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
|
||||
}
|
||||
};
|
||||
|
||||
return std::visit(handle_variant, _validation_type.variant_);
|
||||
}
|
||||
|
||||
inline schema::Type type_to_json_schema_type(
|
||||
const parsing::schema::Type& _type) {
|
||||
const auto handle_variant = [](const auto& _t) -> schema::Type {
|
||||
using T = std::remove_cvref_t<decltype(_t)>;
|
||||
using Type = parsing::schema::Type;
|
||||
if constexpr (std::is_same<T, Type::Boolean>()) {
|
||||
return schema::Type{.value = schema::Type::Boolean{}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Int32>() ||
|
||||
std::is_same<T, Type::Int64>() ||
|
||||
std::is_same<T, Type::UInt32>() ||
|
||||
std::is_same<T, Type::UInt64>() ||
|
||||
std::is_same<T, Type::Integer>()) {
|
||||
return schema::Type{.value = schema::Type::Integer{}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Float>() ||
|
||||
std::is_same<T, Type::Double>()) {
|
||||
return schema::Type{.value = schema::Type::Number{}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::String>()) {
|
||||
return schema::Type{.value = schema::Type::String{}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::AnyOf>()) {
|
||||
auto any_of = std::vector<schema::Type>();
|
||||
for (const auto& t : _t.types_) {
|
||||
any_of.emplace_back(type_to_json_schema_type(t));
|
||||
}
|
||||
return schema::Type{.value = schema::Type::AnyOf{.anyOf = any_of}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Description>()) {
|
||||
auto res = type_to_json_schema_type(*_t.type_);
|
||||
const auto update_prediction = [&](auto _v) -> schema::Type {
|
||||
_v.description = _t.description_;
|
||||
return schema::Type{_v};
|
||||
};
|
||||
return std::visit(update_prediction, res.value);
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::FixedSizeTypedArray>()) {
|
||||
return schema::Type{.value = schema::Type::FixedSizeTypedArray{
|
||||
.items = Ref<schema::Type>::make(
|
||||
type_to_json_schema_type(*_t.type_)),
|
||||
.minContains = _t.size_,
|
||||
.maxContains = _t.size_}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Literal>()) {
|
||||
return schema::Type{.value =
|
||||
schema::Type::StringEnum{.values = _t.values_}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Object>()) {
|
||||
auto properties = std::map<std::string, schema::Type>();
|
||||
auto required = std::vector<std::string>();
|
||||
for (const auto& [k, v] : _t.types_) {
|
||||
properties[k] = type_to_json_schema_type(v);
|
||||
if (!is_optional(v)) {
|
||||
required.push_back(k);
|
||||
}
|
||||
}
|
||||
return schema::Type{.value = schema::Type::Object{
|
||||
.properties = properties, .required = required}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Optional>()) {
|
||||
return schema::Type{.value = schema::Type::AnyOf{
|
||||
.anyOf = {type_to_json_schema_type(*_t.type_),
|
||||
schema::Type{schema::Type::Null{}}}}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Reference>()) {
|
||||
return schema::Type{
|
||||
.value = schema::Type::Reference{.ref = "#/definitions/" + _t.name_}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::StringMap>()) {
|
||||
return schema::Type{.value = schema::Type::StringMap{
|
||||
.additionalProperties = Ref<schema::Type>::make(
|
||||
type_to_json_schema_type(*_t.value_type_))}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Tuple>()) {
|
||||
auto items = std::vector<schema::Type>();
|
||||
for (const auto& t : _t.types_) {
|
||||
items.emplace_back(type_to_json_schema_type(t));
|
||||
}
|
||||
return schema::Type{.value = schema::Type::Tuple{.prefixItems = items}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::TypedArray>()) {
|
||||
return schema::Type{.value = schema::Type::TypedArray{
|
||||
.items = Ref<schema::Type>::make(
|
||||
type_to_json_schema_type(*_t.type_))}};
|
||||
|
||||
} else if constexpr (std::is_same<T, Type::Validated>()) {
|
||||
return handle_validation_type(*_t.type_, _t.validation_);
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
|
||||
}
|
||||
};
|
||||
|
||||
return std::visit(handle_variant, _type.variant_);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct TypeHelper {};
|
||||
|
||||
template <class... Ts>
|
||||
struct TypeHelper<std::variant<Ts...>> {
|
||||
using JSONSchemaType = std::variant<schema::JSONSchema<Ts>...>;
|
||||
};
|
||||
|
||||
/// Returns the JSON schema for a class.
|
||||
template <class T, class... Ps>
|
||||
std::string to_schema(const yyjson_write_flag _flag = 0) {
|
||||
const auto internal_schema =
|
||||
parsing::schema::make<Reader, Writer, T, Processors<Ps...>>();
|
||||
auto definitions = std::map<std::string, schema::Type>();
|
||||
for (const auto& [k, v] : internal_schema.definitions_) {
|
||||
definitions[k] = type_to_json_schema_type(v);
|
||||
}
|
||||
using JSONSchemaType =
|
||||
typename TypeHelper<schema::Type::ReflectionType>::JSONSchemaType;
|
||||
const auto to_schema = [&](auto&& _root) -> JSONSchemaType {
|
||||
using U = std::decay_t<decltype(_root)>;
|
||||
return schema::JSONSchema<U>{.root = std::move(_root),
|
||||
.definitions = definitions};
|
||||
};
|
||||
auto root = type_to_json_schema_type(internal_schema.root_);
|
||||
const auto json_schema = std::visit(to_schema, std::move(root.value));
|
||||
return write(json_schema, _flag);
|
||||
}
|
||||
} // namespace rfl::json
|
||||
|
||||
#endif
|
52
include/rfl/json/write.hpp
Normal file
52
include/rfl/json/write.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#ifndef RFL_JSON_WRITE_HPP_
|
||||
#define RFL_JSON_WRITE_HPP_
|
||||
|
||||
#include <yyjson.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../parsing/Parent.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace json {
|
||||
|
||||
/// Convenient alias for the YYJSON pretty flag
|
||||
inline constexpr yyjson_write_flag pretty = YYJSON_WRITE_PRETTY;
|
||||
|
||||
/// Returns a JSON string.
|
||||
template <class... Ps>
|
||||
std::string write(const auto& _obj, const yyjson_write_flag _flag = 0) {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
auto w = Writer(yyjson_mut_doc_new(NULL));
|
||||
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
|
||||
const char* json_c_str = yyjson_mut_write(w.doc_, _flag, NULL);
|
||||
const auto json_str = std::string(json_c_str);
|
||||
free((void*)json_c_str);
|
||||
yyjson_mut_doc_free(w.doc_);
|
||||
return json_str;
|
||||
}
|
||||
|
||||
/// Writes a JSON into an ostream.
|
||||
template <class... Ps>
|
||||
std::ostream& write(const auto& _obj, std::ostream& _stream,
|
||||
const yyjson_write_flag _flag = 0) {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
auto w = Writer(yyjson_mut_doc_new(NULL));
|
||||
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
|
||||
const char* json_c_str = yyjson_mut_write(w.doc_, _flag, NULL);
|
||||
_stream << json_c_str;
|
||||
free((void*)json_c_str);
|
||||
yyjson_mut_doc_free(w.doc_);
|
||||
return _stream;
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
} // namespace rfl
|
||||
|
||||
#endif // JSON_PARSER_HPP_
|
13
include/rfl/msgpack.hpp
Normal file
13
include/rfl/msgpack.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef RFL_MSGPACK_HPP_
|
||||
#define RFL_MSGPACK_HPP_
|
||||
|
||||
#include "../rfl.hpp"
|
||||
#include "msgpack/Parser.hpp"
|
||||
#include "msgpack/Reader.hpp"
|
||||
#include "msgpack/Writer.hpp"
|
||||
#include "msgpack/load.hpp"
|
||||
#include "msgpack/read.hpp"
|
||||
#include "msgpack/save.hpp"
|
||||
#include "msgpack/write.hpp"
|
||||
|
||||
#endif
|
46
include/rfl/msgpack/Parser.hpp
Normal file
46
include/rfl/msgpack/Parser.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef RFL_MSGPACK_PARSER_HPP_
|
||||
#define RFL_MSGPACK_PARSER_HPP_
|
||||
|
||||
#include "../parsing/Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace parsing {
|
||||
|
||||
/// msgpack-c requires us to explicitly set the number of fields in advance.
|
||||
/// Because of that, we require all of the fields and then set them to nullptr,
|
||||
/// if necessary.
|
||||
template <class ProcessorsType, class... FieldTypes>
|
||||
requires AreReaderAndWriter<msgpack::Reader, msgpack::Writer,
|
||||
NamedTuple<FieldTypes...>>
|
||||
struct Parser<msgpack::Reader, msgpack::Writer, NamedTuple<FieldTypes...>,
|
||||
ProcessorsType>
|
||||
: public NamedTupleParser<msgpack::Reader, msgpack::Writer,
|
||||
/*_ignore_empty_containers=*/false,
|
||||
/*_all_required=*/true, ProcessorsType,
|
||||
FieldTypes...> {
|
||||
};
|
||||
|
||||
template <class ProcessorsType, class... Ts>
|
||||
requires AreReaderAndWriter<msgpack::Reader, msgpack::Writer, std::tuple<Ts...>>
|
||||
struct Parser<msgpack::Reader, msgpack::Writer, std::tuple<Ts...>,
|
||||
ProcessorsType>
|
||||
: public TupleParser<msgpack::Reader, msgpack::Writer,
|
||||
/*_ignore_empty_containers=*/false,
|
||||
/*_all_required=*/true, ProcessorsType, Ts...> {
|
||||
};
|
||||
|
||||
} // namespace parsing
|
||||
} // namespace rfl
|
||||
|
||||
namespace rfl {
|
||||
namespace msgpack {
|
||||
|
||||
template <class T, class ProcessorsType>
|
||||
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
|
||||
|
||||
}
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
148
include/rfl/msgpack/Reader.hpp
Normal file
148
include/rfl/msgpack/Reader.hpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
#ifndef RFL_MSGPACK_READER_HPP_
|
||||
#define RFL_MSGPACK_READER_HPP_
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <source_location>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../Box.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace msgpack {
|
||||
|
||||
struct Reader {
|
||||
using InputArrayType = msgpack_object_array;
|
||||
using InputObjectType = msgpack_object_map;
|
||||
using InputVarType = msgpack_object;
|
||||
|
||||
template <class T>
|
||||
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
|
||||
T::from_msgpack_obj(var);
|
||||
});
|
||||
|
||||
rfl::Result<InputVarType> get_field(
|
||||
const std::string& _name, const InputObjectType& _obj) const noexcept {
|
||||
for (uint32_t i = 0; i < _obj.size; ++i) {
|
||||
const auto& key = _obj.ptr[i].key;
|
||||
if (key.type != MSGPACK_OBJECT_STR) {
|
||||
return Error("Key in element " + std::to_string(i) +
|
||||
" was not a string.");
|
||||
}
|
||||
const auto current_name =
|
||||
std::string_view(key.via.str.ptr, key.via.str.size);
|
||||
if (_name == current_name) {
|
||||
return _obj.ptr[i].val;
|
||||
}
|
||||
}
|
||||
return Error("No field named '" + _name + "' was found.");
|
||||
}
|
||||
|
||||
bool is_empty(const InputVarType& _var) const noexcept {
|
||||
return _var.type == MSGPACK_OBJECT_NIL;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
|
||||
const auto type = _var.type;
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
if (type != MSGPACK_OBJECT_STR) {
|
||||
return Error("Could not cast to string.");
|
||||
}
|
||||
const auto str = _var.via.str;
|
||||
return std::string(str.ptr, str.size);
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
if (type != MSGPACK_OBJECT_BOOLEAN) {
|
||||
return Error("Could not cast to boolean.");
|
||||
}
|
||||
return _var.via.boolean;
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
|
||||
std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
if (type == MSGPACK_OBJECT_FLOAT32 || type == MSGPACK_OBJECT_FLOAT64 ||
|
||||
type == MSGPACK_OBJECT_FLOAT) {
|
||||
return static_cast<T>(_var.via.f64);
|
||||
} else if (type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
|
||||
return static_cast<T>(_var.via.u64);
|
||||
} else if (type == MSGPACK_OBJECT_NEGATIVE_INTEGER) {
|
||||
return static_cast<T>(_var.via.i64);
|
||||
}
|
||||
return rfl::Error(
|
||||
"Could not cast to numeric value. The type must be integral, float "
|
||||
"or double.");
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
}
|
||||
|
||||
rfl::Result<InputArrayType> to_array(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (_var.type != MSGPACK_OBJECT_ARRAY) {
|
||||
return Error("Could not cast to an array.");
|
||||
}
|
||||
return _var.via.array;
|
||||
}
|
||||
|
||||
rfl::Result<InputObjectType> to_object(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (_var.type != MSGPACK_OBJECT_MAP) {
|
||||
return Error("Could not cast to a map.");
|
||||
}
|
||||
return _var.via.map;
|
||||
}
|
||||
|
||||
template <class ArrayReader>
|
||||
std::optional<Error> read_array(const ArrayReader& _array_reader,
|
||||
const InputArrayType& _arr) const noexcept {
|
||||
for (uint32_t i = 0; i < _arr.size; ++i) {
|
||||
const auto err = _array_reader.read(_arr.ptr[i]);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class ObjectReader>
|
||||
std::optional<Error> read_object(const ObjectReader& _object_reader,
|
||||
const InputObjectType& _obj) const noexcept {
|
||||
for (uint32_t i = 0; i < _obj.size; ++i) {
|
||||
const auto& key = _obj.ptr[i].key;
|
||||
const auto& val = _obj.ptr[i].val;
|
||||
if (key.type != MSGPACK_OBJECT_STR) {
|
||||
return Error("Key in element " + std::to_string(i) +
|
||||
" was not a string.");
|
||||
}
|
||||
const auto name = std::string_view(key.via.str.ptr, key.via.str.size);
|
||||
_object_reader.read(name, val);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> use_custom_constructor(
|
||||
const InputVarType& _var) const noexcept {
|
||||
try {
|
||||
return T::from_msgpack_obj(_var);
|
||||
} catch (std::exception& e) {
|
||||
return rfl::Error(e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace msgpack
|
||||
} // namespace rfl
|
||||
|
||||
#endif // JSON_PARSER_HPP_
|
154
include/rfl/msgpack/Writer.hpp
Normal file
154
include/rfl/msgpack/Writer.hpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
#ifndef RFL_MSGPACK_WRITER_HPP_
|
||||
#define RFL_MSGPACK_WRITER_HPP_
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "../Box.hpp"
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl::msgpack {
|
||||
|
||||
class Writer {
|
||||
public:
|
||||
struct MsgpackOutputArray {};
|
||||
|
||||
struct MsgpackOutputObject {};
|
||||
|
||||
struct MsgpackOutputVar {};
|
||||
|
||||
using OutputArrayType = MsgpackOutputArray;
|
||||
using OutputObjectType = MsgpackOutputObject;
|
||||
using OutputVarType = MsgpackOutputVar;
|
||||
|
||||
Writer(msgpack_packer* _pk) : pk_(_pk) {}
|
||||
|
||||
~Writer() = default;
|
||||
|
||||
OutputArrayType array_as_root(const size_t _size) const noexcept {
|
||||
return new_array(_size);
|
||||
}
|
||||
|
||||
OutputObjectType object_as_root(const size_t _size) const noexcept {
|
||||
return new_object(_size);
|
||||
}
|
||||
|
||||
OutputVarType null_as_root() const noexcept {
|
||||
msgpack_pack_nil(pk_);
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType value_as_root(const T& _var) const noexcept {
|
||||
return new_value(_var);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_array(const size_t _size,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return new_array(_size);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
msgpack_pack_str(pk_, _name.size());
|
||||
msgpack_pack_str_body(pk_, _name.data(), _name.size());
|
||||
return new_array(_size);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_array(
|
||||
const size_t _size, OutputArrayType* _parent) const noexcept {
|
||||
return new_object(_size);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
msgpack_pack_str(pk_, _name.size());
|
||||
msgpack_pack_str_body(pk_, _name.data(), _name.size());
|
||||
return new_object(_size);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_array(const T& _var,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return new_value(_var);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_object(const std::string_view& _name,
|
||||
const T& _var,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
msgpack_pack_str(pk_, _name.size());
|
||||
msgpack_pack_str_body(pk_, _name.data(), _name.size());
|
||||
return new_value(_var);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
|
||||
msgpack_pack_nil(pk_);
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_object(const std::string_view& _name,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
msgpack_pack_str(pk_, _name.size());
|
||||
msgpack_pack_str_body(pk_, _name.data(), _name.size());
|
||||
msgpack_pack_nil(pk_);
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
void end_array(OutputArrayType* _arr) const noexcept {}
|
||||
|
||||
void end_object(OutputObjectType* _obj) const noexcept {}
|
||||
|
||||
private:
|
||||
OutputArrayType new_array(const size_t _size) const noexcept {
|
||||
msgpack_pack_array(pk_, _size);
|
||||
return OutputArrayType{};
|
||||
}
|
||||
|
||||
OutputObjectType new_object(const size_t _size) const noexcept {
|
||||
msgpack_pack_map(pk_, _size);
|
||||
return OutputObjectType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType new_value(const T& _var) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
msgpack_pack_str(pk_, _var.size());
|
||||
msgpack_pack_str_body(pk_, _var.c_str(), _var.size());
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
if (_var) {
|
||||
msgpack_pack_true(pk_);
|
||||
} else {
|
||||
msgpack_pack_false(pk_);
|
||||
}
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
msgpack_pack_double(pk_, static_cast<double>(_var));
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
msgpack_pack_int64(pk_, static_cast<std::int64_t>(_var));
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
private:
|
||||
/// The underlying packer.
|
||||
msgpack_packer* pk_;
|
||||
};
|
||||
|
||||
} // namespace rfl::msgpack
|
||||
|
||||
#endif // MSGPACK_PARSER_HPP_
|
22
include/rfl/msgpack/load.hpp
Normal file
22
include/rfl/msgpack/load.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef RFL_MSGPACK_LOAD_HPP_
|
||||
#define RFL_MSGPACK_LOAD_HPP_
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/load_bytes.hpp"
|
||||
#include "read.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace msgpack {
|
||||
|
||||
template <class T, class... Ps>
|
||||
Result<T> load(const std::string& _fname) {
|
||||
const auto read_bytes = [](const auto& _bytes) {
|
||||
return read<T, Ps...>(_bytes);
|
||||
};
|
||||
return rfl::io::load_bytes(_fname).and_then(read_bytes);
|
||||
}
|
||||
|
||||
} // namespace msgpack
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
57
include/rfl/msgpack/read.hpp
Normal file
57
include/rfl/msgpack/read.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef RFL_MSGPACK_READ_HPP_
|
||||
#define RFL_MSGPACK_READ_HPP_
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include <istream>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../internal/wrap_in_rfl_array_t.hpp"
|
||||
#include "Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace msgpack {
|
||||
|
||||
using InputObjectType = typename Reader::InputObjectType;
|
||||
using InputVarType = typename Reader::InputVarType;
|
||||
|
||||
/// Parses an object from a MSGPACK var.
|
||||
template <class T, class... Ps>
|
||||
auto read(const InputVarType& _obj) {
|
||||
const auto r = Reader();
|
||||
return Parser<T, Processors<Ps...>>::read(r, _obj);
|
||||
}
|
||||
|
||||
/// Parses an object from MSGPACK using reflection.
|
||||
template <class T, class... Ps>
|
||||
Result<internal::wrap_in_rfl_array_t<T>> read(const char* _bytes,
|
||||
const size_t _size) {
|
||||
msgpack_zone mempool;
|
||||
msgpack_zone_init(&mempool, 2048);
|
||||
msgpack_object deserialized;
|
||||
msgpack_unpack(_bytes, _size, NULL, &mempool, &deserialized);
|
||||
auto r = read<T, Ps...>(deserialized);
|
||||
msgpack_zone_destroy(&mempool);
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Parses an object from MSGPACK using reflection.
|
||||
template <class T, class... Ps>
|
||||
auto read(const std::vector<char>& _bytes) {
|
||||
return read<T, Ps...>(_bytes.data(), _bytes.size());
|
||||
}
|
||||
|
||||
/// Parses an object from a stream.
|
||||
template <class T, class... Ps>
|
||||
auto read(std::istream& _stream) {
|
||||
std::istreambuf_iterator<char> begin(_stream), end;
|
||||
auto bytes = std::vector<char>(begin, end);
|
||||
return read<T, Ps...>(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
} // namespace msgpack
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
26
include/rfl/msgpack/save.hpp
Normal file
26
include/rfl/msgpack/save.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef RFL_MSGPACK_SAVE_HPP_
|
||||
#define RFL_MSGPACK_SAVE_HPP_
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/save_bytes.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace msgpack {
|
||||
|
||||
template <class... Ps>
|
||||
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
|
||||
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
|
||||
return write<Ps...>(_obj, _stream);
|
||||
};
|
||||
return rfl::io::save_bytes(_fname, _obj, write_func);
|
||||
}
|
||||
|
||||
} // namespace msgpack
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
44
include/rfl/msgpack/write.hpp
Normal file
44
include/rfl/msgpack/write.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef RFL_MSGPACK_WRITE_HPP_
|
||||
#define RFL_MSGPACK_WRITE_HPP_
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../parsing/Parent.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl::msgpack {
|
||||
|
||||
/// Returns msgpack bytes.
|
||||
template <class... Ps>
|
||||
std::vector<char> write(const auto& _obj) noexcept {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
msgpack_sbuffer sbuf;
|
||||
msgpack_sbuffer_init(&sbuf);
|
||||
msgpack_packer pk;
|
||||
msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
|
||||
auto w = Writer(&pk);
|
||||
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
|
||||
auto bytes = std::vector<char>(sbuf.data, sbuf.data + sbuf.size);
|
||||
msgpack_sbuffer_destroy(&sbuf);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// Writes a MSGPACK into an ostream.
|
||||
template <class... Ps>
|
||||
std::ostream& write(const auto& _obj, std::ostream& _stream) noexcept {
|
||||
auto buffer = write<Ps...>(_obj);
|
||||
_stream.write(buffer.data(), buffer.size());
|
||||
return _stream;
|
||||
}
|
||||
|
||||
} // namespace rfl::msgpack
|
||||
|
||||
#endif
|
|
@ -6,6 +6,7 @@
|
|||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "../NamedTuple.hpp"
|
||||
#include "../Result.hpp"
|
||||
|
@ -59,7 +60,7 @@ struct NamedTupleParser {
|
|||
using ViewType = std::remove_cvref_t<decltype(view)>;
|
||||
const auto err =
|
||||
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
|
||||
if (err) {
|
||||
if (err) [[unlikely]] {
|
||||
return *err;
|
||||
}
|
||||
return *ptr;
|
||||
|
@ -69,11 +70,11 @@ struct NamedTupleParser {
|
|||
static std::optional<Error> read_view(
|
||||
const R& _r, const InputVarType& _var,
|
||||
NamedTuple<FieldTypes...>* _view) noexcept {
|
||||
const auto obj = _r.to_object(_var);
|
||||
if (obj) {
|
||||
return read_object(_r, *obj, _view);
|
||||
auto obj = _r.to_object(_var);
|
||||
if (!obj) [[unlikely]] {
|
||||
return obj.error();
|
||||
}
|
||||
return obj.error();
|
||||
return read_object(_r, *obj, _view);
|
||||
}
|
||||
|
||||
/// For writing, we do not need to make the distinction between
|
||||
|
@ -147,38 +148,43 @@ struct NamedTupleParser {
|
|||
}
|
||||
|
||||
/// Generates error messages for when fields are missing.
|
||||
template <size_t _i = 0>
|
||||
static void handle_missing_fields(const std::array<bool, size_>& _found,
|
||||
const NamedTupleType& _view,
|
||||
std::array<bool, size_>* _set,
|
||||
std::vector<Error>* _errors) noexcept {
|
||||
if constexpr (_i < sizeof...(FieldTypes)) {
|
||||
using FieldType =
|
||||
std::tuple_element_t<_i, typename NamedTupleType::Fields>;
|
||||
using ValueType = std::remove_reference_t<
|
||||
std::remove_pointer_t<typename FieldType::Type>>;
|
||||
template <int _i>
|
||||
static void handle_one_missing_field(const std::array<bool, size_>& _found,
|
||||
const NamedTupleType& _view,
|
||||
std::array<bool, size_>* _set,
|
||||
std::vector<Error>* _errors) noexcept {
|
||||
using FieldType = std::tuple_element_t<_i, typename NamedTupleType::Fields>;
|
||||
using ValueType = std::remove_reference_t<
|
||||
std::remove_pointer_t<typename FieldType::Type>>;
|
||||
|
||||
if (!std::get<_i>(_found)) {
|
||||
if constexpr (_all_required ||
|
||||
is_required<ValueType, _ignore_empty_containers>()) {
|
||||
constexpr auto current_name =
|
||||
std::tuple_element_t<_i, typename NamedTupleType::Fields>::name();
|
||||
_errors->push_back("Field named '" + std::string(current_name) +
|
||||
"' not found.");
|
||||
if (!std::get<_i>(_found)) {
|
||||
if constexpr (_all_required ||
|
||||
is_required<ValueType, _ignore_empty_containers>()) {
|
||||
constexpr auto current_name =
|
||||
std::tuple_element_t<_i, typename NamedTupleType::Fields>::name();
|
||||
_errors->emplace_back(Error(
|
||||
"Field named '" + std::string(current_name) + "' not found."));
|
||||
} else {
|
||||
if constexpr (!std::is_const_v<ValueType>) {
|
||||
::new (rfl::get<_i>(_view)) ValueType();
|
||||
} else {
|
||||
if constexpr (!std::is_const_v<ValueType>) {
|
||||
::new (rfl::get<_i>(_view)) ValueType();
|
||||
} else {
|
||||
using NonConstT = std::remove_const_t<ValueType>;
|
||||
::new (const_cast<NonConstT*>(rfl::get<_i>(_view))) NonConstT();
|
||||
}
|
||||
std::get<_i>(*_set) = true;
|
||||
using NonConstT = std::remove_const_t<ValueType>;
|
||||
::new (const_cast<NonConstT*>(rfl::get<_i>(_view))) NonConstT();
|
||||
}
|
||||
std::get<_i>(*_set) = true;
|
||||
}
|
||||
handle_missing_fields<_i + 1>(_found, _view, _set, _errors);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates error messages for when fields are missing.
|
||||
template <int... _is>
|
||||
static void handle_missing_fields(
|
||||
const std::array<bool, size_>& _found, const NamedTupleType& _view,
|
||||
std::array<bool, size_>* _set, std::vector<Error>* _errors,
|
||||
std::integer_sequence<int, _is...>) noexcept {
|
||||
(handle_one_missing_field<_is>(_found, _view, _set, _errors), ...);
|
||||
}
|
||||
|
||||
static std::optional<Error> read_object(const R& _r,
|
||||
const InputObjectType& _obj,
|
||||
NamedTupleType* _view) noexcept {
|
||||
|
@ -193,7 +199,8 @@ struct NamedTupleParser {
|
|||
if (err) {
|
||||
return *err;
|
||||
}
|
||||
handle_missing_fields(found, *_view, &set, &errors);
|
||||
handle_missing_fields(found, *_view, &set, &errors,
|
||||
std::make_integer_sequence<int, size_>());
|
||||
if (errors.size() != 0) {
|
||||
object_reader.call_destructors_where_necessary();
|
||||
return to_single_error_message(errors);
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
#include "../internal/is_validator.hpp"
|
||||
#include "../internal/processed_t.hpp"
|
||||
#include "../internal/to_ptr_named_tuple.hpp"
|
||||
#include "../to_view.hpp"
|
||||
#include "../type_name_t.hpp"
|
||||
#include "AreReaderAndWriter.hpp"
|
||||
#include "Parent.hpp"
|
||||
#include "Parser_base.hpp"
|
||||
#include "StructReader.hpp"
|
||||
#include "is_tagged_union_wrapper.hpp"
|
||||
#include "schema/Type.hpp"
|
||||
|
||||
|
@ -55,19 +55,13 @@ struct Parser {
|
|||
return Parser<R, W, ReflectionType, ProcessorsType>::read(_r, _var)
|
||||
.and_then(wrap_in_t);
|
||||
} else if constexpr (std::is_class_v<T> && std::is_aggregate_v<T>) {
|
||||
return StructReader<R, W, T, ProcessorsType>::read(_r, _var);
|
||||
return read_struct(_r, _var);
|
||||
} else if constexpr (std::is_enum_v<T>) {
|
||||
using StringConverter = internal::enums::StringConverter<T>;
|
||||
return _r.template to_basic_type<std::string>(_var).and_then(
|
||||
StringConverter::string_to_enum);
|
||||
} else if constexpr (internal::is_basic_type_v<T>) {
|
||||
return _r.template to_basic_type<std::remove_cvref_t<T>>(_var);
|
||||
} else {
|
||||
static_assert(
|
||||
always_false_v<T>,
|
||||
"Unsupported type. Please refer to the sections on custom "
|
||||
"classes and custom parsers for information on how add "
|
||||
"support for your own classes.");
|
||||
return _r.template to_basic_type<std::remove_cvref_t<T>>(_var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,13 +87,8 @@ struct Parser {
|
|||
using StringConverter = internal::enums::StringConverter<T>;
|
||||
const auto str = StringConverter::enum_to_string(_var);
|
||||
ParentType::add_value(_w, str, _parent);
|
||||
} else if constexpr (internal::is_basic_type_v<T>) {
|
||||
ParentType::add_value(_w, _var, _parent);
|
||||
} else {
|
||||
static_assert(always_false_v<T>,
|
||||
"Unsupported type. Please refer to the sections on custom "
|
||||
"classes and custom parsers for information on how add "
|
||||
"support for your own classes.");
|
||||
ParentType::add_value(_w, _var, _parent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,6 +211,23 @@ struct Parser {
|
|||
}
|
||||
}
|
||||
|
||||
/// The way this works is that we allocate space on the stack in this size of
|
||||
/// the struct in which we then write the individual fields using
|
||||
/// views and placement new. This is how we deal with the fact that some
|
||||
/// fields might not be default-constructible.
|
||||
static Result<T> read_struct(const R& _r, const InputVarType& _var) {
|
||||
alignas(T) unsigned char buf[sizeof(T)];
|
||||
auto ptr = reinterpret_cast<T*>(buf);
|
||||
auto view = ProcessorsType::template process<T>(to_view(*ptr));
|
||||
using ViewType = std::remove_cvref_t<decltype(view)>;
|
||||
const auto err =
|
||||
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
|
||||
if (err) [[unlikely]] {
|
||||
return *err;
|
||||
}
|
||||
return std::move(*ptr);
|
||||
}
|
||||
|
||||
static std::string replace_non_alphanumeric(std::string _str) {
|
||||
for (auto& ch : _str) {
|
||||
ch = std::isalnum(ch) ? ch : '_';
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
#ifndef RFL_PARSING_STRUCTREADER_HPP_
|
||||
#define RFL_PARSING_STRUCTREADER_HPP_
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../to_view.hpp"
|
||||
#include "AreReaderAndWriter.hpp"
|
||||
|
||||
namespace rfl::parsing {
|
||||
|
||||
template <class R, class W, class StructType, class ProcessorsType>
|
||||
requires AreReaderAndWriter<R, W, StructType>
|
||||
struct StructReader {
|
||||
public:
|
||||
using InputVarType = typename R::InputVarType;
|
||||
|
||||
/// The way this works is that we allocate space on the stack in this size of
|
||||
/// the struct in which we then write the individual fields using
|
||||
/// views and placement new. This is how we deal with the fact that some
|
||||
/// fields might not be default-constructible.
|
||||
static Result<StructType> read(const R& _r, const InputVarType& _var) {
|
||||
alignas(StructType) unsigned char buf[sizeof(StructType)];
|
||||
auto ptr = reinterpret_cast<StructType*>(buf);
|
||||
auto view = ProcessorsType::template process<StructType>(to_view(*ptr));
|
||||
using ViewType = std::remove_cvref_t<decltype(view)>;
|
||||
const auto err =
|
||||
Parser<R, W, ViewType, ProcessorsType>::read_view(_r, _var, &view);
|
||||
if (err) {
|
||||
return *err;
|
||||
}
|
||||
return std::move(*ptr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rfl::parsing
|
||||
|
||||
#endif
|
|
@ -4,6 +4,7 @@
|
|||
#include <array>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
|
@ -24,63 +25,68 @@ class ViewReader {
|
|||
|
||||
~ViewReader() = default;
|
||||
|
||||
template <size_t _i = 0>
|
||||
/// Assigns the parsed version of _var to the field signified by _name, if
|
||||
/// such a field exists in the underlying view.
|
||||
void read(const std::string_view& _name, const InputVarType& _var) const {
|
||||
if constexpr (_i < size_) {
|
||||
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>;
|
||||
using OriginalType = std::remove_cvref_t<typename FieldType::Type>;
|
||||
using CurrentType =
|
||||
std::remove_cvref_t<std::remove_pointer_t<OriginalType>>;
|
||||
constexpr auto current_name = FieldType::name();
|
||||
if (!std::get<_i>(*found_) && _name == current_name) {
|
||||
auto res = Parser<R, W, CurrentType, ProcessorsType>::read(*r_, _var);
|
||||
if (res) {
|
||||
if constexpr (std::is_pointer_v<OriginalType>) {
|
||||
move_to(rfl::get<_i>(*view_), &(*res));
|
||||
} else {
|
||||
rfl::get<_i>(*view_) = *res;
|
||||
}
|
||||
std::get<_i>(*set_) = true;
|
||||
} else {
|
||||
errors_->push_back(Error("Failed to parse field '" +
|
||||
std::string(current_name) +
|
||||
"': " + res.error()->what()));
|
||||
}
|
||||
std::get<_i>(*found_) = true;
|
||||
return;
|
||||
}
|
||||
read<_i + 1>(_name, _var);
|
||||
}
|
||||
assign_to_matching_field(*r_, _name, _var, view_, errors_, found_, set_,
|
||||
std::make_integer_sequence<int, size_>());
|
||||
}
|
||||
|
||||
/// Because of the way we have allocated the fields, we need to manually
|
||||
/// trigger the destructors.
|
||||
template <size_t _i = 0>
|
||||
void call_destructors_where_necessary() const {
|
||||
if constexpr (_i < size_) {
|
||||
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>;
|
||||
using OriginalType = std::remove_cvref_t<typename FieldType::Type>;
|
||||
using ValueType =
|
||||
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
|
||||
if constexpr (!std::is_array_v<ValueType> &&
|
||||
std::is_pointer_v<OriginalType> &&
|
||||
std::is_destructible_v<ValueType>) {
|
||||
if (std::get<_i>(*set_)) {
|
||||
rfl::get<_i>(*view_)->~ValueType();
|
||||
}
|
||||
} else if constexpr (std::is_array_v<ValueType>) {
|
||||
if (std::get<_i>(*set_)) {
|
||||
auto ptr = rfl::get<_i>(*view_);
|
||||
call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr);
|
||||
}
|
||||
}
|
||||
call_destructors_where_necessary<_i + 1>();
|
||||
[&]<int... is>(std::integer_sequence<int, is...>) {
|
||||
(call_destructor_on_one_if_necessary<is>(), ...);
|
||||
}
|
||||
(std::make_integer_sequence<int, size_>());
|
||||
}
|
||||
|
||||
private:
|
||||
template <int i>
|
||||
static void assign_if_field_matches(const R& _r,
|
||||
const std::string_view& _current_name,
|
||||
const auto& _var, auto* _view,
|
||||
auto* _errors, auto* _found, auto* _set,
|
||||
bool* _already_assigned) {
|
||||
using FieldType = std::tuple_element_t<i, typename ViewType::Fields>;
|
||||
using OriginalType = typename FieldType::Type;
|
||||
using T =
|
||||
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
|
||||
constexpr auto name = FieldType::name();
|
||||
if (!(*_already_assigned) && !std::get<i>(*_found) &&
|
||||
_current_name == name) {
|
||||
std::get<i>(*_found) = true;
|
||||
*_already_assigned = true;
|
||||
auto res = Parser<R, W, T, ProcessorsType>::read(_r, _var);
|
||||
if (!res) {
|
||||
_errors->emplace_back(Error("Failed to parse field '" +
|
||||
std::string(name) +
|
||||
"': " + std::move(res.error()->what())));
|
||||
return;
|
||||
}
|
||||
if constexpr (std::is_pointer_v<OriginalType>) {
|
||||
move_to(rfl::get<i>(*_view), &(*res));
|
||||
} else {
|
||||
rfl::get<i>(*_view) = std::move(*res);
|
||||
}
|
||||
std::get<i>(*_set) = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <int... is>
|
||||
static void assign_to_matching_field(const R& _r,
|
||||
const std::string_view& _current_name,
|
||||
const auto& _var, auto* _view,
|
||||
auto* _errors, auto* _found, auto* _set,
|
||||
std::integer_sequence<int, is...>) {
|
||||
bool already_assigned = false;
|
||||
(assign_if_field_matches<is>(_r, _current_name, _var, _view, _errors,
|
||||
_found, _set, &already_assigned),
|
||||
...);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void call_destructor_on_array(const size_t _size, T* _ptr) const {
|
||||
static void call_destructor_on_array(const size_t _size, T* _ptr) {
|
||||
for (size_t i = 0; i < _size; ++i) {
|
||||
if constexpr (std::is_array_v<T>) {
|
||||
call_destructor_on_array(sizeof(*_ptr) / sizeof(**_ptr), *(_ptr + i));
|
||||
|
@ -90,14 +96,34 @@ class ViewReader {
|
|||
}
|
||||
}
|
||||
|
||||
template <int _i>
|
||||
void call_destructor_on_one_if_necessary() const {
|
||||
using FieldType = std::tuple_element_t<_i, typename ViewType::Fields>;
|
||||
using OriginalType = std::remove_cvref_t<typename FieldType::Type>;
|
||||
using ValueType =
|
||||
std::remove_cvref_t<std::remove_pointer_t<typename FieldType::Type>>;
|
||||
if constexpr (!std::is_array_v<ValueType> &&
|
||||
std::is_pointer_v<OriginalType> &&
|
||||
std::is_destructible_v<ValueType>) {
|
||||
if (std::get<_i>(*set_)) {
|
||||
rfl::get<_i>(*view_)->~ValueType();
|
||||
}
|
||||
} else if constexpr (std::is_array_v<ValueType>) {
|
||||
if (std::get<_i>(*set_)) {
|
||||
auto ptr = rfl::get<_i>(*view_);
|
||||
call_destructor_on_array(sizeof(*ptr) / sizeof(**ptr), *ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Target, class Source>
|
||||
void move_to(Target* _t, Source* _s) const {
|
||||
static void move_to(Target* _t, Source* _s) {
|
||||
if constexpr (std::is_const_v<Target>) {
|
||||
return move_to(const_cast<std::remove_const_t<Target>*>(_t), _s);
|
||||
} else if constexpr (!internal::is_array_v<Source> &&
|
||||
} else if constexpr (!rfl::internal::is_array_v<Source> &&
|
||||
!std::is_array_v<Target>) {
|
||||
::new (_t) Target(std::move(*_s));
|
||||
} else if constexpr (internal::is_array_v<Source>) {
|
||||
} else if constexpr (rfl::internal::is_array_v<Source>) {
|
||||
static_assert(std::is_array_v<Target>,
|
||||
"Expected target to be a c-array.");
|
||||
for (size_t i = 0; i < _s->arr_.size(); ++i) {
|
||||
|
|
13
include/rfl/xml.hpp
Normal file
13
include/rfl/xml.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef RFL_XML_HPP_
|
||||
#define RFL_XML_HPP_
|
||||
|
||||
#include "../rfl.hpp"
|
||||
#include "xml/Parser.hpp"
|
||||
#include "xml/Reader.hpp"
|
||||
#include "xml/Writer.hpp"
|
||||
#include "xml/load.hpp"
|
||||
#include "xml/read.hpp"
|
||||
#include "xml/save.hpp"
|
||||
#include "xml/write.hpp"
|
||||
|
||||
#endif
|
40
include/rfl/xml/Parser.hpp
Normal file
40
include/rfl/xml/Parser.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifndef RFL_XML_PARSER_HPP_
|
||||
#define RFL_XML_PARSER_HPP_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "../internal/is_attribute.hpp"
|
||||
#include "../parsing/NamedTupleParser.hpp"
|
||||
#include "../parsing/Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace parsing {
|
||||
|
||||
/// XML is very special. It doesn't have proper support for arrays, which means
|
||||
/// that we just need to ignore empty containers. Therefore, we need to a
|
||||
/// template specialization for the NamedTuple parser to accommodate for it.
|
||||
template <class ProcessorsType, class... FieldTypes>
|
||||
requires AreReaderAndWriter<xml::Reader, xml::Writer, NamedTuple<FieldTypes...>>
|
||||
struct Parser<xml::Reader, xml::Writer, NamedTuple<FieldTypes...>,
|
||||
ProcessorsType>
|
||||
: public NamedTupleParser<xml::Reader, xml::Writer,
|
||||
/*_ignore_empty_containers=*/true,
|
||||
/*_all_required=*/ProcessorsType::all_required_,
|
||||
ProcessorsType, FieldTypes...> {
|
||||
};
|
||||
|
||||
} // namespace parsing
|
||||
} // namespace rfl
|
||||
|
||||
namespace rfl {
|
||||
namespace xml {
|
||||
|
||||
template <class T, class ProcessorsType>
|
||||
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
|
||||
|
||||
}
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
174
include/rfl/xml/Reader.hpp
Normal file
174
include/rfl/xml/Reader.hpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#ifndef RFL_XML_READER_HPP_
|
||||
#define RFL_XML_READER_HPP_
|
||||
|
||||
#include <array>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <pugixml.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
#include "../parsing/is_view_reader.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace xml {
|
||||
|
||||
struct Reader {
|
||||
struct XMLInputArray {
|
||||
XMLInputArray(pugi::xml_node _node) : node_(_node) {}
|
||||
pugi::xml_node node_;
|
||||
};
|
||||
|
||||
struct XMLInputObject {
|
||||
XMLInputObject(pugi::xml_node _node) : node_(_node) {}
|
||||
pugi::xml_node node_;
|
||||
};
|
||||
|
||||
struct XMLInputVar {
|
||||
XMLInputVar() : node_or_attribute_(pugi::xml_node()) {}
|
||||
XMLInputVar(pugi::xml_attribute _attr) : node_or_attribute_(_attr) {}
|
||||
XMLInputVar(pugi::xml_node _node) : node_or_attribute_(_node) {}
|
||||
std::variant<pugi::xml_node, pugi::xml_attribute> node_or_attribute_;
|
||||
};
|
||||
|
||||
using InputArrayType = XMLInputArray;
|
||||
using InputObjectType = XMLInputObject;
|
||||
using InputVarType = XMLInputVar;
|
||||
|
||||
// TODO
|
||||
template <class T>
|
||||
static constexpr bool has_custom_constructor = false;
|
||||
|
||||
/// XML-only helper function. This is needed because XML distinguishes between
|
||||
/// nodes and attributes.
|
||||
static rfl::Result<pugi::xml_node> cast_as_node(
|
||||
const std::variant<pugi::xml_node, pugi::xml_attribute>&
|
||||
_node_or_attribute) {
|
||||
const auto cast = [](const auto& _n) -> Result<pugi::xml_node> {
|
||||
using Type = std::remove_cvref_t<decltype(_n)>;
|
||||
if constexpr (std::is_same<Type, pugi::xml_node>()) {
|
||||
return _n;
|
||||
} else {
|
||||
return Error("Field '" + std::string(_n.name()) + "' is an attribute.");
|
||||
}
|
||||
};
|
||||
return std::visit(cast, _node_or_attribute);
|
||||
}
|
||||
|
||||
rfl::Result<InputVarType> get_field(
|
||||
const std::string& _name, const InputObjectType _obj) const noexcept {
|
||||
const auto node = _obj.node_.child(_name.c_str());
|
||||
if (!node) {
|
||||
return rfl::Error("Object contains no field named '" + _name + "'.");
|
||||
}
|
||||
return InputVarType(node);
|
||||
}
|
||||
|
||||
bool is_empty(const InputVarType _var) const noexcept {
|
||||
const auto wrap = [](const auto& _node) { return !_node; };
|
||||
return std::visit(cast_as_node, _var.node_or_attribute_)
|
||||
.transform(wrap)
|
||||
.value_or(false);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> to_basic_type(const InputVarType _var) const noexcept {
|
||||
const auto get_value = [](const auto& _n) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_n)>;
|
||||
if constexpr (std::is_same<Type, pugi::xml_node>()) {
|
||||
return std::string(_n.child_value());
|
||||
} else {
|
||||
return std::string(_n.value());
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
return std::visit(get_value, _var.node_or_attribute_);
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
return std::visit(get_value, _var.node_or_attribute_) == "true";
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
|
||||
const auto str = std::visit(get_value, _var.node_or_attribute_);
|
||||
try {
|
||||
return static_cast<T>(std::stod(str));
|
||||
} catch (std::exception& e) {
|
||||
return Error("Could not cast '" + std::string(str) +
|
||||
"' to floating point value.");
|
||||
}
|
||||
} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
const auto str = std::visit(get_value, _var.node_or_attribute_);
|
||||
try {
|
||||
return static_cast<T>(std::stoi(str));
|
||||
} catch (std::exception& e) {
|
||||
return Error("Could not cast '" + std::string(str) + "' to integer.");
|
||||
}
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
}
|
||||
|
||||
rfl::Result<InputArrayType> to_array(const InputVarType _var) const noexcept {
|
||||
const auto wrap = [](const auto& _node) { return InputArrayType(_node); };
|
||||
return std::visit(cast_as_node, _var.node_or_attribute_).transform(wrap);
|
||||
}
|
||||
|
||||
template <class ArrayReader>
|
||||
std::optional<Error> read_array(const ArrayReader& _array_reader,
|
||||
const InputArrayType& _arr) const noexcept {
|
||||
const auto name = _arr.node_.name();
|
||||
for (auto node = _arr.node_; node; node = node.next_sibling(name)) {
|
||||
const auto err = _array_reader.read(InputVarType(node));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class ObjectReader>
|
||||
std::optional<Error> read_object(const ObjectReader& _object_reader,
|
||||
const InputObjectType& _obj) const noexcept {
|
||||
for (auto child = _obj.node_.first_child(); child;
|
||||
child = child.next_sibling()) {
|
||||
_object_reader.read(std::string_view(child.name()), InputVarType(child));
|
||||
}
|
||||
|
||||
for (auto attr = _obj.node_.first_attribute(); attr;
|
||||
attr = attr.next_attribute()) {
|
||||
_object_reader.read(std::string_view(attr.name()), InputVarType(attr));
|
||||
}
|
||||
|
||||
if constexpr (parsing::is_view_reader_v<ObjectReader>) {
|
||||
_object_reader.read(std::string_view("xml_content"),
|
||||
InputVarType(_obj.node_));
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
rfl::Result<InputObjectType> to_object(
|
||||
const InputVarType _var) const noexcept {
|
||||
const auto wrap = [](const auto& _node) { return InputObjectType(_node); };
|
||||
return std::visit(cast_as_node, _var.node_or_attribute_).transform(wrap);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> use_custom_constructor(
|
||||
const InputVarType _var) const noexcept {
|
||||
return rfl::Error("TODO");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
181
include/rfl/xml/Writer.hpp
Normal file
181
include/rfl/xml/Writer.hpp
Normal file
|
@ -0,0 +1,181 @@
|
|||
#ifndef RFL_XML_WRITER_HPP_
|
||||
#define RFL_XML_WRITER_HPP_
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <pugixml.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace xml {
|
||||
|
||||
struct Writer {
|
||||
static constexpr const char* XML_CONTENT = "xml_content";
|
||||
|
||||
struct XMLOutputArray {
|
||||
XMLOutputArray(const std::string_view& _name,
|
||||
const Ref<pugi::xml_node>& _node)
|
||||
: name_(_name), node_(_node) {}
|
||||
std::string_view name_;
|
||||
Ref<pugi::xml_node> node_;
|
||||
};
|
||||
|
||||
struct XMLOutputObject {
|
||||
XMLOutputObject(const Ref<pugi::xml_node>& _node) : node_(_node) {}
|
||||
Ref<pugi::xml_node> node_;
|
||||
};
|
||||
|
||||
struct XMLOutputVar {
|
||||
XMLOutputVar(const Ref<pugi::xml_node>& _node) : node_(_node) {}
|
||||
Ref<pugi::xml_node> node_;
|
||||
};
|
||||
|
||||
using OutputArrayType = XMLOutputArray;
|
||||
using OutputObjectType = XMLOutputObject;
|
||||
using OutputVarType = XMLOutputVar;
|
||||
|
||||
Writer(const Ref<pugi::xml_node>& _root, const std::string& _root_name)
|
||||
: root_(_root), root_name_(_root_name) {}
|
||||
|
||||
~Writer() = default;
|
||||
|
||||
OutputArrayType array_as_root(const size_t _size) const noexcept {
|
||||
auto node_child =
|
||||
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
|
||||
return OutputArrayType(root_name_, node_child);
|
||||
}
|
||||
|
||||
OutputObjectType object_as_root(const size_t _size) const noexcept {
|
||||
auto node_child =
|
||||
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
|
||||
return OutputObjectType(node_child);
|
||||
}
|
||||
|
||||
OutputVarType null_as_root() const noexcept {
|
||||
auto node_child =
|
||||
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
|
||||
return OutputVarType(node_child);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType value_as_root(const T& _var) const noexcept {
|
||||
const auto str = to_string(_var);
|
||||
auto node_child =
|
||||
Ref<pugi::xml_node>::make(root_->append_child(root_name_.c_str()));
|
||||
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
|
||||
return OutputVarType(node_child);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_array(const size_t _size,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return *_parent;
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return OutputArrayType(_name, _parent->node_);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_array(
|
||||
const size_t _size, OutputArrayType* _parent) const noexcept {
|
||||
auto node_child = Ref<pugi::xml_node>::make(
|
||||
_parent->node_->append_child(_parent->name_.data()));
|
||||
return OutputObjectType(node_child);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
auto node_child =
|
||||
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
|
||||
return OutputObjectType(node_child);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_array(const T& _var,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
const auto str = to_string(_var);
|
||||
auto node_child = Ref<pugi::xml_node>::make(
|
||||
_parent->node_->append_child(_parent->name_.data()));
|
||||
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
|
||||
return OutputVarType(node_child);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_object(
|
||||
const std::string_view& _name, const T& _var, OutputObjectType* _parent,
|
||||
const bool _is_attribute = false) const noexcept {
|
||||
const auto str = to_string(_var);
|
||||
if (_is_attribute) {
|
||||
_parent->node_->append_attribute(_name.data()) = str.c_str();
|
||||
return OutputVarType(_parent->node_);
|
||||
} else if (_name == XML_CONTENT) {
|
||||
_parent->node_->append_child(pugi::node_pcdata).set_value(str.c_str());
|
||||
return OutputVarType(_parent->node_);
|
||||
} else {
|
||||
auto node_child =
|
||||
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
|
||||
node_child->append_child(pugi::node_pcdata).set_value(str.c_str());
|
||||
return OutputVarType(node_child);
|
||||
}
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
|
||||
auto node_child = Ref<pugi::xml_node>::make(
|
||||
_parent->node_->append_child(_parent->name_.data()));
|
||||
return OutputVarType(node_child);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_object(
|
||||
const std::string_view& _name, OutputObjectType* _parent,
|
||||
const bool _is_attribute = false) const noexcept {
|
||||
if (_is_attribute) {
|
||||
return OutputVarType(_parent->node_);
|
||||
} else if (_name == XML_CONTENT) {
|
||||
return OutputVarType(_parent->node_);
|
||||
} else {
|
||||
auto node_child =
|
||||
Ref<pugi::xml_node>::make(_parent->node_->append_child(_name.data()));
|
||||
return OutputVarType(node_child);
|
||||
}
|
||||
}
|
||||
|
||||
void end_array(OutputArrayType* _arr) const noexcept {}
|
||||
|
||||
void end_object(OutputObjectType* _obj) const noexcept {}
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
std::string to_string(const T& _val) const noexcept {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
|
||||
return _val;
|
||||
} else if constexpr (std::is_same<std::remove_cvref_t<T>, bool>()) {
|
||||
return _val ? "true" : "false";
|
||||
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>() ||
|
||||
std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
return std::to_string(_val);
|
||||
} else {
|
||||
static_assert(always_false_v<T>, "Unsupported type");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Ref<pugi::xml_node> root_;
|
||||
|
||||
std::string root_name_;
|
||||
};
|
||||
|
||||
} // namespace xml
|
||||
} // namespace rfl
|
||||
|
||||
#endif // XML_PARSER_HPP_
|
22
include/rfl/xml/load.hpp
Normal file
22
include/rfl/xml/load.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef RFL_XML_LOAD_HPP_
|
||||
#define RFL_XML_LOAD_HPP_
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/load_string.hpp"
|
||||
#include "read.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace xml {
|
||||
|
||||
template <class T, class... Ps>
|
||||
Result<T> load(const std::string& _fname) {
|
||||
const auto read_string = [](const auto& _str) {
|
||||
return read<T, Ps...>(_str);
|
||||
};
|
||||
return rfl::io::load_string(_fname).and_then(read_string);
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
50
include/rfl/xml/read.hpp
Normal file
50
include/rfl/xml/read.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef RFL_XML_READ_HPP_
|
||||
#define RFL_XML_READ_HPP_
|
||||
|
||||
#include <istream>
|
||||
#include <pugixml.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../internal/get_type_name.hpp"
|
||||
#include "../internal/remove_namespaces.hpp"
|
||||
#include "Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace xml {
|
||||
|
||||
using InputVarType = typename Reader::InputVarType;
|
||||
|
||||
/// Parses an object from a XML var.
|
||||
template <class T, class... Ps>
|
||||
auto read(const InputVarType& _var) {
|
||||
const auto r = Reader();
|
||||
return Parser<T, Processors<Ps...>>::read(r, _var);
|
||||
}
|
||||
|
||||
/// Parses an object from XML using reflection.
|
||||
template <class T, class... Ps>
|
||||
Result<T> read(const std::string& _xml_str) {
|
||||
pugi::xml_document doc;
|
||||
const auto result = doc.load_string(_xml_str.c_str());
|
||||
if (!result) {
|
||||
return Error("XML string could not be parsed: " +
|
||||
std::string(result.description()));
|
||||
}
|
||||
const auto var = InputVarType(doc.first_child());
|
||||
return read<T, Ps...>(var);
|
||||
}
|
||||
|
||||
/// Parses an object from a stringstream.
|
||||
template <class T, class... Ps>
|
||||
auto read(std::istream& _stream) {
|
||||
const auto xml_str = std::string(std::istreambuf_iterator<char>(_stream),
|
||||
std::istreambuf_iterator<char>());
|
||||
return read<T, Ps...>(xml_str);
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
28
include/rfl/xml/save.hpp
Normal file
28
include/rfl/xml/save.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef RFL_XML_SAVE_HPP_
|
||||
#define RFL_XML_SAVE_HPP_
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../internal/StringLiteral.hpp"
|
||||
#include "../io/save_string.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace xml {
|
||||
|
||||
template <internal::StringLiteral _root = internal::StringLiteral(""),
|
||||
class... Ps>
|
||||
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
|
||||
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
|
||||
return write<_root, Ps...>(_obj, _stream);
|
||||
};
|
||||
return rfl::io::save_string(_fname, _obj, write_func);
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
74
include/rfl/xml/write.hpp
Normal file
74
include/rfl/xml/write.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#ifndef RFL_XML_WRITE_HPP_
|
||||
#define RFL_XML_WRITE_HPP_
|
||||
|
||||
#include <ostream>
|
||||
#include <pugixml.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../internal/StringLiteral.hpp"
|
||||
#include "../internal/get_type_name.hpp"
|
||||
#include "../internal/remove_namespaces.hpp"
|
||||
#include "../parsing/Parent.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace xml {
|
||||
|
||||
template <internal::StringLiteral _root, class T>
|
||||
consteval auto get_root_name() {
|
||||
if constexpr (_root != internal::StringLiteral("")) {
|
||||
return _root;
|
||||
} else {
|
||||
return internal::remove_namespaces<
|
||||
internal::get_type_name<std::remove_cvref_t<T>>()>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a XML into an ostream.
|
||||
template <internal::StringLiteral _root = internal::StringLiteral(""),
|
||||
class... Ps>
|
||||
std::ostream& write(const auto& _obj, std::ostream& _stream,
|
||||
const std::string& _indent = " ") {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
|
||||
constexpr auto root_name = get_root_name<_root, T>();
|
||||
|
||||
static_assert(root_name.string_view().find("<") == std::string_view::npos &&
|
||||
root_name.string_view().find(">") == std::string_view::npos,
|
||||
"The name of an XML root node cannot contain '<' or '>'. "
|
||||
"Please assign an "
|
||||
"explicit root name to rfl::xml::write(...) like this: "
|
||||
"rfl::xml::write<\"root_name\">(...).");
|
||||
|
||||
const auto doc = rfl::Ref<pugi::xml_document>::make();
|
||||
|
||||
auto declaration_node = doc->append_child(pugi::node_declaration);
|
||||
declaration_node.append_attribute("version") = "1.0";
|
||||
declaration_node.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
auto w = Writer(doc, root_name.str());
|
||||
|
||||
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
|
||||
|
||||
doc->save(_stream, _indent.c_str());
|
||||
|
||||
return _stream;
|
||||
}
|
||||
|
||||
/// Returns a XML string.
|
||||
template <internal::StringLiteral _root = internal::StringLiteral(""),
|
||||
class... Ps>
|
||||
std::string write(const auto& _obj, const std::string& _indent = " ") {
|
||||
std::stringstream stream;
|
||||
write<_root, Ps...>(_obj, stream);
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace xml
|
||||
} // namespace rfl
|
||||
|
||||
#endif // XML_PARSER_HPP_
|
13
include/rfl/yaml.hpp
Normal file
13
include/rfl/yaml.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef RFL_YAML_HPP_
|
||||
#define RFL_YAML_HPP_
|
||||
|
||||
#include "../rfl.hpp"
|
||||
#include "yaml/Parser.hpp"
|
||||
#include "yaml/Reader.hpp"
|
||||
#include "yaml/Writer.hpp"
|
||||
#include "yaml/load.hpp"
|
||||
#include "yaml/read.hpp"
|
||||
#include "yaml/save.hpp"
|
||||
#include "yaml/write.hpp"
|
||||
|
||||
#endif
|
17
include/rfl/yaml/Parser.hpp
Normal file
17
include/rfl/yaml/Parser.hpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#ifndef RFL_YAML_PARSER_HPP_
|
||||
#define RFL_YAML_PARSER_HPP_
|
||||
|
||||
#include "../parsing/Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
#include "Writer.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace yaml {
|
||||
|
||||
template <class T, class ProcessorsType>
|
||||
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;
|
||||
|
||||
}
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
137
include/rfl/yaml/Reader.hpp
Normal file
137
include/rfl/yaml/Reader.hpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#ifndef RFL_YAML_READER_HPP_
|
||||
#define RFL_YAML_READER_HPP_
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <array>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace yaml {
|
||||
|
||||
struct Reader {
|
||||
struct YAMLInputArray {
|
||||
YAMLInputArray(const YAML::Node& _node) : node_(_node) {}
|
||||
YAML::Node node_;
|
||||
};
|
||||
|
||||
struct YAMLInputObject {
|
||||
YAMLInputObject(const YAML::Node& _node) : node_(_node) {}
|
||||
YAML::Node node_;
|
||||
};
|
||||
|
||||
struct YAMLInputVar {
|
||||
YAMLInputVar(const YAML::Node& _node) : node_(_node) {}
|
||||
YAML::Node node_;
|
||||
};
|
||||
|
||||
using InputArrayType = YAMLInputArray;
|
||||
using InputObjectType = YAMLInputObject;
|
||||
using InputVarType = YAMLInputVar;
|
||||
|
||||
template <class T, class = void>
|
||||
struct has_from_json_obj : std::false_type {};
|
||||
|
||||
template <class T>
|
||||
static constexpr bool has_custom_constructor = (requires(InputVarType var) {
|
||||
T::from_yaml_obj(var);
|
||||
});
|
||||
|
||||
rfl::Result<InputVarType> get_field(
|
||||
const std::string& _name, const InputObjectType& _obj) const noexcept {
|
||||
auto var = InputVarType(_obj.node_[_name]);
|
||||
if (!var.node_) {
|
||||
return rfl::Error("Object contains no field named '" + _name + "'.");
|
||||
}
|
||||
return var;
|
||||
}
|
||||
|
||||
bool is_empty(const InputVarType& _var) const noexcept {
|
||||
return !_var.node_ && true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> to_basic_type(const InputVarType& _var) const noexcept {
|
||||
try {
|
||||
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>() ||
|
||||
std::is_same<std::remove_cvref_t<T>, bool>() ||
|
||||
std::is_floating_point<std::remove_cvref_t<T>>() ||
|
||||
std::is_integral<std::remove_cvref_t<T>>()) {
|
||||
return _var.node_.as<std::remove_cvref_t<T>>();
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Unsupported type.");
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
return rfl::Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
rfl::Result<InputArrayType> to_array(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (!_var.node_.IsSequence()) {
|
||||
return rfl::Error("Could not cast to sequence!");
|
||||
}
|
||||
return InputArrayType(_var.node_);
|
||||
}
|
||||
|
||||
template <class ArrayReader>
|
||||
std::optional<Error> read_array(const ArrayReader& _array_reader,
|
||||
const InputArrayType& _arr) const noexcept {
|
||||
for (size_t i = 0; i < _arr.node_.size(); ++i) {
|
||||
const auto err = _array_reader.read(_arr.node_[i]);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <class ObjectReader>
|
||||
std::optional<Error> read_object(const ObjectReader& _object_reader,
|
||||
const InputObjectType& _obj) const noexcept {
|
||||
for (const auto& p : _obj.node_) {
|
||||
try {
|
||||
const auto k = p.first.as<std::string>();
|
||||
_object_reader.read(std::string_view(k), InputVarType(p.second));
|
||||
} catch (std::exception& e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
rfl::Result<InputObjectType> to_object(
|
||||
const InputVarType& _var) const noexcept {
|
||||
if (!_var.node_.IsMap()) {
|
||||
return rfl::Error("Could not cast to map!");
|
||||
}
|
||||
return InputObjectType(_var.node_);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
rfl::Result<T> use_custom_constructor(
|
||||
const InputVarType _var) const noexcept {
|
||||
try {
|
||||
return T::from_yaml_obj(_var);
|
||||
} catch (std::exception& e) {
|
||||
return rfl::Error(e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yaml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
148
include/rfl/yaml/Writer.hpp
Normal file
148
include/rfl/yaml/Writer.hpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
#ifndef RFL_YAML_WRITER_HPP_
|
||||
#define RFL_YAML_WRITER_HPP_
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../always_false.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace yaml {
|
||||
|
||||
class Writer {
|
||||
public:
|
||||
struct YAMLArray {};
|
||||
|
||||
struct YAMLObject {};
|
||||
|
||||
struct YAMLVar {};
|
||||
|
||||
using OutputArrayType = YAMLArray;
|
||||
using OutputObjectType = YAMLObject;
|
||||
using OutputVarType = YAMLVar;
|
||||
|
||||
Writer(const Ref<YAML::Emitter>& _out) : out_(_out) {}
|
||||
|
||||
~Writer() = default;
|
||||
|
||||
OutputArrayType array_as_root(const size_t _size) const noexcept {
|
||||
return new_array();
|
||||
}
|
||||
|
||||
OutputObjectType object_as_root(const size_t _size) const noexcept {
|
||||
return new_object();
|
||||
}
|
||||
|
||||
OutputVarType null_as_root() const noexcept {
|
||||
return insert_value(YAML::Null);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType value_as_root(const T& _var) const noexcept {
|
||||
return insert_value(_var);
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_array(const size_t _size,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return new_array();
|
||||
}
|
||||
|
||||
OutputArrayType add_array_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return new_array(_name);
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_array(
|
||||
const size_t _size, OutputArrayType* _parent) const noexcept {
|
||||
return new_object();
|
||||
}
|
||||
|
||||
OutputObjectType add_object_to_object(
|
||||
const std::string_view& _name, const size_t _size,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return new_object(_name);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_array(const T& _var,
|
||||
OutputArrayType* _parent) const noexcept {
|
||||
return insert_value(_var);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType add_value_to_object(const std::string_view& _name,
|
||||
const T& _var,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return insert_value(_name, _var);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_array(OutputArrayType* _parent) const noexcept {
|
||||
return insert_value(YAML::Null);
|
||||
}
|
||||
|
||||
OutputVarType add_null_to_object(const std::string_view& _name,
|
||||
OutputObjectType* _parent) const noexcept {
|
||||
return insert_value(_name, YAML::Null);
|
||||
}
|
||||
|
||||
void end_array(OutputArrayType* _arr) const noexcept {
|
||||
(*out_) << YAML::EndSeq;
|
||||
}
|
||||
|
||||
void end_object(OutputObjectType* _obj) const noexcept {
|
||||
(*out_) << YAML::EndMap;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
OutputVarType insert_value(const std::string_view& _name,
|
||||
const T& _var) const noexcept {
|
||||
(*out_) << YAML::Key << _name.data() << YAML::Value << _var;
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
OutputVarType insert_value(const T& _var) const noexcept {
|
||||
(*out_) << _var;
|
||||
return OutputVarType{};
|
||||
}
|
||||
|
||||
OutputArrayType new_array(const std::string_view& _name) const noexcept {
|
||||
(*out_) << YAML::Key << _name.data() << YAML::Value << YAML::BeginSeq;
|
||||
return OutputArrayType{};
|
||||
}
|
||||
|
||||
OutputArrayType new_array() const noexcept {
|
||||
(*out_) << YAML::BeginSeq;
|
||||
return OutputArrayType{};
|
||||
}
|
||||
|
||||
OutputObjectType new_object(const std::string_view& _name) const noexcept {
|
||||
(*out_) << YAML::Key << _name.data() << YAML::Value << YAML::BeginMap;
|
||||
return OutputObjectType{};
|
||||
}
|
||||
|
||||
OutputObjectType new_object() const noexcept {
|
||||
(*out_) << YAML::BeginMap;
|
||||
return OutputObjectType{};
|
||||
}
|
||||
|
||||
public:
|
||||
const Ref<YAML::Emitter> out_;
|
||||
};
|
||||
|
||||
} // namespace yaml
|
||||
} // namespace rfl
|
||||
|
||||
#endif // JSON_PARSER_HPP_
|
22
include/rfl/yaml/load.hpp
Normal file
22
include/rfl/yaml/load.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef RFL_YAML_LOAD_HPP_
|
||||
#define RFL_YAML_LOAD_HPP_
|
||||
|
||||
#include "../Result.hpp"
|
||||
#include "../io/load_string.hpp"
|
||||
#include "read.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace yaml {
|
||||
|
||||
template <class T, class... Ps>
|
||||
Result<T> load(const std::string& _fname) {
|
||||
const auto read_string = [](const auto& _str) {
|
||||
return read<T, Ps...>(_str);
|
||||
};
|
||||
return rfl::io::load_string(_fname).and_then(read_string);
|
||||
}
|
||||
|
||||
} // namespace yaml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
47
include/rfl/yaml/read.hpp
Normal file
47
include/rfl/yaml/read.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef RFL_YAML_READ_HPP_
|
||||
#define RFL_YAML_READ_HPP_
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <istream>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../internal/wrap_in_rfl_array_t.hpp"
|
||||
#include "Parser.hpp"
|
||||
#include "Reader.hpp"
|
||||
namespace rfl {
|
||||
namespace yaml {
|
||||
|
||||
using InputVarType = typename Reader::InputVarType;
|
||||
|
||||
/// Parses an object from a YAML var.
|
||||
template <class T, class... Ps>
|
||||
auto read(const InputVarType& _var) {
|
||||
const auto r = Reader();
|
||||
return Parser<T, Processors<Ps...>>::read(r, _var);
|
||||
}
|
||||
|
||||
/// Parses an object from YAML using reflection.
|
||||
template <class T, class... Ps>
|
||||
Result<internal::wrap_in_rfl_array_t<T>> read(const std::string& _yaml_str) {
|
||||
try {
|
||||
const auto var = InputVarType(YAML::Load(_yaml_str));
|
||||
return read<T, Ps...>(var);
|
||||
} catch (std::exception& e) {
|
||||
return Error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an object from a stringstream.
|
||||
template <class T, class... Ps>
|
||||
auto read(std::istream& _stream) {
|
||||
const auto yaml_str = std::string(std::istreambuf_iterator<char>(_stream),
|
||||
std::istreambuf_iterator<char>());
|
||||
return read<T, Ps...>(yaml_str);
|
||||
}
|
||||
|
||||
} // namespace yaml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
27
include/rfl/yaml/save.hpp
Normal file
27
include/rfl/yaml/save.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef RFL_YAML_SAVE_HPP_
|
||||
#define RFL_YAML_SAVE_HPP_
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../io/save_string.hpp"
|
||||
#include "write.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace yaml {
|
||||
|
||||
template <class... Ps>
|
||||
Result<Nothing> save(const std::string& _fname, const auto& _obj) {
|
||||
const auto write_func = [](const auto& _obj, auto& _stream) -> auto& {
|
||||
return write<Ps...>(_obj, _stream);
|
||||
};
|
||||
return rfl::io::save_string(_fname, _obj, write_func);
|
||||
}
|
||||
|
||||
} // namespace yaml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
44
include/rfl/yaml/write.hpp
Normal file
44
include/rfl/yaml/write.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef RFL_YAML_WRITE_HPP_
|
||||
#define RFL_YAML_WRITE_HPP_
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../Processors.hpp"
|
||||
#include "../parsing/Parent.hpp"
|
||||
#include "Parser.hpp"
|
||||
|
||||
namespace rfl {
|
||||
namespace yaml {
|
||||
|
||||
/// Writes a YAML into an ostream.
|
||||
template <class... Ps>
|
||||
std::ostream& write(const auto& _obj, std::ostream& _stream) {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
const auto out = Ref<YAML::Emitter>::make();
|
||||
auto w = Writer(out);
|
||||
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
|
||||
_stream << out->c_str();
|
||||
return _stream;
|
||||
}
|
||||
|
||||
/// Returns a YAML string.
|
||||
template <class... Ps>
|
||||
std::string write(const auto& _obj) {
|
||||
using T = std::remove_cvref_t<decltype(_obj)>;
|
||||
using ParentType = parsing::Parent<Writer>;
|
||||
const auto out = Ref<YAML::Emitter>::make();
|
||||
auto w = Writer(out);
|
||||
Parser<T, Processors<Ps...>>::write(w, _obj, typename ParentType::Root{});
|
||||
return out->c_str();
|
||||
}
|
||||
|
||||
} // namespace yaml
|
||||
} // namespace rfl
|
||||
|
||||
#endif
|
7690
include/yyjson.h
Normal file
7690
include/yyjson.h
Normal file
File diff suppressed because it is too large
Load diff
41
meson.build
41
meson.build
|
@ -1,8 +1,10 @@
|
|||
project(
|
||||
'draconis++', 'cpp',
|
||||
'draconis++', ['cpp', 'objcpp'],
|
||||
version: '0.1.0',
|
||||
default_options: [
|
||||
'cpp_std=c++26',
|
||||
'objc_std=c++20',
|
||||
'objcpp_std=c++20',
|
||||
'cpp_std=c++20',
|
||||
'default_library=static',
|
||||
'warning_level=everything',
|
||||
'buildtype=debugoptimized'
|
||||
|
@ -12,6 +14,21 @@ project(
|
|||
clangtidy = find_program('clang-tidy', required: false)
|
||||
|
||||
cpp = meson.get_compiler('cpp')
|
||||
objcpp = meson.get_compiler('objcpp')
|
||||
|
||||
add_project_arguments(
|
||||
objcpp.get_supported_arguments([
|
||||
'-Wno-c++20-compat',
|
||||
'-Wno-c++98-compat',
|
||||
'-Wno-c++98-compat-pedantic',
|
||||
'-Wno-missing-prototypes',
|
||||
'-Wno-padded',
|
||||
'-Wno-pre-c++20-compat-pedantic',
|
||||
'-Wno-switch-default',
|
||||
'-Wunused-function',
|
||||
]),
|
||||
language: 'objcpp'
|
||||
)
|
||||
|
||||
add_project_arguments(
|
||||
cpp.get_supported_arguments([
|
||||
|
@ -22,7 +39,7 @@ add_project_arguments(
|
|||
'-Wno-padded',
|
||||
'-Wno-pre-c++20-compat-pedantic',
|
||||
'-Wno-switch-default',
|
||||
'-Wunused-function'
|
||||
'-Wunused-function',
|
||||
]),
|
||||
language: 'cpp'
|
||||
)
|
||||
|
@ -40,7 +57,11 @@ if host_machine.system() == 'linux'
|
|||
endif
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
source_file_names += ['src/os/macos.cpp']
|
||||
source_file_names += [
|
||||
'src/os/macos.cpp',
|
||||
'src/os/macos/NowPlayingBridge.h',
|
||||
'src/os/macos/NowPlayingBridge.mm'
|
||||
]
|
||||
endif
|
||||
|
||||
sources = []
|
||||
|
@ -49,16 +70,16 @@ foreach file : source_file_names
|
|||
sources += files(file)
|
||||
endforeach
|
||||
|
||||
quill = subproject('quill')
|
||||
|
||||
deps = []
|
||||
|
||||
deps += cpp.find_library('cpr')
|
||||
deps += cpp.find_library('curl')
|
||||
deps += cpp.find_library('tomlplusplus')
|
||||
deps += dependency('boost', static: true)
|
||||
deps += dependency('coost')
|
||||
deps += dependency('fmt')
|
||||
deps += quill.get_variable('quill_dep')
|
||||
deps += dependency('Foundation')
|
||||
deps += dependency('MediaPlayer')
|
||||
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
deps += dependency('playerctl')
|
||||
|
@ -73,5 +94,7 @@ executable(
|
|||
'draconis++',
|
||||
sources,
|
||||
dependencies: deps,
|
||||
include_directories: incdir
|
||||
include_directories: incdir,
|
||||
objc_args: ['-fobjc-arc'],
|
||||
link_args: ['-framework', 'Foundation', '-framework', 'MediaPlayer']
|
||||
)
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
#include <fmt/core.h>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/toml.hpp>
|
||||
#include <toml++/toml.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define DEFINE_GETTER(class_name, type, name) \
|
||||
|
@ -21,9 +15,11 @@ DEFINE_GETTER(Weather, const string, ApiKey)
|
|||
DEFINE_GETTER(Weather, const string, Units)
|
||||
|
||||
const Config& Config::getInstance() {
|
||||
static const Config& INSTANCE =
|
||||
*new Config(rfl::toml::load<Config>("./config.toml").value());
|
||||
return INSTANCE;
|
||||
static const auto* INSTANCE =
|
||||
new Config(rfl::toml::load<Config>("./config.toml").value());
|
||||
static std::once_flag Flag;
|
||||
std::call_once(Flag, [] { std::atexit([] { delete INSTANCE; }); });
|
||||
return *INSTANCE;
|
||||
}
|
||||
|
||||
Config::Config(General general, NowPlaying now_playing, Weather weather)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <boost/json.hpp>
|
||||
#include <co/json.h>
|
||||
#include <cpr/cpr.h>
|
||||
#include <fmt/core.h>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/toml.hpp>
|
||||
#include <string>
|
||||
#include <toml++/impl/parser.hpp>
|
||||
#include <toml++/toml.h>
|
||||
#include <unistd.h>
|
||||
#include <variant>
|
||||
|
||||
using std::string;
|
||||
|
@ -29,7 +25,7 @@ class Weather {
|
|||
public:
|
||||
Weather(Location location, string api_key, string units);
|
||||
|
||||
[[nodiscard]] boost::json::object getWeatherInfo() const;
|
||||
[[nodiscard]] co::Json getWeatherInfo() const;
|
||||
[[nodiscard]] const Location getLocation() const;
|
||||
[[nodiscard]] const string getApiKey() const;
|
||||
[[nodiscard]] const string getUnits() const;
|
||||
|
|
|
@ -1,83 +1,77 @@
|
|||
#include <rfl.hpp>
|
||||
#include <rfl/toml.hpp>
|
||||
#include <toml++/toml.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace chrono;
|
||||
using namespace boost;
|
||||
|
||||
// Function to read cache from file
|
||||
optional<pair<json::object, system_clock::time_point>> ReadCacheFromFile() {
|
||||
std::optional<std::pair<co::Json, std::chrono::system_clock::time_point>>
|
||||
ReadCacheFromFile() {
|
||||
const string cacheFile = "/tmp/weather_cache.json";
|
||||
ifstream ifs(cacheFile);
|
||||
std::ifstream ifs(cacheFile);
|
||||
|
||||
if (!ifs.is_open()) {
|
||||
fmt::println("Cache file not found.");
|
||||
return nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
fmt::println("Reading from cache file...");
|
||||
|
||||
json::object cachedData;
|
||||
system_clock::time_point timestamp;
|
||||
co::Json val;
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
|
||||
try {
|
||||
json::value val;
|
||||
ifs >> val;
|
||||
cachedData = val.as_object();
|
||||
std::stringstream buf;
|
||||
buf << ifs.rdbuf();
|
||||
|
||||
string tsStr = cachedData["timestamp"].as_string().c_str();
|
||||
timestamp = system_clock::time_point(milliseconds(stoll(tsStr)));
|
||||
val.parse_from(buf.str());
|
||||
|
||||
cachedData.erase("timestamp");
|
||||
string tsStr = val["timestamp"].as_string().c_str();
|
||||
timestamp = std::chrono::system_clock::time_point(
|
||||
std::chrono::milliseconds(stoll(tsStr)));
|
||||
|
||||
val.erase("timestamp");
|
||||
} catch (...) {
|
||||
fmt::println(stderr, "Failed to read from cache file.");
|
||||
|
||||
return nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
fmt::println("Successfully read from cache file.");
|
||||
return make_pair(cachedData, timestamp);
|
||||
return make_pair(val, timestamp);
|
||||
}
|
||||
|
||||
// Function to write cache to file
|
||||
void WriteCacheToFile(const json::object& data) {
|
||||
void WriteCacheToFile(const co::Json& data) {
|
||||
const string cacheFile = "/tmp/weather_cache.json";
|
||||
fmt::println("Writing to cache file...");
|
||||
ofstream ofs(cacheFile);
|
||||
std::ofstream ofs(cacheFile);
|
||||
|
||||
if (!ofs.is_open()) {
|
||||
fmt::println(stderr, "Failed to open cache file for writing.");
|
||||
return;
|
||||
}
|
||||
|
||||
json::object dataToWrite = data;
|
||||
data["timestamp"] =
|
||||
std::to_string(duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
|
||||
dataToWrite["timestamp"] = to_string(
|
||||
duration_cast<milliseconds>(system_clock::now().time_since_epoch())
|
||||
.count());
|
||||
|
||||
ofs << json::serialize(dataToWrite);
|
||||
ofs << data.as_string();
|
||||
|
||||
fmt::println("Successfully wrote to cache file.");
|
||||
}
|
||||
|
||||
// Function to make API request
|
||||
json::object MakeApiRequest(const string& url) {
|
||||
co::Json MakeApiRequest(const string& url) {
|
||||
using namespace cpr;
|
||||
|
||||
fmt::println("Making API request...");
|
||||
const Response res = Get(Url {url});
|
||||
fmt::println("Received response from API.");
|
||||
json::value json = json::parse(res.text);
|
||||
return json.as_object();
|
||||
co::Json json = json::parse(res.text);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
// Core function to get weather information
|
||||
json::object Weather::getWeatherInfo() const {
|
||||
co::Json Weather::getWeatherInfo() const {
|
||||
using namespace cpr;
|
||||
|
||||
const Location loc = m_Location;
|
||||
|
@ -86,10 +80,11 @@ json::object Weather::getWeatherInfo() const {
|
|||
|
||||
// Check if cache is valid
|
||||
if (auto cachedData = ReadCacheFromFile()) {
|
||||
auto [data, timestamp] = *cachedData;
|
||||
auto& [data, timestamp] = *cachedData;
|
||||
|
||||
if (system_clock::now() - timestamp <
|
||||
minutes(10)) { // Assuming cache duration is always 10 minutes
|
||||
if (std::chrono::system_clock::now() - timestamp <
|
||||
std::chrono::minutes(
|
||||
10)) { // Assuming cache duration is always 10 minutes
|
||||
fmt::println("Cache is valid. Returning cached data.");
|
||||
return data;
|
||||
}
|
||||
|
@ -99,7 +94,7 @@ json::object Weather::getWeatherInfo() const {
|
|||
fmt::println("No valid cache found.");
|
||||
}
|
||||
|
||||
json::object result;
|
||||
co::Json result;
|
||||
|
||||
if (holds_alternative<string>(loc)) {
|
||||
const string city = get<string>(loc);
|
||||
|
|
23
src/main.cpp
23
src/main.cpp
|
@ -1,17 +1,10 @@
|
|||
#include <boost/json/src.hpp>
|
||||
#include <cpr/cpr.h>
|
||||
#include <co/log.h>
|
||||
#include <ctime>
|
||||
#include <curl/curl.h>
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/toml.hpp>
|
||||
#include <rfl/toml/load.hpp>
|
||||
#include <rfl/toml/read.hpp>
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "os/macos/NowPlayingBridge.h"
|
||||
#include "os/os.h"
|
||||
|
||||
using std::string;
|
||||
|
@ -49,9 +42,10 @@ DateNum ParseDate(string const& input) {
|
|||
return Default;
|
||||
}
|
||||
|
||||
int main() {
|
||||
using boost::json::object;
|
||||
using std::time_t;
|
||||
int main(int argc, char** argv) {
|
||||
flag::parse(argc, argv);
|
||||
|
||||
LOG << "hello " << 23;
|
||||
|
||||
const Config& config = Config::getInstance();
|
||||
|
||||
|
@ -97,12 +91,13 @@ int main() {
|
|||
|
||||
fmt::println("{:%B} {}, {:%-I:%0M %p}", localTime, date, localTime);
|
||||
|
||||
object json = config.getWeather().getWeatherInfo();
|
||||
co::Json json = config.getWeather().getWeatherInfo();
|
||||
|
||||
const int temp = json.get("main", "temp").as_int();
|
||||
const char* townName =
|
||||
json["name"].is_string() ? json["name"].as_string().c_str() : "Unknown";
|
||||
|
||||
fmt::println("{}", townName);
|
||||
fmt::println("It is {}°F in {}", temp, townName);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#ifdef __linux__
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <playerctl/playerctl.h>
|
||||
#include <fstream>
|
||||
#include <playerctl/playerctl.h>
|
||||
|
||||
#include "os.h"
|
||||
|
||||
using std::string;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <string>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include "macos/NowPlayingBridge.h"
|
||||
#include "os.h"
|
||||
|
||||
uint64_t GetMemInfo() {
|
||||
uint64_t mem = 0;
|
||||
size_t size = sizeof(mem);
|
||||
size_t size = sizeof(mem);
|
||||
|
||||
sysctlbyname("hw.memsize", &mem, &size, nullptr, 0);
|
||||
|
||||
|
@ -14,7 +16,7 @@ uint64_t GetMemInfo() {
|
|||
}
|
||||
|
||||
std::string GetNowPlaying() {
|
||||
return "";
|
||||
return getCurrentPlayingTitle();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
14
src/os/macos/NowPlayingBridge.h
Normal file
14
src/os/macos/NowPlayingBridge.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
// NowPlayingBridge.h
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NowPlayingBridge : NSObject
|
||||
+ (NSDictionary*)currentPlayingMetadata;
|
||||
@end
|
||||
#else
|
||||
extern "C" {
|
||||
const char* getCurrentPlayingTitle();
|
||||
const char* getCurrentPlayingArtist();
|
||||
}
|
||||
#endif
|
90
src/os/macos/NowPlayingBridge.mm
Normal file
90
src/os/macos/NowPlayingBridge.mm
Normal file
|
@ -0,0 +1,90 @@
|
|||
// NowPlayingBridge.mm
|
||||
|
||||
#import "NowPlayingBridge.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <dispatch/dispatch.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
typedef void (*MRMediaRemoteGetNowPlayingInfoFunction)(
|
||||
dispatch_queue_t queue, void (^handler)(NSDictionary *information));
|
||||
|
||||
@implementation NowPlayingBridge
|
||||
|
||||
+ (NSDictionary *)currentPlayingMetadata {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
|
||||
CFURLRef ref = (__bridge CFURLRef)
|
||||
[NSURL fileURLWithPath:
|
||||
@"/System/Library/PrivateFrameworks/MediaRemote.framework"];
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, ref);
|
||||
|
||||
if (!bundle) {
|
||||
NSLog(@"Failed to load MediaRemote framework");
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
|
||||
MRMediaRemoteGetNowPlayingInfoFunction MRMediaRemoteGetNowPlayingInfo =
|
||||
(MRMediaRemoteGetNowPlayingInfoFunction)CFBundleGetFunctionPointerForName(
|
||||
bundle, CFSTR("MRMediaRemoteGetNowPlayingInfo"));
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
if (!MRMediaRemoteGetNowPlayingInfo) {
|
||||
NSLog(@"Failed to get function pointer for MRMediaRemoteGetNowPlayingInfo");
|
||||
CFRelease(bundle);
|
||||
return nil;
|
||||
}
|
||||
|
||||
__block NSDictionary *nowPlayingInfo = nil;
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
|
||||
MRMediaRemoteGetNowPlayingInfo(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
^(NSDictionary *information) {
|
||||
nowPlayingInfo = [information copy];
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
});
|
||||
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
|
||||
CFRelease(bundle);
|
||||
return nowPlayingInfo;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char *getCurrentPlayingTitle() {
|
||||
NSDictionary *metadata = [NowPlayingBridge currentPlayingMetadata];
|
||||
if (metadata == nil) {
|
||||
return nullptr;
|
||||
}
|
||||
NSString *title =
|
||||
[metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoTitle"];
|
||||
if (title) {
|
||||
return strdup([title UTF8String]);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char *getCurrentPlayingArtist() {
|
||||
NSDictionary *metadata = [NowPlayingBridge currentPlayingMetadata];
|
||||
if (metadata == nil) {
|
||||
return nullptr;
|
||||
}
|
||||
NSString *artist =
|
||||
[metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoArtist"];
|
||||
if (artist) {
|
||||
return strdup([artist UTF8String]);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -1,45 +0,0 @@
|
|||
AccessModifierOffset: -2
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackParameters: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Allman
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: ''
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyExcessCharacter: 1
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
PointerAlignment: Left
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
Cpp11BracedListStyle: true
|
||||
Standard: c++20
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
|
@ -1,5 +0,0 @@
|
|||
ignore:
|
||||
- "examples"
|
||||
- "quill/include/quill/bundled"
|
||||
- "quill/src/bundled"
|
||||
- "quill/test"
|
|
@ -1,33 +0,0 @@
|
|||
name: coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure
|
||||
run: cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=17 -DQUILL_BUILD_TESTS=ON -DQUILL_CODE_COVERAGE=ON -DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j4
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cd build
|
||||
ctest --build-config Debug
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
111
subprojects/quill-4.2.0/.github/workflows/linux.yml
vendored
111
subprojects/quill-4.2.0/.github/workflows/linux.yml
vendored
|
@ -1,111 +0,0 @@
|
|||
name: linux
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
cxx: [ g++-8, g++-10 ]
|
||||
build_type: [ Debug, Release ]
|
||||
std: [ 17 ]
|
||||
os: [ ubuntu-20.04 ]
|
||||
with_tests: [ ON ]
|
||||
|
||||
include:
|
||||
# Build and with g++8
|
||||
- cxx: g++-8
|
||||
std: 17
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
install: sudo apt -o Acquire::Retries=5 install g++-8
|
||||
|
||||
# Build and test as shared library
|
||||
- cxx: g++-10
|
||||
build_type: Release
|
||||
std: 17
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON
|
||||
|
||||
# Builds with no exceptions
|
||||
- cxx: g++-10
|
||||
build_type: Release
|
||||
std: 17
|
||||
os: ubuntu-20.04
|
||||
with_tests: OFF
|
||||
cmake_options: -DQUILL_NO_EXCEPTIONS=ON
|
||||
|
||||
# Build and test with valgrind, sanitizers
|
||||
- cxx: g++-10
|
||||
build_type: Release
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DQUILL_USE_VALGRIND=ON
|
||||
ctest_options: -T memcheck
|
||||
install: sudo apt -o Acquire::Retries=5 install valgrind
|
||||
|
||||
# Build and test sanitizers
|
||||
- cxx: clang++-12
|
||||
build_type: Release
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DQUILL_SANITIZE_ADDRESS=ON
|
||||
|
||||
# Build and test sanitizers
|
||||
- cxx: clang++-12
|
||||
build_type: Release
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DQUILL_SANITIZE_THREAD=ON
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
${{matrix.install}}
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
env:
|
||||
CXX: ${{matrix.cxx}}
|
||||
CXXFLAGS: ${{matrix.cxxflags}}
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.cmake_options}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=${{matrix.with_tests}} \
|
||||
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`nproc`
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`nproc`
|
||||
ctest --build-config ${{matrix.build_type}} ${{matrix.ctest_options}} --parallel $threads --output-on-failure
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: True
|
|
@ -1,50 +0,0 @@
|
|||
name: macos
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-14 ]
|
||||
build_type: [ Debug, Release ]
|
||||
std: [ 17, 20 ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=ON \
|
||||
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`sysctl -n hw.logicalcpu`
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`sysctl -n hw.logicalcpu`
|
||||
ctest --build-config ${{matrix.build_type}} --parallel $threads --output-on-failure
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
name: windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# windows-2016 and windows-2019 have MSVC 2017 and 2019 installed
|
||||
# respectively: https://github.com/actions/virtual-environments.
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-2019, windows-2022 ]
|
||||
platform: [ x64 ]
|
||||
build_type: [ Debug, Release ]
|
||||
std: [ 17, 20 ]
|
||||
with_tests: [ ON ]
|
||||
|
||||
include:
|
||||
# Builds with no exceptions
|
||||
- os: windows-2019
|
||||
platform: x64
|
||||
build_type: Release
|
||||
std: 17
|
||||
with_tests: "OFF"
|
||||
cmake_options: -DQUILL_NO_EXCEPTIONS=ON
|
||||
|
||||
# Builds for ARM
|
||||
- os: windows-2019
|
||||
platform: ARM64
|
||||
build_type: Release
|
||||
std: 17
|
||||
with_tests: "OFF"
|
||||
|
||||
- os: windows-2019
|
||||
platform: ARM
|
||||
build_type: Release
|
||||
std: 17
|
||||
with_tests: "OFF"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
shell: bash # Use a bash shell for $GITHUB_WORKSPACE.
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.cmake_options}} \
|
||||
-A ${{matrix.platform}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=${{matrix.with_tests}} \
|
||||
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||
ctest --build-config ${{matrix.build_type}} --parallel $threads --output-on-failure
|
8
subprojects/quill-4.2.0/.gitignore
vendored
8
subprojects/quill-4.2.0/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
|||
# Project exclude paths
|
||||
/cmake-build-debug/
|
||||
/.vs
|
||||
/out/build/x64-Debug
|
||||
/CMakeSettings.json
|
||||
/out/build/x64-Release
|
||||
/out/build/Mingw64-Debug
|
||||
/out/build/x64-Debug-2
|
|
@ -1 +0,0 @@
|
|||
768ccd8048e2f53838c17ea6480236bb8d89fa785fb08f378539bfb0aa61ac1f
|
|
@ -1,22 +0,0 @@
|
|||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# We recommend specifying your dependencies to enable reproducible builds:
|
||||
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
File diff suppressed because it is too large
Load diff
|
@ -1,166 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.8)
|
||||
project(quill)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Options
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Builds Quill without exceptions by adding the -fno-exceptions flag to the compiler.
|
||||
option(QUILL_NO_EXCEPTIONS "Build without exceptions using -fno-exceptions flag" OFF)
|
||||
|
||||
# Disables features that rely on retrieving the thread name, which is not supported in older versions of Windows (e.g., Windows Server 2012/2016).
|
||||
# Enabling this option ensures compatibility with older Windows versions.
|
||||
option(QUILL_NO_THREAD_NAME_SUPPORT "Disable features not supported on Windows Server 2012/2016" OFF)
|
||||
|
||||
# Enables the use of _mm_prefetch, _mm_clflush, and _mm_clflushopt instructions to enhance cache coherence performance on x86 architectures.
|
||||
# When enabled, Quill will utilize these instructions on the frontend's queue operations.
|
||||
# Ensure to specify the target architecture with -march="..." when compiling to maximize compatibility and performance.
|
||||
option(QUILL_X86ARCH "Enable optimizations for cache coherence on x86 architectures using specific CPU instructions" OFF)
|
||||
|
||||
# When enabled, removes the non-prefixed `LOG_*` macros, leaving only `QUILL_LOG_*` macros available.
|
||||
# This is useful in scenarios where the original macro names conflict with those of an existing logging library.
|
||||
option(QUILL_DISABLE_NON_PREFIXED_MACROS "Disable non-prefixed macros" OFF)
|
||||
|
||||
option(QUILL_DISABLE_POSITION_INDEPENDENT_CODE "Disable position-independent code" OFF)
|
||||
|
||||
option(QUILL_BUILD_EXAMPLES "Build the examples" OFF)
|
||||
|
||||
option(QUILL_BUILD_TESTS "Build the tests (Requires https://github.com/google/googletest to be installed)" OFF)
|
||||
|
||||
option(QUILL_BUILD_BENCHMARKS "Build the benchmarks" OFF)
|
||||
|
||||
option(QUILL_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
|
||||
|
||||
option(QUILL_SANITIZE_THREAD "Enable thread sanitizer in tests (Using this option with any other compiler except clang may result to false positives)" OFF)
|
||||
|
||||
option(QUILL_CODE_COVERAGE "Enable code coverage" OFF)
|
||||
|
||||
option(QUILL_USE_VALGRIND "Use valgrind as the default memcheck tool in CTest (Requires Valgrind)" OFF)
|
||||
|
||||
option(QUILL_ENABLE_INSTALL "Enable CMake Install when Quill is not a master project" OFF)
|
||||
|
||||
option(QUILL_DOCS_GEN "Generate documentation" OFF)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Use newer policies if possible, up to most recent tested version of CMake.
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Include common compiler options
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
include(${PROJECT_SOURCE_DIR}/cmake/SetCommonCompileOptions.cmake)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Determine if quill is built as a subproject (using add_subdirectory) or if it is the master project.
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
set(QUILL_MASTER_PROJECT FALSE CACHE BOOL "Master Project" FORCE)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(QUILL_MASTER_PROJECT TRUE CACHE BOOL "Master Project" FORCE)
|
||||
endif ()
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Custom cmake functions
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/quill/cmake")
|
||||
include(Utility)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Resolve version
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
quill_extract_version()
|
||||
|
||||
project(quill VERSION ${QUILL_VERSION} LANGUAGES CXX)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Set default build to release
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build" FORCE)
|
||||
endif ()
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# Compiler config
|
||||
#---------------------------------------------------------------------------------------
|
||||
if (NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
endif ()
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Required Packages
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if (QUILL_BUILD_TESTS)
|
||||
enable_testing()
|
||||
|
||||
if (QUILL_USE_VALGRIND)
|
||||
# find valgrind
|
||||
find_program(MEMORYCHECK_COMMAND NAMES valgrind)
|
||||
if (NOT MEMORYCHECK_COMMAND)
|
||||
message(WARNING "Valgrind not found")
|
||||
endif ()
|
||||
|
||||
# set valgrind params
|
||||
set(MEMORYCHECK_COMMAND_OPTIONS "--tool=memcheck --leak-check=full --leak-resolution=med --show-leak-kinds=all --track-origins=yes --vgdb=no --fair-sched=yes")
|
||||
|
||||
# add memcheck test action to ctest
|
||||
include(CTest)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Log Info
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
if (QUILL_MASTER_PROJECT)
|
||||
option(QUILL_VERBOSE_MAKEFILE "Verbose make output" OFF)
|
||||
|
||||
message(STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})
|
||||
message(STATUS "QUILL_VERSION: ${QUILL_VERSION}")
|
||||
endif ()
|
||||
|
||||
message(STATUS "QUILL_NO_EXCEPTIONS: " ${QUILL_NO_EXCEPTIONS})
|
||||
message(STATUS "QUILL_NO_THREAD_NAME_SUPPORT: " ${QUILL_NO_THREAD_NAME_SUPPORT})
|
||||
message(STATUS "QUILL_X86ARCH: " ${QUILL_X86ARCH})
|
||||
message(STATUS "QUILL_DISABLE_NON_PREFIXED_MACROS: " ${QUILL_DISABLE_NON_PREFIXED_MACROS})
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# Verbose make file option
|
||||
#---------------------------------------------------------------------------------------
|
||||
if (QUILL_VERBOSE_MAKEFILE)
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE CACHE BOOL "Verbose output" FORCE)
|
||||
endif ()
|
||||
|
||||
# address sanitizer flags
|
||||
if (QUILL_SANITIZE_ADDRESS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer -g")
|
||||
endif ()
|
||||
|
||||
# thread sanitizer flags
|
||||
if (QUILL_SANITIZE_THREAD)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
endif ()
|
||||
|
||||
# Append extra options for coverage
|
||||
if (QUILL_CODE_COVERAGE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
|
||||
endif ()
|
||||
|
||||
# Build Examples
|
||||
if (QUILL_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif ()
|
||||
|
||||
if (QUILL_BUILD_BENCHMARKS)
|
||||
add_subdirectory(benchmarks)
|
||||
endif ()
|
||||
|
||||
add_subdirectory(quill)
|
||||
|
||||
if (QUILL_DOCS_GEN)
|
||||
# Add the cmake folder so the FindSphinx module is found
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
|
||||
add_subdirectory(docs)
|
||||
endif ()
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 - present, Odysseas Georgoudis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2021 The Meson development team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,511 +0,0 @@
|
|||
<div align="center">
|
||||
<a href="https://github.com/odygrd/quill">
|
||||
<img width="125" src="https://i.postimg.cc/DZrH8HkX/quill-circle-photos-v2-x2-colored-toned.png" alt="Quill logo">
|
||||
</a>
|
||||
<h1>Quill</h1>
|
||||
|
||||
<div>
|
||||
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Alinux">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/linux.yml?branch=master&label=linux&logo=linux&style=flat-square" alt="linux-ci" />
|
||||
</a>
|
||||
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Amacos">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/macos.yml?branch=master&label=macos&logo=apple&logoColor=white&style=flat-square" alt="macos-ci" />
|
||||
</a>
|
||||
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Awindows">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/windows.yml?branch=master&label=windows&logo=windows&logoColor=blue&style=flat-square" alt="windows-ci" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="https://codecov.io/gh/odygrd/quill">
|
||||
<img src="https://img.shields.io/codecov/c/gh/odygrd/quill/master.svg?logo=codecov&style=flat-square" alt="Codecov" />
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/odygrd/quill/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
|
||||
<img src="https://img.shields.io/codacy/grade/cd387bc34658475d98bff84db3ad5287?logo=codacy&style=flat-square" alt="Codacy" />
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/odygrd/quill">
|
||||
<img src="https://img.shields.io/codefactor/grade/github/odygrd/quill?logo=codefactor&style=flat-square" alt="CodeFactor" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="https://opensource.org/licenses/MIT">
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="license" />
|
||||
</a>
|
||||
<a href="https://en.wikipedia.org/wiki/C%2B%2B17">
|
||||
<img src="https://img.shields.io/badge/language-C%2B%2B17-red.svg?style=flat-square" alt="language" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><b>Asynchronous Low Latency C++ Logging Library</b></p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Documentation](#documentation)
|
||||
- [Features](#features)
|
||||
- [Caveats](#caveats)
|
||||
- [Performance](#performance)
|
||||
- [Quick Start](#quick-start)
|
||||
- [CMake Integration](#cmake-integration)
|
||||
- [Design](#design)
|
||||
- [License](#license)
|
||||
|
||||
| homebrew | vcpkg | conan |
|
||||
|:--------------------:|:---------------------:|:-----------------:|
|
||||
| `brew install quill` | `vcpkg install quill` | `quill/[>=1.2.3]` |
|
||||
|
||||
## Introduction
|
||||
|
||||
Quill is a high-performance, cross-platform logging library designed for C++17 and onwards.
|
||||
Quill is a production-ready logging library that has undergone extensive unit testing. It has been successfully utilized
|
||||
in production environments, including financial trading applications, providing high-performance and reliable logging
|
||||
capabilities.
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and usage instructions, please visit
|
||||
the [Quill Documentation on Read the Docs](http://quillcpp.readthedocs.io/). It provides comprehensive information on
|
||||
how to integrate and utilize Quill in your C++ applications.
|
||||
|
||||
Additionally, you can explore the [examples](http://github.com/odygrd/quill/tree/master/examples) folder in the Quill
|
||||
repository on GitHub. These examples serve as valuable resources to understand different usage scenarios and demonstrate
|
||||
the capabilities of the library.
|
||||
|
||||
## Features
|
||||
|
||||
- **Low Latency Logging**: Achieve fast logging performance with low latency. Refer to
|
||||
the [Benchmarks](http://github.com/odygrd/quill#performance) for more details.
|
||||
- **Asynchronous logging**: Log arguments and messages are formatted in a backend logging thread, effectively offloading
|
||||
the formatting overhead from the critical path.
|
||||
- **Custom Formatters**: Customize log formatting based on user-defined patterns.
|
||||
Explore [Formatters](http://quillcpp.readthedocs.io/en/latest/tutorial.html#formatters) for further details.
|
||||
- **Flexible Timestamp Generation**: Choose between rdtsc, chrono, or custom clocks (useful for simulations) for
|
||||
log message timestamp generation.
|
||||
- **Log Stack Traces**: Store log messages in a ring buffer and display them later in response to a higher severity log
|
||||
statement or on demand. Refer
|
||||
to [Backtrace Logging](http://quillcpp.readthedocs.io/en/latest/tutorial.html#backtrace-logging) for more information.
|
||||
- **Multiple Logging Sinks**: Utilize various logging targets, including:
|
||||
- Console logging with color support.
|
||||
- File logging.
|
||||
- Rotating log files based on time or size.
|
||||
- JSON logging.
|
||||
- Custom sinks.
|
||||
- **Log Message Filtering**: Apply filters to selectively process log messages. Learn more
|
||||
about [Filters](http://quillcpp.readthedocs.io/en/latest/tutorial.html#filters).
|
||||
- **Structured Logging**: Generate JSON structured logs.
|
||||
See [Structured-Log](http://quillcpp.readthedocs.io/en/latest/tutorial.html#json-log) for details.
|
||||
- **Blocking or Dropping Message Modes**: Choose between `blocking` or `dropping` message modes in the library.
|
||||
In `blocking` mode, the hot threads pause and wait when the queue is full until space becomes available, ensuring no
|
||||
message loss but introducing potential latency. In `dropping` mode, log messages beyond the queue's capacity may be
|
||||
dropped to prioritize low latency. The library provides reports on dropped messages, queue reallocations, and blocked
|
||||
hot threads for monitoring purposes.
|
||||
- **Queue Types**: The library supports different types of queues for transferring logs from the hot path to the backend
|
||||
thread: bounded queues with a fixed capacity and unbounded queues that start small and can dynamically grow.
|
||||
- **Wide Character Support**: Wide strings compatible with ASCII encoding are supported, applicable to Windows only.
|
||||
Additionally, there is support for logging STL containers consisting of wide strings. Note that chaining STL types,
|
||||
such as `std::vector<std::vector<std::wstring>>` is not supported for wide strings.
|
||||
- **Ordered Log Statements**: Log statements are ordered by timestamp even when produced by different threads,
|
||||
facilitating easier debugging of multithreaded applications.
|
||||
- **Compile-Time Log Level Stripping**: Completely strip out log levels at compile time, reducing `if` branches.
|
||||
- **Clean and Warning-Free Codebase**: Ensure a clean and warning-free codebase, even with high compiler warning levels.
|
||||
- **Crash-Safe Behavior**: Benefit from crash-safe behavior with a built-in signal handler.
|
||||
- **Type-Safe API**: Type safe api using the excellent [{fmt}](http://github.com/fmtlib/fmt) library.
|
||||
- **Huge Pages**: Benefit from support for huge pages on the hot path. This feature allows for improved performance and
|
||||
efficiency.
|
||||
|
||||
## Caveats
|
||||
|
||||
Quill may not work well with `fork()` since it spawns a background thread and `fork()` doesn't work well
|
||||
with multithreading.
|
||||
|
||||
If your application uses `fork()` and you want to log in the child processes as well, you should call
|
||||
`quill::start()` after the `fork()` call. Additionally, you should ensure that you write to different
|
||||
files in the parent and child processes to avoid conflicts.
|
||||
|
||||
For example :
|
||||
|
||||
```c++
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// DO NOT CALL THIS BEFORE FORK
|
||||
// quill::Backend::start();
|
||||
|
||||
if (fork() == 0)
|
||||
{
|
||||
quill::Backend::start();
|
||||
|
||||
// Get or create a handler to the file - Write to a different file
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"child.log");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
QUILL_LOG_INFO(logger, "Hello from Child {}", 123);
|
||||
}
|
||||
else
|
||||
{
|
||||
quill::Backend::start();
|
||||
|
||||
// Get or create a handler to the file - Write to a different file
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"parent.log");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
QUILL_LOG_INFO(logger, "Hello from Parent {}", 123);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Latency
|
||||
|
||||
The results presented in the tables below are measured in `nanoseconds (ns)`.
|
||||
|
||||
#### Logging Numbers
|
||||
|
||||
The following message is logged 100'000 times for each thread:
|
||||
|
||||
`LOG_INFO(logger, "Logging int: {}, int: {}, double: {}", i, j, d)`.
|
||||
|
||||
##### 1 Thread Logging
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 7 | 7 | 8 | 8 | 9 | 10 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 8 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 11 | 12 | 13 | 14 | 15 | 20 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 21 | 21 | 22 | 24 | 28 | 57 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 41 | 45 | 47 | 48 | 49 | 69 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 51 | 54 | 63 | 81 | 113 | 160 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 148 | 153 | 159 | 163 | 169 | 176 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 1192 | 1282 | 1363 | 1440 | 1624 | 1802 |
|
||||
|
||||
##### 4 Threads Logging Simultaneously
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 7 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 8 | 8 | 9 | 9 | 11 | 13 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 9 | 10 | 10 | 11 | 13 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 9 | 9 | 10 | 11 | 12 | 15 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 12 | 13 | 13 | 14 | 15 | 19 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 21 | 21 | 22 | 22 | 29 | 62 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 42 | 46 | 47 | 48 | 54 | 78 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 53 | 62 | 93 | 122 | 150 | 216 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 209 | 236 | 276 | 304 | 409 | 700 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 1344 | 1415 | 1489 | 1557 | 1815 | 5855 |
|
||||
|
||||
#### Logging Large Strings
|
||||
|
||||
The following message is logged 100'000 times for each thread:
|
||||
|
||||
`LOG_INFO(logger, "Logging int: {}, int: {}, string: {}", i, j, large_string)`.
|
||||
|
||||
The large string used in the log message is over 35 characters to prevent the short string optimization
|
||||
of `std::string`.
|
||||
|
||||
##### 1 Thread Logging
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 10 | 12 | 13 | 13 | 14 | 16 |
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 11 | 12 | 13 | 14 | 15 | 17 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 11 | 12 | 13 | 14 | 15 | 17 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 13 | 14 | 15 | 15 | 17 | 19 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 13 | 14 | 16 | 16 | 17 | 21 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 22 | 23 | 23 | 25 | 30 | 59 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 52 | 55 | 64 | 83 | 114 | 160 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 102 | 122 | 134 | 137 | 143 | 153 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 120 | 123 | 127 | 130 | 138 | 145 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 955 | 1049 | 1129 | 1190 | 1351 | 1539 |
|
||||
|
||||
##### 4 Threads Logging Simultaneously
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 11 | 12 | 13 | 15 | 16 | 18 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 11 | 12 | 13 | 15 | 16 | 18 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 12 | 13 | 14 | 15 | 16 | 19 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 13 | 15 | 15 | 16 | 17 | 20 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 14 | 16 | 17 | 18 | 19 | 22 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 23 | 24 | 24 | 25 | 31 | 62 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 53 | 60 | 92 | 121 | 149 | 212 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 101 | 121 | 133 | 136 | 143 | 160 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 186 | 215 | 266 | 297 | 381 | 641 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 1089 | 1164 | 1252 | 1328 | 1578 | 5563 |
|
||||
|
||||
#### Logging Complex Types
|
||||
|
||||
The following message is logged 100'000 times for each thread:
|
||||
|
||||
`LOG_INFO(logger, "Logging int: {}, int: {}, vector: {}", i, j, v)`.
|
||||
|
||||
Logging `std::vector<std::string> v` containing 16 large strings, each ranging from 50 to 60 characters.
|
||||
The strings used in the log message are over 35 characters to prevent the short string optimization of `std::string`.
|
||||
|
||||
##### 1 Thread Logging
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 52 | 54 | 56 | 58 | 60 | 63 |
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 53 | 55 | 57 | 59 | 62 | 103 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 66 | 70 | 79 | 81 | 84 | 91 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 632 | 651 | 676 | 698 | 737 | 1049 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 724 | 752 | 776 | 789 | 814 | 857 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 6242 | 6331 | 6438 | 6523 | 6782 | 7341 |
|
||||
|
||||
##### 4 Threads Logging Simultaneously
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 55 | 57 | 59 | 61 | 64 | 77 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 70 | 74 | 83 | 85 | 88 | 102 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 92 | 100 | 110 | 118 | 135 | 157 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 674 | 694 | 736 | 762 | 805 | 884 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 789 | 813 | 833 | 845 | 872 | 908 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 6500 | 6596 | 6724 | 6848 | 7560 | 9036 |
|
||||
|
||||
The benchmark was conducted on `Linux RHEL 9` with an `Intel Core i5-12600` at 4.8 GHz.
|
||||
The cpus are isolated on this system and each thread was pinned to a different CPU. `GCC 13.1` was used as the compiler.
|
||||
|
||||
The benchmark methodology involved logging 20 messages in a loop, calculating and storing the average latency for those
|
||||
20 messages, then waiting around ~2 milliseconds, and repeating this process for a specified number of iterations.
|
||||
|
||||
_In the `Quill Bounded Dropping` benchmarks, the dropping queue size is set to `262,144` bytes, which is double the
|
||||
default size of `131,072` bytes._
|
||||
|
||||
You can find the benchmark code on the [logger_benchmarks](http://github.com/odygrd/logger_benchmarks) repository.
|
||||
|
||||
### Throughput
|
||||
|
||||
The maximum throughput is measured by determining the maximum number of log messages the backend logging thread can
|
||||
write to the log file per second.
|
||||
|
||||
When measured on the same system as the latency benchmarks mentioned above the average throughput of the backend
|
||||
logging thread is `4.56 million msgs/sec`
|
||||
|
||||
While the primary focus of the library is not on throughput, it does provide efficient handling of log messages across
|
||||
multiple threads. The backend logging thread, responsible for formatting and ordering log messages from hot threads,
|
||||
ensures that all queues are emptied on a high priority basis. The backend thread internally buffers the log messages
|
||||
and then writes them later when the caller thread queues are empty or when a predefined limit,
|
||||
`backend_thread_transit_events_soft_limit`, is reached. This approach prevents the need for allocating new queues
|
||||
or dropping messages on the hot path.
|
||||
|
||||
Comparing throughput with other logging libraries in an asynchronous logging scenario has proven challenging. Some
|
||||
libraries may drop log messages, resulting in smaller log files than expected, while others only offer asynchronous
|
||||
flush, making it difficult to determine when the logging thread has finished processing all messages.
|
||||
In contrast, Quill provides a blocking flush log guarantee, ensuring that every log message from the hot threads up to
|
||||
that point is flushed to the file.
|
||||
|
||||
For benchmarking purposes, you can find the
|
||||
code [here](https://github.com/odygrd/quill/blob/master/benchmarks/backend_throughput/quill_backend_throughput.cpp).
|
||||
|
||||
### Compilation Time
|
||||
|
||||
Compile times are measured using `clang 15` and for `Release` build.
|
||||
|
||||
Below, you can find the additional headers that the library will include when you need to log, following
|
||||
the [recommended_usage](https://github.com/odygrd/quill/blob/master/examples/recommended_usage/recommended_usage.cpp)
|
||||
example
|
||||
|
||||
![quill_v4_1_compiler_profile.speedscope.png](docs%2Fquill_v4_1_compiler_profile.speedscope.png)
|
||||
|
||||
There is also a compile-time benchmark measuring the compilation time of 2000 auto-generated log statements with
|
||||
various arguments. You can find
|
||||
it [here](https://github.com/odygrd/quill/blob/master/benchmarks/compile_time/compile_time_bench.cpp). It takes approximately 30
|
||||
seconds to compile.
|
||||
|
||||
![quill_v4_compiler_bench.speedscope.png](docs%2Fquill_v4_compiler_bench.speedscope.png)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```c++
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Log to file
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"example_file_logging.log");
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
// set the log level of the logger to trace_l3 (default is info)
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_INFO(logger, "Welcome to Quill!");
|
||||
LOG_ERROR(logger, "An error message. error code {}", 123);
|
||||
LOG_WARNING(logger, "A warning message.");
|
||||
LOG_CRITICAL(logger, "A critical error.");
|
||||
LOG_DEBUG(logger, "Debugging foo {}", 1234);
|
||||
LOG_TRACE_L1(logger, "{:>30}", "right aligned");
|
||||
LOG_TRACE_L2(logger, "Positional arguments are {1} {0} ", "too", "supported");
|
||||
LOG_TRACE_L3(logger, "Support for floats {:03.2f}", 1.23456);
|
||||
}
|
||||
```
|
||||
|
||||
```c++
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_INFO(logger, "Welcome to Quill!");
|
||||
LOG_ERROR(logger, "An error message. error code {}", 123);
|
||||
LOG_WARNING(logger, "A warning message.");
|
||||
LOG_CRITICAL(logger, "A critical error.");
|
||||
LOG_DEBUG(logger, "Debugging foo {}", 1234);
|
||||
LOG_TRACE_L1(logger, "{:>30}", "right aligned");
|
||||
LOG_TRACE_L2(logger, "Positional arguments are {1} {0} ", "too", "supported");
|
||||
LOG_TRACE_L3(logger, "Support for floats {:03.2f}", 1.23456);
|
||||
}
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
[![Screenshot-2020-08-14-at-01-09-43.png](http://i.postimg.cc/02Vbt8LH/Screenshot-2020-08-14-at-01-09-43.png)](http://postimg.cc/LnZ95M4z)
|
||||
|
||||
## CMake-Integration
|
||||
|
||||
#### External
|
||||
|
||||
##### Building and Installing Quill
|
||||
|
||||
```
|
||||
git clone http://github.com/odygrd/quill.git
|
||||
mkdir cmake_build
|
||||
cd cmake_build
|
||||
cmake ..
|
||||
make install
|
||||
```
|
||||
|
||||
Note: To install in custom directory invoke cmake with `-DCMAKE_INSTALL_PREFIX=/quill/install-dir/`
|
||||
|
||||
Then use the library from a CMake project, you can locate it directly with `find_package()`
|
||||
|
||||
##### Directory Structure
|
||||
|
||||
```
|
||||
my_project/
|
||||
├── CMakeLists.txt
|
||||
├── main.cpp
|
||||
```
|
||||
|
||||
##### CMakeLists.txt
|
||||
|
||||
```cmake
|
||||
# Set only if needed - quill was installed under a custom non-standard directory
|
||||
set(CMAKE_PREFIX_PATH /test_quill/usr/local/)
|
||||
|
||||
find_package(quill REQUIRED)
|
||||
|
||||
# Linking your project against quill
|
||||
add_executable(example main.cpp)
|
||||
target_link_libraries(example PUBLIC quill::quill)
|
||||
```
|
||||
|
||||
#### Embedded
|
||||
|
||||
To embed the library directly, copy the source [folder](http://github.com/odygrd/quill/tree/master/quill/quill) to your
|
||||
project and call `add_subdirectory()` in your `CMakeLists.txt` file
|
||||
|
||||
##### Directory Structure
|
||||
|
||||
```
|
||||
my_project/
|
||||
├── quill/ (source folder)
|
||||
├── CMakeLists.txt
|
||||
├── main.cpp
|
||||
```
|
||||
|
||||
##### CMakeLists.txt
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
project(my_project)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_subdirectory(quill)
|
||||
|
||||
add_executable(my_project main.cpp)
|
||||
target_link_libraries(my_project PUBLIC quill::quill)
|
||||
```
|
||||
|
||||
#### Building Quill for Android NDK
|
||||
|
||||
To build Quill for Android NDK add the following `CMake` flags when configuring the build:
|
||||
|
||||
```
|
||||
-DQUILL_NO_THREAD_NAME_SUPPORT:BOOL=ON
|
||||
```
|
||||
|
||||
## Design
|
||||
|
||||
### Frontend (caller-thread)
|
||||
|
||||
When invoking a `LOG_` macro:
|
||||
|
||||
1. Creates a static constexpr metadata object to store `Metadata` such as the format string and source location.
|
||||
|
||||
2. Pushes the data SPSC lock-free queue. For each log message, the following variables are pushed
|
||||
|
||||
| Variable | Description |
|
||||
|------------|:--------------------------------------------------------------------------------------------------------------:|
|
||||
| timestamp | Current timestamp |
|
||||
| Metadata* | Pointer to metadata information |
|
||||
| Logger* | Pointer to the logger instance |
|
||||
| DecodeFunc | A pointer to a templated function containing all the log message argument types, used for decoding the message |
|
||||
| Args... | A serialized binary copy of each log message argument that was passed to the `LOG_` macro |
|
||||
|
||||
### Backend
|
||||
|
||||
Consumes each message from the SPSC queue, retrieves all the necessary information and then formats the message.
|
||||
Subsequently, forwards the log message to all Sinks associated with the Logger.
|
||||
|
||||
![design.jpg](docs%2Fdesign.jpg)
|
||||
|
||||
## License
|
||||
|
||||
Quill is licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
|
||||
Quill depends on third party libraries with separate copyright notices and license terms.
|
||||
Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses.
|
||||
|
||||
- ([MIT License](http://opensource.org/licenses/MIT)) {fmt} (http://github.com/fmtlib/fmt/blob/master/LICENSE.rst)
|
||||
- ([MIT License](http://opensource.org/licenses/MIT)) doctest (http://github.com/onqtam/doctest/blob/master/LICENSE.txt)
|
|
@ -1,3 +0,0 @@
|
|||
add_subdirectory(hot_path_latency)
|
||||
add_subdirectory(backend_throughput)
|
||||
add_subdirectory(compile_time)
|
|
@ -1,7 +0,0 @@
|
|||
add_executable(BENCHMARK_quill_backend_throughput quill_backend_throughput.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_backend_throughput)
|
||||
target_link_libraries(BENCHMARK_quill_backend_throughput quill)
|
||||
|
||||
add_executable(BENCHMARK_quill_backend_throughput_no_buffering quill_backend_throughput_no_buffering.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_backend_throughput_no_buffering)
|
||||
target_link_libraries(BENCHMARK_quill_backend_throughput_no_buffering quill)
|
|
@ -1,65 +0,0 @@
|
|||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
static constexpr size_t total_iterations = 4'000'000;
|
||||
|
||||
/**
|
||||
* The backend worker just spins, so we just measure the total time elapsed for total_iterations
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
// main thread affinity
|
||||
quill::detail::set_cpu_affinity(0);
|
||||
|
||||
quill::BackendOptions backend_options;
|
||||
backend_options.backend_cpu_affinity = 5;
|
||||
|
||||
// Start the logging backend thread and give it some tiem to init
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
|
||||
// Create a file sink to write to a file
|
||||
std::shared_ptr<quill::Sink> file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"quill_backend_total_time.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger(
|
||||
"bench_logger", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)");
|
||||
|
||||
quill::Frontend::preallocate();
|
||||
|
||||
// start counting the time until backend worker finishes
|
||||
auto const start_time = std::chrono::steady_clock::now();
|
||||
for (size_t iteration = 0; iteration < total_iterations; ++iteration)
|
||||
{
|
||||
LOG_INFO(logger, "Iteration: {} int: {} double: {}", iteration, iteration * 2,
|
||||
static_cast<double>(iteration) / 2);
|
||||
}
|
||||
|
||||
// block until all messages are flushed
|
||||
logger->flush_log();
|
||||
|
||||
auto const end_time = std::chrono::steady_clock::now();
|
||||
auto const delta = end_time - start_time;
|
||||
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
|
||||
|
||||
std::cout << fmtquill::format(
|
||||
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
|
||||
"log messages \n",
|
||||
total_iterations / delta_d / 1e6,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(delta).count(), total_iterations)
|
||||
<< std::endl;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
static constexpr size_t total_iterations = 4'000'000;
|
||||
|
||||
/**
|
||||
* The backend worker just spins, so we just measure the total time elapsed for total_iterations
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
// main thread affinity
|
||||
quill::detail::set_cpu_affinity(0);
|
||||
|
||||
quill::BackendOptions backend_options;
|
||||
backend_options.backend_cpu_affinity = 5;
|
||||
backend_options.transit_events_hard_limit = 1;
|
||||
|
||||
// Start the logging backend thread and give it some tiem to init
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
|
||||
// Create a file sink to write to a file
|
||||
std::shared_ptr<quill::Sink> file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"quill_backend_total_time.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger(
|
||||
"bench_logger", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)");
|
||||
|
||||
quill::Frontend::preallocate();
|
||||
|
||||
// start counting the time until backend worker finishes
|
||||
auto const start_time = std::chrono::steady_clock::now();
|
||||
for (size_t iteration = 0; iteration < total_iterations; ++iteration)
|
||||
{
|
||||
LOG_INFO(logger, "Iteration: {} int: {} double: {}", iteration, iteration * 2,
|
||||
static_cast<double>(iteration) / 2);
|
||||
}
|
||||
|
||||
// block until all messages are flushed
|
||||
logger->flush_log();
|
||||
|
||||
auto const end_time = std::chrono::steady_clock::now();
|
||||
auto const delta = end_time - start_time;
|
||||
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
|
||||
|
||||
std::cout << fmtquill::format(
|
||||
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
|
||||
"log messages \n",
|
||||
total_iterations / delta_d / 1e6,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(delta).count(), total_iterations)
|
||||
<< std::endl;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
add_subdirectory(qwrapper)
|
||||
|
||||
add_executable(BENCHMARK_quill_compile_time compile_time_bench.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_compile_time)
|
||||
target_link_libraries(BENCHMARK_quill_compile_time qwrapper_compile_time_bench)
|
File diff suppressed because it is too large
Load diff
|
@ -1,57 +0,0 @@
|
|||
import random
|
||||
|
||||
|
||||
def generate_log_statements(num_statements):
|
||||
argument_types = [
|
||||
'1', '2', '3.0', '4.0f', '5L', '6LL', '7UL', '8ULL', 'true', 'false',
|
||||
'"example1"', '"example2"', '"example3"', 'std::string("str1")',
|
||||
'std::string("str2")', 'std::string_view("view1")', 'std::string_view("view2")',
|
||||
'static_cast<short>(9)', 'static_cast<unsigned short>(10)'
|
||||
]
|
||||
random_words = ["quick", "brown", "fox", "jumps", "over", "lazy", "dog", "logging", "test", "example"]
|
||||
|
||||
statements = []
|
||||
for i in range(num_statements):
|
||||
num_args = random.randint(1, 10) # Number of arguments for the log statement
|
||||
args = random.sample(argument_types, num_args)
|
||||
placeholders = ' '.join(["{}" for _ in args])
|
||||
num_words = random.randint(3, 4) # Number of random words in the log message
|
||||
words = ' '.join(random.sample(random_words, num_words))
|
||||
statement = f' LOG_INFO(logger, "{words} {placeholders}", {", ".join(args)});'
|
||||
statements.append(statement)
|
||||
|
||||
return statements
|
||||
|
||||
|
||||
def write_to_file(filename, statements):
|
||||
with open(filename, 'w') as f:
|
||||
f.write('#include "quill/Backend.h"\n')
|
||||
f.write('#include "quill/Frontend.h"\n')
|
||||
f.write('#include "quill/LogMacros.h"\n')
|
||||
f.write('#include "quill/Logger.h"\n')
|
||||
f.write('#include "quill/sinks/ConsoleSink.h"\n')
|
||||
f.write('#include <string>\n')
|
||||
f.write('#include <utility>\n\n')
|
||||
f.write('/**\n')
|
||||
f.write(' * Trivial logging example to console\n')
|
||||
f.write(' */\n\n')
|
||||
f.write('int main()\n')
|
||||
f.write('{\n')
|
||||
f.write(' // Start the backend thread\n')
|
||||
f.write(' quill::BackendOptions backend_options;\n')
|
||||
f.write(' quill::Backend::start(backend_options);\n\n')
|
||||
f.write(' // Frontend\n')
|
||||
f.write(' auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");\n')
|
||||
f.write(' quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));\n\n')
|
||||
|
||||
for statement in statements:
|
||||
f.write(f'{statement}\n')
|
||||
|
||||
f.write('\n return 0;\n')
|
||||
f.write('}\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
num_statements = 2000
|
||||
statements = generate_log_statements(num_statements)
|
||||
write_to_file('log_benchmark.cpp', statements)
|
|
@ -1,16 +0,0 @@
|
|||
set(LIB_NAME qwrapper_compile_time_bench)
|
||||
|
||||
add_library(${LIB_NAME} STATIC
|
||||
include/qwrapper/qwrapper.h
|
||||
include/qwrapper/qwrapper.cpp)
|
||||
|
||||
# Add include directories for this library
|
||||
target_include_directories(${LIB_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Link quill dependency
|
||||
target_link_libraries(${LIB_NAME} PUBLIC quill::quill)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue