diff --git a/include/openvic-dataloader/csv/LineObject.hpp b/include/openvic-dataloader/csv/LineObject.hpp index 43aeea8..f7122e1 100644 --- a/include/openvic-dataloader/csv/LineObject.hpp +++ b/include/openvic-dataloader/csv/LineObject.hpp @@ -15,6 +15,9 @@ #include +#include +#include + namespace ovdl::csv { /// LineObject should be able to recognize the differences between: /// Input -> Indexes == "" @@ -168,4 +171,101 @@ namespace ovdl::csv { } return stream; } -} \ No newline at end of file +} + +// Supports s for designating a separator, including s{{ and s}} to escape brackets +// Also supports s{} for dynamic separator which supports multi-character separators +template<> +struct fmt::formatter : formatter { + struct { + arg_id_kind kind = arg_id_kind::none; + detail::arg_ref ref; + std::string value = ";"; + } sep_spec {}; + + constexpr format_parse_context::iterator parse(format_parse_context& ctx) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *ctx.begin() == '}') { + return it; + } + + if (*it == 's') { + ++it; + + if (*it == '{') { + ++it; + + if (*it == '{') { + ++it; + sep_spec.value = { it, it + 1 }; + } else if (*it == '}') { + ++it; + sep_spec.ref = ctx.next_arg_id(); + sep_spec.kind = arg_id_kind::index; + } else { + it = detail::parse_arg_id(it, end, detail::dynamic_spec_handler { ctx, sep_spec.ref, sep_spec.kind }); + } + } else if (*it == '}') { + ++it; + + if (*it != '}') { + report_error("invalid format string"); + return it; + } + } else { + sep_spec.value = { it, it + 1 }; + ++it; + } + } + + return it; + } + + struct separator_spec_getter { + template::value)> + constexpr auto operator()(T value) -> string_view { + return value; + } + + constexpr auto operator()(const char* value) -> string_view { + return value; + } + + template::value)> + constexpr auto operator()(T) -> string_view { + report_error("separator is not string-like"); + return {}; + } + }; + + format_context::iterator format(ovdl::csv::LineObject line, format_context& ctx) const { + string_view separator = sep_spec.value; + if (sep_spec.kind != arg_id_kind::none) { + auto arg = sep_spec.kind == arg_id_kind::index ? ctx.arg(sep_spec.ref.index) : ctx.arg(sep_spec.ref.name); + if (!arg) { + report_error("argument not found"); + } + separator = arg.visit(separator_spec_getter {}); + } + + auto out = ctx.out(); + ovdl::csv::LineObject::position_type sep_index = 0; + for (const auto& [pos, val] : line) { + while (sep_index < pos) { + out = detail::write(out, separator); + ctx.advance_to(out); + sep_index++; + } + if (std::any_of(val.begin(), val.end(), [&](char c) { return (separator.size() == 1 && c == separator[0]) || std::isspace(c); })) { + out = detail::write(out, '"'); + ctx.advance_to(out); + out = formatter::format(val, ctx); + out = detail::write(out, '"'); + ctx.advance_to(out); + } else { + out = formatter::format(val, ctx); + } + } + return out; + } +}; \ No newline at end of file diff --git a/tests/src/csv/LineObject.cpp b/tests/src/csv/LineObject.cpp index 4ab73b6..2dd9e59 100644 --- a/tests/src/csv/LineObject.cpp +++ b/tests/src/csv/LineObject.cpp @@ -28,6 +28,10 @@ TEST_CASE("LineObject", "[line-object]") { CHECK(ss.str() == ""sv); } + + SECTION("fmt to_string") { + CHECK(fmt::to_string(line) == ""sv); + } } SECTION("no prefix") { @@ -50,6 +54,10 @@ TEST_CASE("LineObject", "[line-object]") { CHECK(ss.str() == "a;b;c"sv); } + + SECTION("fmt to_string") { + CHECK(fmt::to_string(line) == "a;b;c"sv); + } } SECTION("no suffix") { @@ -88,6 +96,10 @@ TEST_CASE("LineObject", "[line-object]") { CHECK(ss.str() == ";a;b;c"sv); } + + SECTION("fmt to_string") { + CHECK(fmt::to_string(line) == ";a;b;c"sv); + } } SECTION("prefix and suffix with spaces") { @@ -112,6 +124,10 @@ TEST_CASE("LineObject", "[line-object]") { CHECK(ss.str() == ";\"a b\";\"c d\";\"e f\""sv); } + + SECTION("fmt to_string") { + CHECK(fmt::to_string(line) == ";\"a b\";\"c d\";\"e f\""sv); + } } SECTION("prefix and suffix with separators") { @@ -136,6 +152,10 @@ TEST_CASE("LineObject", "[line-object]") { CHECK(ss.str() == ";\"a;b\";\"c;d\";\"e;f\""sv); } + + SECTION("fmt to_string") { + CHECK(fmt::to_string(line) == ";\"a;b\";\"c;d\";\"e;f\""sv); + } } SECTION("prefix and suffix with custom char separator") { @@ -151,6 +171,8 @@ TEST_CASE("LineObject", "[line-object]") { ss << line.use_sep("|") << std::flush; CHECK(ss.str() == "|a;b|c;d|e;f"sv); + + CHECK(fmt::format("{:s|}", line) == "|a;b|c;d|e;f"sv); } SECTION("prefix and suffix with custom char separator and containing the separator") { @@ -175,6 +197,10 @@ TEST_CASE("LineObject", "[line-object]") { CHECK(ss.str() == "|\"a|b\"|\"c|d\"|\"e|f\""sv); } + + SECTION("fmt format") { + CHECK(fmt::format("{:s|}", line) == "|\"a|b\"|\"c|d\"|\"e|f\""sv); + } } SECTION("prefix and suffix with custom string_view separator") { @@ -192,5 +218,9 @@ TEST_CASE("LineObject", "[line-object]") { CHECK(ss.str() == "heya;bheyc;dheye;f"sv); } + + SECTION("fmt format") { + CHECK(fmt::format("{:s{}}", line, "hey") == "heya;bheyc;dheye;f"sv); + } } } \ No newline at end of file