diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h new file mode 100644 index 00000000..e8465475 --- /dev/null +++ b/include/fmt/ranges.h @@ -0,0 +1,224 @@ +// 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 + +namespace fmt { + +template +struct formatting_base { + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } +}; + +template +struct formatting_range : formatting_base +{ + Char prefix = '{'; + Char delimiter = ','; + Char postfix = '}'; + bool add_spaces = true; +}; + +template +struct formatting_tuple : formatting_base +{ + Char prefix = '['; + Char delimiter = ','; + Char postfix = ']'; + bool add_spaces = true; +}; + +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(const char ch, OutputIterator out) { + *out++ = ch; +} + +} // namespace fmt + + +namespace fmt { +namespace meta { + +/// 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 const bool value = !std::is_void< decltype(check(nullptr)) >::value; +}; + +template +constexpr bool is_like_std_string_v = is_like_std_string::value; + +template +struct conditional_helper {}; + +template +struct is_range_ : std::false_type {}; + +template +struct is_range_().begin()), + decltype(std::declval().end()) + >, void> +> : std::true_type {}; + +template +constexpr bool is_range_v = 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< U >::value, + std::declval::type>(), + int()); + template static void check(...); +public: + static constexpr bool value = !std::is_void< decltype(check(nullptr)) >::value; +}; + +template +constexpr bool is_tuple_like_v = 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 +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 meta +} // namespace fmt + +namespace fmt { + +// ===================================================================================================================== +template +struct formatter< TupleT, Char + , std::enable_if_t> > +{ + fmt::formatting_tuple formating; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return formating.parse(ctx); + } + + template + auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + std::ptrdiff_t i = 0; + fmt::copy(formating.prefix, out); + fmt::meta::for_each(values, [&](const auto &v) { + if (i++ > 0) { fmt::copy(formating.delimiter, out); } + if (formating.add_spaces) { format_to(out, " {}", v); } + else { format_to(out, "{}", v); } + }); + if (formating.add_spaces) { *out++ = ' '; } + fmt::copy(formating.postfix, out); + + return ctx.out(); + } +}; + +} // namespace fmt + + + +namespace fmt { + + +template +struct formatter > > +{ + static constexpr std::ptrdiff_t range_length_limit = FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range. + + fmt::formatting_range formating; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return formating.parse(ctx); + } + + template + auto format(const RangeT &values, FormatContext &ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + fmt::copy(formating.prefix, out); + std::ptrdiff_t i = 0; + for (const auto& it : values) { + if (i > 0) { fmt::copy(formating.delimiter, out); } + if (formating.add_spaces) { format_to(out, " {}", it); } + else { format_to(out, "{}", it); } + if (++i > range_length_limit) { + format_to(out, " ... "); + break; + } + } + if (formating.add_spaces) { *out++ = ' '; } + fmt::copy(formating.postfix, out); + return ctx.out(); + } +}; + +} // namespace fmt + + +#endif // FMT_RANGES_H_ \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b0c479cb..07b10a0a 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 00000000..7dc770bf --- /dev/null +++ b/test/ranges-test.cc @@ -0,0 +1,53 @@ +// 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. + +#ifdef WIN32 +#define _CRT_SECURE_NO_WARNINGS +#endif + +#define FMT_HEADER_ONLY 1 +#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)); +} +