mirror of
https://github.com/fmtlib/fmt.git
synced 2026-07-30 16:26:27 +08:00
Support width and alignment in std::exception formatter
The formatter for std::exception (and std::exception_ptr) previously
accepted only the optional 't' type-name specifier, so standard fill,
align and width specifiers were rejected with "unknown format
specifier". This made it impossible to pad or align exception messages,
e.g. when laying them out in a log column.
Parse the standard fill/align/width specifiers (as the std::filesystem::
path formatter already does) and apply them when writing the message.
Dynamic width ({:{}}) and the 'none'/'unknown exception' cases for
exception_ptr are handled too. Existing behavior ({}, {:t}, nested
exception unwinding) is unchanged.
This commit is contained in:
parent
7b4ef1c814
commit
b059132e13
@ -620,6 +620,8 @@ struct formatter<
|
||||
T, char,
|
||||
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
detail::arg_ref<char> width_ref_;
|
||||
bool with_typename_ = false;
|
||||
|
||||
public:
|
||||
@ -627,7 +629,14 @@ struct formatter<
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
if (*it == 't') {
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
char c = *it;
|
||||
if ((c >= '0' && c <= '9') || c == '{')
|
||||
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
|
||||
if (it != end && *it == 't') {
|
||||
++it;
|
||||
with_typename_ = FMT_USE_RTTI != 0;
|
||||
}
|
||||
@ -637,7 +646,20 @@ struct formatter<
|
||||
template <typename Context>
|
||||
auto format(const std::exception& ex, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return write(ctx.out(), ex);
|
||||
auto buf = memory_buffer();
|
||||
write(appender(buf), ex);
|
||||
return write_padded(ctx, string_view(buf.data(), buf.size()));
|
||||
}
|
||||
|
||||
protected:
|
||||
// Applies the parsed fill/align/width to an already-formatted message.
|
||||
template <typename Context>
|
||||
auto write_padded(Context& ctx, string_view message) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
return detail::write(ctx.out(), message, specs);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -675,13 +697,13 @@ template <> struct formatter<std::exception_ptr> : formatter<std::exception> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::exception_ptr& ep, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
if (!ep) return detail::write(ctx.out(), string_view("none"));
|
||||
if (!ep) return this->write_padded(ctx, string_view("none"));
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (const std::exception& e) {
|
||||
return formatter<std::exception>::format(e, ctx);
|
||||
} catch (...) {
|
||||
return detail::write(ctx.out(), string_view("unknown exception"));
|
||||
return this->write_padded(ctx, string_view("unknown exception"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -465,6 +465,43 @@ TEST(std_test, exception_ptr) {
|
||||
#endif // FMT_USE_RTTI
|
||||
}
|
||||
|
||||
TEST(std_test, exception_align) {
|
||||
// The exception formatter supports standard width, fill and alignment.
|
||||
auto ex = std::runtime_error("boom");
|
||||
EXPECT_EQ(fmt::format("{:>8}", ex), " boom");
|
||||
EXPECT_EQ(fmt::format("{:<8}", ex), "boom ");
|
||||
EXPECT_EQ(fmt::format("{:^8}", ex), " boom ");
|
||||
EXPECT_EQ(fmt::format("{:*>8}", ex), "****boom");
|
||||
EXPECT_EQ(fmt::format("{:{}}", ex, 8), "boom ");
|
||||
// A width smaller than the message leaves the message untouched.
|
||||
EXPECT_EQ(fmt::format("{:>2}", ex), "boom");
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
// Alignment combines with the 't' (type name) presentation type.
|
||||
try {
|
||||
using namespace my_ns1::my_ns2;
|
||||
throw my_exception("oops");
|
||||
} catch (const std::exception& e) {
|
||||
auto base = fmt::format("{:t}", e);
|
||||
auto padded = fmt::format("{:*<40t}", e);
|
||||
EXPECT_EQ(padded.size(), 40u);
|
||||
EXPECT_EQ(padded.substr(0, base.size()), base);
|
||||
EXPECT_EQ(padded.substr(base.size()), std::string(40 - base.size(), '*'));
|
||||
}
|
||||
#endif // FMT_USE_RTTI
|
||||
|
||||
// exception_ptr honors the same specifiers, for both the empty ("none")
|
||||
// and the non-empty case.
|
||||
std::exception_ptr enull = nullptr;
|
||||
EXPECT_EQ(fmt::format("{:>8}", enull), " none");
|
||||
std::exception_ptr ep;
|
||||
try {
|
||||
throw std::runtime_error("bang");
|
||||
} catch (...) {
|
||||
ep = std::current_exception();
|
||||
}
|
||||
EXPECT_EQ(fmt::format("{:>8}", ep), " bang");
|
||||
}
|
||||
#if FMT_USE_RTTI
|
||||
TEST(std_test, type_info) {
|
||||
EXPECT_EQ(fmt::format("{}", typeid(std::runtime_error)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user