first very hacky poc
This commit is contained in:
parent
37a5ecdd2c
commit
5df04d4634
146
include/fmt/structured.h
Normal file
146
include/fmt/structured.h
Normal file
@ -0,0 +1,146 @@
|
||||
#ifndef FMT_STRUCTURED_H
|
||||
#define FMT_STRUCTURED_H
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "format.h"
|
||||
#include "ranges.h" // fixme dont rely on ranges?!
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
/**
|
||||
* Structure that holds reflection information about type T.
|
||||
* @tparam T
|
||||
*/
|
||||
template <typename T, typename Enable = void> struct reflection {
|
||||
static constexpr bool available = false;
|
||||
};
|
||||
|
||||
// fixme probably the reflection should provide accessors to the data instead of
|
||||
// (name, value) pairs. i.e. NamedField may have an operator() that returns the
|
||||
// value of a field given the object the field belongs to.
|
||||
template <typename T> struct NamedField {
|
||||
const std::string name;
|
||||
const T value;
|
||||
|
||||
bool operator==(const NamedField<T>& other) const {
|
||||
return value == other.value && name == other.name;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T> struct Extended { const T& value; };
|
||||
|
||||
// fixme: Would probably be better to visit all name field elements directly in
|
||||
// the Extended formatter instead of relying on user space specialization
|
||||
// mechanism
|
||||
/**
|
||||
* Format a field name/value pair as ".{name}={value}"
|
||||
* @tparam T
|
||||
*/
|
||||
template <typename T> struct fmt::formatter<NamedField<T>> {
|
||||
template <typename FormatContext>
|
||||
auto format(NamedField<T> const& t, FormatContext& ctx) {
|
||||
*ctx.out()++ = '.';
|
||||
std::copy(t.name.begin(), t.name.end(), ctx.out());
|
||||
*ctx.out()++ = '=';
|
||||
if constexpr (reflection<T>::available) {
|
||||
return fmt::formatter<Extended<T>>{}.format(t.value, ctx);
|
||||
} else {
|
||||
return fmt::formatter<T>{}.format(t.value, ctx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats objects as they would be written using designated initializers.
|
||||
*
|
||||
* E.g. {:e} will output Outer{.a=1, b=2, .inner=Inner{.x=3, .y=4, .z=5}}
|
||||
*
|
||||
* @tparam T The object to format.
|
||||
* @tparam C The character type used.
|
||||
*/
|
||||
// fixme remove T template parameter
|
||||
template <typename T, typename C>
|
||||
struct fmt::formatter<Extended<T>, C,
|
||||
std::enable_if_t<reflection<T>::available, void>> {
|
||||
template <typename ParseContext> constexpr auto parse(ParseContext& ctx) {
|
||||
auto it = ctx.begin();
|
||||
if (*it != '}') throw format_error("configuration not yet supported");
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(T const& t, FormatContext& ctx) {
|
||||
std::string name = reflection<T>::name();
|
||||
|
||||
std::copy(name.begin(), name.end(), ctx.out());
|
||||
*ctx.out()++ = '{';
|
||||
using base = formatter<decltype(fmt::join(reflection<T>::fields(t), ", "))>;
|
||||
base{}.format(fmt::join(reflection<T>::fields(t), ", "), ctx);
|
||||
*ctx.out()++ = '}';
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
// fixme remove this class and merge it with the fallback formatter in detail
|
||||
// namespace
|
||||
template <typename T, typename Char>
|
||||
struct fmt::formatter<T, Char,
|
||||
std::enable_if_t<reflection<T>::available, void>> {
|
||||
using extended = fmt::formatter<Extended<T>>;
|
||||
|
||||
template <typename ParseContext> constexpr auto parse(ParseContext& ctx) {
|
||||
// Parse the presentation format and store it in the formatter:
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (*(it++) != 'e') {
|
||||
throw format_error("invalid format");
|
||||
}
|
||||
_extended = extended{};
|
||||
ctx.advance_to(it);
|
||||
_extended.parse(ctx);
|
||||
|
||||
// Check if reached the end of the range:
|
||||
if (it != end && *it != '}') {
|
||||
throw format_error("invalid format");
|
||||
}
|
||||
|
||||
// Return an iterator past the end of the parsed range:
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(T const& t, FormatContext& ctx) {
|
||||
return _extended.format(t, ctx);
|
||||
}
|
||||
|
||||
private:
|
||||
extended _extended;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<reflection<T>::available>> {
|
||||
using extended = fmt::formatter<Extended<T>>;
|
||||
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto it = formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
// fixme: not sure what to return, if the closing '}' remains (like required
|
||||
// for formatter specialization)
|
||||
// some internals will throw from ErrorHandler::on_error("invalid
|
||||
// type specifier");
|
||||
ctx.advance_to(it++); // fixme: also not sure
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T& value, FormatContext& ctx) {
|
||||
return extended{}.format(value, ctx);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_STRUCTURED_H
|
||||
@ -103,6 +103,9 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
||||
endif ()
|
||||
add_fmt_test(locale-test)
|
||||
add_fmt_test(ostream-test)
|
||||
add_fmt_test(reflect-struct-test)
|
||||
# currently this requires constexpr if, variadic templates and potentially std::variant
|
||||
set_property(TARGET reflect-struct-test PROPERTY CXX_STANDARD 17)
|
||||
add_fmt_test(compile-test)
|
||||
add_fmt_test(printf-test)
|
||||
add_fmt_test(custom-formatter-test)
|
||||
|
||||
83
test/reflect-struct-test.cc
Normal file
83
test/reflect-struct-test.cc
Normal file
@ -0,0 +1,83 @@
|
||||
// Formatting library for C++ - std::ostream support tests
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#define FMT_STRING_ALIAS 1
|
||||
#include "fmt/format.h"
|
||||
|
||||
struct Inner {
|
||||
int x;
|
||||
double y;
|
||||
std::string z;
|
||||
};
|
||||
|
||||
struct Outer {
|
||||
std::string a;
|
||||
std::string b;
|
||||
Inner inner;
|
||||
};
|
||||
|
||||
// fixme: if this is enabled, then then fmt::format("{:e}", Outer{...}) fails,
|
||||
// because internally formatter<Inner> will be used to parse the "{:e}"
|
||||
// format string, which obviously is not a valid int format string.
|
||||
// This issue should be resolved by not using the NamedField formatter
|
||||
// specialization in structured.h
|
||||
//// Test that there is no issues with specializations when fmt/ostream.h is
|
||||
//// included after fmt/format.h.
|
||||
// namespace fmt {
|
||||
// template <> struct formatter<Inner> : formatter<int> {
|
||||
// template <typename FormatContext>
|
||||
// typename FormatContext::iterator format(const Inner&, FormatContext& ctx) {
|
||||
// return formatter<int>::format(42, ctx);
|
||||
// }
|
||||
//};
|
||||
//} // namespace fmt
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "fmt/structured.h"
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
|
||||
using fmt::format;
|
||||
using fmt::format_error;
|
||||
|
||||
// provide explicit reflection for custom types
|
||||
template <> struct fmt::reflection<Outer> {
|
||||
static constexpr bool available = true;
|
||||
static auto name() { return "Outer"; }
|
||||
static auto fields(const Outer& outer) {
|
||||
return std::make_tuple(NamedField<std::string>{"a", outer.a},
|
||||
NamedField<std::string>{"b", outer.b},
|
||||
NamedField<Inner>{"inner", outer.inner});
|
||||
}
|
||||
static auto values(const Outer& outer) {
|
||||
return std::make_tuple(outer.a, outer.b, outer.inner);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct fmt::reflection<Inner> {
|
||||
static constexpr bool available = true;
|
||||
static auto name() { return "Inner"; }
|
||||
static auto fields(const Inner& inner) {
|
||||
return std::make_tuple(NamedField<int>{"x", inner.x},
|
||||
NamedField<double>{"y", inner.y},
|
||||
NamedField<std::string>{"z", inner.z});
|
||||
}
|
||||
|
||||
static auto values(const Inner& inner) {
|
||||
return std::make_tuple(inner.x, inner.y, inner.z);
|
||||
}
|
||||
};
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
TEST(StructuredTest, FallbackPickup) {
|
||||
Inner inner{1, 3.1, "hello"};
|
||||
Outer outer{"a", "b", inner};
|
||||
EXPECT_EQ("Outer{.a=a, .b=b, .inner=Inner{.x=1, .y=3.1, .z=hello}}",
|
||||
fmt::format("{:e}", outer));
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user