From a17b8c32e9302e0eaf01aa6b9734a52f2813813e Mon Sep 17 00:00:00 2001 From: Hugo Sales Date: Sat, 27 Jul 2024 21:27:22 +0000 Subject: [PATCH 1/2] Add 'n', 'm' and ',' format specifiers for tuple-like items - n: Indicates that separator, opening and closing brackets should be "" - m: Indicates that both opening and closing brackets should be "" while the separator should be ": " - ,: Indicates that what follows should be the separator --- include/fmt/ranges.h | 21 ++++++++++++++++++++- test/ranges-test.cc | 11 +++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 0d3dfbd8d378..7ac08c9366aa 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -330,7 +330,26 @@ struct formatter FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { auto it = ctx.begin(); - if (it != ctx.end() && *it != '}') report_error("invalid format specifier"); + auto end = ctx.end(); + if (it != end && detail::to_ascii(*it) == 'n') { + ++it; + set_brackets({}, {}); + set_separator({}); + } else if (it != end && detail::to_ascii(*it) == 'm') { + if (std::tuple_size::value != 2) report_error("'m' format specifier can only be used for tuples with 2 elements"); + ++it; + set_brackets({}, {}); + set_separator(detail::string_literal{}); + } + if (it != end && detail::to_ascii(*it) == ',') { + ++it; + auto sep_end = it; + while (sep_end != end && *sep_end != '}') { ++sep_end; } + set_separator(basic_string_view(it, std::distance(it, sep_end))); + it = sep_end; + } + if (it != end && *it != '}') report_error("invalid format specifier"); + ctx.advance_to(it); detail::for_each(formatters_, detail::parse_empty_specs{ctx}); return it; } diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 1a5b5a706321..4ff3e8d6874a 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -23,6 +23,7 @@ #include "fmt/format.h" #include "gtest/gtest.h" +#include "gtest-extra.h" #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601 # define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY @@ -170,6 +171,14 @@ TEST(ranges_test, format_adl_begin_end) { TEST(ranges_test, format_pair) { auto p = std::pair(42, 1.5f); EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)"); + EXPECT_EQ(fmt::format("{:}", p), "(42, 1.5)"); + EXPECT_EQ(fmt::format("{:m}", p), "42: 1.5"); + EXPECT_EQ(fmt::format("{:n}", p), "421.5"); + EXPECT_EQ(fmt::format("{:, }", p), "(42 1.5)"); + EXPECT_EQ(fmt::format("{:,}", p), "(421.5)"); + EXPECT_EQ(fmt::format("{:,; }", p), "(42; 1.5)"); + EXPECT_EQ(fmt::format("{:n,; }", p), "42; 1.5"); + EXPECT_EQ(fmt::format("{:n,=}", p), "42=1.5"); } struct unformattable {}; @@ -178,6 +187,8 @@ TEST(ranges_test, format_tuple) { auto t = std::tuple(42, 1.5f, "this is tuple", 'i'); EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')"); + EXPECT_EQ(fmt::format("{:n}", t), "421.5\"this is tuple\"'i'"); + EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:m}"), t), fmt::format_error, "'m' format specifier can only be used for tuples with 2 elements"); EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()"); From a95c4ea5aaab46d0313c0a9e1432ddc1a37c7f05 Mon Sep 17 00:00:00 2001 From: Hugo Sales Date: Sun, 28 Jul 2024 21:32:09 +0000 Subject: [PATCH 2/2] Make map formatter use tuple formatter This reverts parts of 16cec4f5913ef4c5c734290cd0d1eda39cf36bd0 --- include/fmt/ranges.h | 17 ++++++++++------- test/ranges-test.cc | 19 +++++++------------ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 7ac08c9366aa..2efc8e05c546 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -583,8 +583,8 @@ struct formatter< using map_type = detail::maybe_const_range; using element_type = detail::uncvref_type; - decltype(detail::tuple::get_formatters( - detail::tuple_index_sequence())) formatters_; + formatter underlying_; + bool no_delimiters_ = false; public: @@ -605,8 +605,13 @@ struct formatter< } ctx.advance_to(it); } - detail::for_each(formatters_, detail::parse_empty_specs{ctx}); - return it; + auto it2 = underlying_.parse(ctx); + if (it2 != end && *it2 != '}') report_error("invalid format specifier"); + if (it == it2) { + underlying_.set_separator(detail::string_literal{}); + underlying_.set_brackets({}, {}); + } + return it2; } template @@ -620,9 +625,7 @@ struct formatter< for (auto&& value : map) { if (i > 0) out = detail::copy(sep, out); ctx.advance_to(out); - detail::for_each2(formatters_, mapper.map(value), - detail::format_tuple_element{ - 0, ctx, detail::string_literal{}}); + underlying_.format(mapper.map(value), ctx); ++i; } basic_string_view close = detail::string_literal{}; diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 4ff3e8d6874a..ff49027c16e7 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -89,6 +89,13 @@ TEST(ranges_test, format_map) { auto m = std::map{{"one", 1}, {"two", 2}}; EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}"); EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2"); + EXPECT_EQ(fmt::format("{:n:}", m), "\"one\": 1, \"two\": 2"); + EXPECT_EQ(fmt::format("{:n:m}", m), "\"one\": 1, \"two\": 2"); + EXPECT_EQ(fmt::format("{:n:n}", m), "\"one\"1, \"two\"2"); + EXPECT_EQ(fmt::format("{::n}", m), "{\"one\"1, \"two\"2}"); + EXPECT_EQ(fmt::format("{:n:m}", m), "\"one\": 1, \"two\": 2"); + EXPECT_EQ(fmt::format("{:n:n,;}", m), "\"one\";1, \"two\";2"); + EXPECT_EQ(fmt::format("{:n:,;}", m), "(\"one\";1), (\"two\";2)"); } struct test_map_value {}; @@ -101,18 +108,6 @@ template <> struct formatter : formatter { } }; -template -struct formatter> : formatter { - auto format(std::pair, format_context& ctx) const - -> format_context::iterator { - return ctx.out(); - } -}; - -template -struct is_tuple_formattable, char> - : std::false_type {}; - FMT_END_NAMESPACE TEST(ranges_test, format_map_custom_pair) {