Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion include/rfl/yaml/Reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ struct Reader {
static constexpr bool has_custom_constructor =
(requires(InputVarType var) { T::from_yaml_obj(var); });

Reader(const std::string_view& _yaml_str) noexcept : yaml_str_(_yaml_str) {}

rfl::Result<InputVarType> get_field_from_array(
const size_t _idx, const InputArrayType& _arr) const noexcept {
if (_idx >= _arr.node_.size()) {
Expand Down Expand Up @@ -76,7 +78,22 @@ struct Reader {
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>>()) {
return _var.node_.as<std::remove_cvref_t<T>>();
auto result = _var.node_.as<std::remove_cvref_t<T>>();
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
// In case of multi-line YAML literal strings, yaml-cpp may parse an
// extra new line which is not there intentionally. This may break
// multiple re-serialization checks, for this reason we trim trailing
// new-lines here.
//
// This is only done for literal blocks which doesn't have tags or anchors
if (_var.node_.Tag() == "!" && yaml_str_[_var.node_.Mark().pos] == '|') {
auto last_non_new_line = result.find_last_not_of("\r\n");
if (last_non_new_line != std::string::npos) {
result = result.substr(0, last_non_new_line + 1);
}
}
}
return result;

} else if constexpr (std::is_integral<std::remove_cvref_t<T>>()) {
return static_cast<T>(_var.node_.as<std::remove_cvref_t<int64_t>>());
Expand Down Expand Up @@ -141,6 +158,9 @@ struct Reader {
return error(e.what());
}
}

private:
std::string_view yaml_str_;
};

} // namespace yaml
Expand Down
31 changes: 29 additions & 2 deletions include/rfl/yaml/Writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ namespace rfl::yaml {

class RFL_API Writer {
public:
enum Flags {
no_flags = 0,

/// A string value which has at least one new-line character will be written
/// as multiline YAML literal. It costs one call to std::basic_string::find
/// on all string values.
string_multiline_literal = 1,

/// All string values will be written as multiline YAML literal
string_all_literal = 2
};

struct YAMLArray {};

struct YAMLObject {};
Expand All @@ -25,7 +37,7 @@ class RFL_API Writer {
using OutputObjectType = YAMLObject;
using OutputVarType = YAMLVar;

Writer(const Ref<YAML::Emitter>& _out);
Writer(const Ref<YAML::Emitter>& _out, Flags _flags = no_flags);

~Writer();

Expand Down Expand Up @@ -82,14 +94,27 @@ class RFL_API Writer {

void end_object(OutputObjectType* _obj) const;

private:
template <class T>
void insert_literal_block_if_needed(const T& _var) const {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>()) {
if (flags_ & string_all_literal || (flags_ & string_multiline_literal && _var.find('\n') != std::string::npos)) {
(*out_) << YAML::Literal;
}
}
}

public:
template <class T>
OutputVarType insert_value(const std::string_view& _name,
const T& _var) const {
if constexpr (std::is_same<std::remove_cvref_t<T>, std::string>() ||
std::is_same<std::remove_cvref_t<T>, bool>() ||
std::is_same<std::remove_cvref_t<T>,
std::remove_cvref_t<decltype(YAML::Null)>>()) {
(*out_) << YAML::Key << std::string(_name) << YAML::Value << _var;
(*out_) << YAML::Key << std::string(_name) << YAML::Value;
insert_literal_block_if_needed(_var);
(*out_) << _var;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
// std::to_string is necessary to ensure that floating point values are
// always written as floats.
Expand All @@ -110,6 +135,7 @@ class RFL_API Writer {
std::is_same<std::remove_cvref_t<T>, bool>() ||
std::is_same<std::remove_cvref_t<T>,
std::remove_cvref_t<decltype(YAML::Null)>>()) {
insert_literal_block_if_needed(_var);
(*out_) << _var;
} else if constexpr (std::is_floating_point<std::remove_cvref_t<T>>()) {
// std::to_string is necessary to ensure that floating point values are
Expand All @@ -135,6 +161,7 @@ class RFL_API Writer {

public:
const Ref<YAML::Emitter> out_;
Flags flags_;
};

} // namespace rfl::yaml
Expand Down
6 changes: 3 additions & 3 deletions include/rfl/yaml/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ 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();
auto read(const InputVarType& _var, const std::string& _yaml_str) {
const auto r = Reader(_yaml_str);
using ProcessorsType = Processors<Ps...>;
static_assert(!ProcessorsType::no_field_names_,
"The NoFieldNames processor is not supported for BSON, XML, "
Expand All @@ -32,7 +32,7 @@ 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);
return read<T, Ps...>(var, _yaml_str);
} catch (std::exception& e) {
return error(e.what());
}
Expand Down
8 changes: 4 additions & 4 deletions include/rfl/yaml/write.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ namespace yaml {

/// Writes a YAML into an ostream.
template <class... Ps>
std::ostream& write(const auto& _obj, std::ostream& _stream) {
std::ostream& write(const auto& _obj, std::ostream& _stream, Writer::Flags _flags = Writer::Flags::no_flags) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto out = Ref<YAML::Emitter>::make();
auto w = Writer(out);
auto w = Writer(out, _flags);
using ProcessorsType = Processors<Ps...>;
static_assert(!ProcessorsType::no_field_names_,
"The NoFieldNames processor is not supported for BSON, XML, "
Expand All @@ -33,11 +33,11 @@ std::ostream& write(const auto& _obj, std::ostream& _stream) {

/// Returns a YAML string.
template <class... Ps>
std::string write(const auto& _obj) {
std::string write(const auto& _obj, Writer::Flags _flags = Writer::Flags::no_flags) {
using T = std::remove_cvref_t<decltype(_obj)>;
using ParentType = parsing::Parent<Writer>;
const auto out = Ref<YAML::Emitter>::make();
auto w = Writer(out);
auto w = Writer(out, _flags);
using ProcessorsType = Processors<Ps...>;
static_assert(!ProcessorsType::no_field_names_,
"The NoFieldNames processor is not supported for BSON, XML, "
Expand Down
2 changes: 1 addition & 1 deletion src/rfl/yaml/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace rfl::yaml {

Writer::Writer(const Ref<YAML::Emitter>& _out) : out_(_out) {}
Writer::Writer(const Ref<YAML::Emitter>& _out, Flags _flags) : out_(_out), flags_(_flags) {}

Writer::~Writer() = default;

Expand Down
48 changes: 48 additions & 0 deletions tests/yaml/test_multiline.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <rfl.hpp>
#include <string>

#include "write_and_read.hpp"

struct MultilineTestStruct {
std::string normal_string;
std::string multiline_string;
};

namespace test_multiline {
TEST(yaml, test_multiline) {
const auto test = MultilineTestStruct{.normal_string = "The normal string",
.multiline_string =
R"(Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.)"
};

write_and_read(test, rfl::yaml::Writer::string_multiline_literal);
write_and_read(test, rfl::yaml::Writer::string_all_literal);
}

TEST(yaml, test_multiline_read) {
const auto test = MultilineTestStruct{.normal_string = "The normal string",
.multiline_string = "Foobar\n\n"
};

const std::string random_yaml(
R"(
normal_string: |
The normal string


multiline_string: "Foobar\n\n"

)"
);

auto read_result = rfl::yaml::read<MultilineTestStruct>(random_yaml);
EXPECT_TRUE(read_result.has_value());
EXPECT_EQ(read_result.value().normal_string, test.normal_string);
EXPECT_EQ(read_result.value().multiline_string, test.multiline_string);
}
} // namespace test_multiline
6 changes: 3 additions & 3 deletions tests/yaml/write_and_read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
#include <rfl/yaml.hpp>

template <class... Ps>
void write_and_read(const auto& _struct) {
void write_and_read(const auto& _struct, rfl::yaml::Writer::Flags _flags = rfl::yaml::Writer::Flags::no_flags) {
using T = std::remove_cvref_t<decltype(_struct)>;
const auto serialized1 = rfl::yaml::write<Ps...>(_struct);
const auto serialized1 = rfl::yaml::write<Ps...>(_struct, _flags);
const auto res = rfl::yaml::read<T, Ps...>(
std::string_view(serialized1.c_str(), serialized1.size()));
EXPECT_TRUE(res && true) << "Test failed on read. Error: "
<< res.error().what();
const auto serialized2 = rfl::yaml::write<Ps...>(res.value());
const auto serialized2 = rfl::yaml::write<Ps...>(res.value(), _flags);
EXPECT_EQ(serialized1, serialized2);
}

Expand Down
Loading