From e3f7f3a2e9544ec2db3d6997c78b77f498948ff8 Mon Sep 17 00:00:00 2001 From: Remotion Date: Thu, 10 May 2018 16:11:00 +0200 Subject: [PATCH] Add support for ranges, containers and tuple-like types in fmt/ranges.h --- include/fmt/ranges.h | 231 +++++++++++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/ranges-test.cc | 88 +++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 include/fmt/ranges.h create mode 100644 test/ranges-test.cc diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h new file mode 100644 index 000000000000..3e8480a1ac18 --- /dev/null +++ b/include/fmt/ranges.h @@ -0,0 +1,231 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#include "format.h" +#include + +// output only up to N items from the range. +#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT +# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 +#endif + +FMT_BEGIN_NAMESPACE + +template +struct formatting_base { + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } +}; + +template +struct formatting_range : formatting_base { + static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = + FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range. + Char prefix = '{'; + Char delimiter = ','; + Char postfix = '}'; + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +template +struct formatting_tuple : formatting_base { + Char prefix = '('; + Char delimiter = ','; + Char postfix = ')'; + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +namespace internal { + +template +void copy(const RangeT &range, OutputIterator out) { + for (const auto &it : range) { + *out++ = it; + } +} + +template +void copy(const char *str, OutputIterator out) { + const char *p_curr = str; + while (*p_curr) { + *out++ = *p_curr++; + } +} + +template +void copy(char ch, OutputIterator out) { + *out++ = ch; +} + +/// Return true value if T has std::string interface, like std::string_view. +template +class is_like_std_string { + template + static auto check(U *p) -> + decltype(p->find('a'), p->length(), p->data(), int()); + template + static void check(...); + + public: + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value; +}; + +template +struct conditional_helper {}; + +template +struct is_range_ : std::false_type {}; + +template +struct is_range_().begin()), + decltype(std::declval().end())>, + void>::type> : std::true_type {}; + +template +struct is_range { + static FMT_CONSTEXPR_DECL const bool value = + is_range_::value && !is_like_std_string::value; +}; + +/// tuple_size and tuple_element check. +template +class is_tuple_like_ { + template + static auto check(U *p) -> + decltype(std::tuple_size::value, + std::declval::type>(), int()); + template + static void check(...); + + public: + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value; +}; + +template +struct is_tuple_like { + static FMT_CONSTEXPR_DECL const bool value = + is_tuple_like_::value && !is_range_::value; +}; + +template +void for_each(std::index_sequence, Tuple &&tup, F &&f) noexcept { + using std::get; + // using free function get(T) now. + const int _[] = {0, ((void)f(get(tup)), 0)...}; + (void)_; // blocks warnings +} + +template +FMT_CONSTEXPR std::make_index_sequence::value> +get_indexes(T const &) { return {}; } + +template +void for_each(Tuple &&tup, F &&f) { + const auto indexes = get_indexes(tup); + for_each(indexes, std::forward(tup), std::forward(f)); +} +} // namespace internal + +template +struct formatter::value>::type> { + + fmt::formatting_tuple formatting; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return formatting.parse(ctx); + } + + template + auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + std::size_t i = 0; + internal::copy(formatting.prefix, out); + internal::for_each(values, [&](const auto &v) { + if (i > 0) { + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + if (formatting.add_delimiter_spaces && i > 0) { + format_to(out, " {}", v); + } else { + format_to(out, "{}", v); + } + ++i; + }); + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + + return ctx.out(); + } +}; + +template +struct formatter< RangeT, Char, + typename std::enable_if::value>::type> { + + fmt::formatting_range formatting; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return formatting.parse(ctx); + } + + template + auto format(const RangeT &values, FormatContext &ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + internal::copy(formatting.prefix, out); + std::size_t i = 0; + for (const auto &it : values) { + if (i > 0) { + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + if (formatting.add_delimiter_spaces && i > 0) { + format_to(out, " {}", it); + } else { + format_to(out, "{}", it); + } + if (++i > formatting.range_length_limit) { + format_to(out, " ... "); + break; + } + } + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + return ctx.out(); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b0c479cbd65a..07b10a0a0b75 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -92,6 +92,7 @@ add_fmt_test(printf-test) add_fmt_test(time-test) add_fmt_test(util-test mock-allocator.h) add_fmt_test(custom-formatter-test) +add_fmt_test(ranges-test) # Enable stricter options for one test to make sure that the header is free of # warnings. diff --git a/test/ranges-test.cc b/test/ranges-test.cc new file mode 100644 index 000000000000..76c37a112229 --- /dev/null +++ b/test/ranges-test.cc @@ -0,0 +1,88 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#include "fmt/ranges.h" + +#include "gtest/gtest.h" + +#include +#include +#include +#include + +TEST(RangesTest, FormatVector) { + std::vector iv{1, 2, 3, 5, 7, 11}; + auto ivf = fmt::format("{}", iv); + EXPECT_EQ("{1, 2, 3, 5, 7, 11}", ivf); +} + +TEST(RangesTest, FormatVector2) { + std::vector> ivv{{1, 2}, {3, 5}, {7, 11}}; + auto ivf = fmt::format("{}", ivv); + EXPECT_EQ("{{1, 2}, {3, 5}, {7, 11}}", ivf); +} + +TEST(RangesTest, FormatMap) { + std::map simap{{"one", 1}, {"two", 2}}; + EXPECT_EQ("{(one, 1), (two, 2)}", fmt::format("{}", simap)); +} + +TEST(RangesTest, FormatPair) { + std::pair pa1{42, 3.14159265358979f}; + EXPECT_EQ("(42, 3.14159)", fmt::format("{}", pa1)); +} + +TEST(RangesTest, FormatTuple) { + std::tuple tu1{42, 3.14159265358979f, + "this is tuple"}; + EXPECT_EQ("(42, 3.14159, this is tuple)", fmt::format("{}", tu1)); +} + +/// check if 'if constexpr' is supported. +#if (__cplusplus > 201402L) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) + +struct my_struct { + int32_t i; + std::string str; // can throw + template + decltype(auto) get() const noexcept { + if constexpr (N == 0) + return i; + else if constexpr (N == 1) + return fmt::string_view{str}; + } +}; + +template +decltype(auto) get(const my_struct& s) noexcept { + return s.get(); +} + +namespace std { + +template <> +struct tuple_size : std::integral_constant {}; + +template +struct tuple_element { + using type = decltype(std::declval().get()); +}; + +} // namespace std + +TEST(RangesTest, FormatStruct) { + my_struct mst{13, "my struct"}; + EXPECT_EQ("(13, my struct)", fmt::format("{}", mst)); +} + +#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG > + // 201402L && _MSC_VER >= 1910)