diff --git a/include/fmt/std.h b/include/fmt/std.h index 379b186efbb2..ef36a70a4e9e 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -620,6 +620,8 @@ struct formatter< T, char, typename std::enable_if::value>::type> { private: + format_specs specs_; + detail::arg_ref 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 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 + 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 : formatter { template 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::format(e, ctx); } catch (...) { - return detail::write(ctx.out(), string_view("unknown exception")); + return this->write_padded(ctx, string_view("unknown exception")); } } }; diff --git a/test/std-test.cc b/test/std-test.cc index 6fc92938a1c3..bba264afeda1 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -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)),