-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for ranges, containers and types tuple interface... #735
Changes from 2 commits
df46ef0
50ee89b
951b743
a442c77
5717bd2
4cfc6c4
648b937
f101d62
7a2099e
a7041e9
a70b480
d2ef18c
f65bcb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <type_traits> | ||
|
||
// 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 <typename Char> | ||
struct formatting_base { | ||
template <typename ParseContext> | ||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } | ||
}; | ||
|
||
template <typename Char, typename Enable = void> | ||
struct formatting_range : formatting_base<Char> | ||
{ | ||
Char prefix = '{'; | ||
Char delimiter = ','; | ||
Char postfix = '}'; | ||
bool add_spaces = true; | ||
}; | ||
|
||
template <typename Char, typename Enable = void> | ||
struct formatting_tuple : formatting_base<Char> | ||
{ | ||
Char prefix = '['; | ||
Char delimiter = ','; | ||
Char postfix = ']'; | ||
bool add_spaces = true; | ||
}; | ||
|
||
template<typename RangeT, typename OutputIterator> | ||
void copy(const RangeT &range, OutputIterator out) { | ||
for (const auto& it : range) { | ||
*out++ = it; | ||
} | ||
} | ||
|
||
template<typename OutputIterator> | ||
void copy(const char *str, OutputIterator out) { | ||
const char* p_curr = str; | ||
while (*p_curr) { | ||
*out++ = *p_curr++; | ||
} | ||
} | ||
|
||
template<typename OutputIterator> | ||
void copy(const char ch, OutputIterator out) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
*out++ = ch; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest putting all of the above in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
} // namespace fmt | ||
|
||
|
||
namespace fmt { | ||
namespace meta { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
/// Return true value if T has std::string interface, like std::string_view. | ||
template<typename T> | ||
class is_like_std_string { | ||
template<typename U> static auto check(U* p) -> decltype( | ||
p->find('a') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to require There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is length() and data() really enough to be sure that this is std::string like type? |
||
, p->length() | ||
, p->data() | ||
, int()); | ||
template<typename > static void check(...); | ||
public: | ||
static const bool value = !std::is_void< decltype(check<T>(nullptr)) >::value; | ||
}; | ||
|
||
template<typename T> | ||
constexpr bool is_like_std_string_v = is_like_std_string<T>::value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here and elsewhere: constexpr -> FMT_CONSTEXPR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
template<typename... Ts> | ||
struct conditional_helper {}; | ||
|
||
template<typename T, typename _ = void> | ||
struct is_range_ : std::false_type {}; | ||
|
||
template<typename T> | ||
struct is_range_<T, | ||
std::conditional_t< false, | ||
conditional_helper< | ||
decltype(std::declval<T>().begin()), | ||
decltype(std::declval<T>().end()) | ||
>, void> | ||
> : std::true_type {}; | ||
|
||
template<typename T> | ||
constexpr bool is_range_v = is_range_<T>::value && !is_like_std_string<T>::value; | ||
|
||
|
||
/// tuple_size and tuple_element check. | ||
template<typename T> | ||
class is_tuple_like_ { | ||
template<typename U> static auto check(U* p) -> decltype( | ||
std::tuple_size< U >::value, | ||
std::declval<typename std::tuple_element<0, U>::type>(), | ||
int()); | ||
template<typename > static void check(...); | ||
public: | ||
static constexpr bool value = !std::is_void< decltype(check<T>(nullptr)) >::value; | ||
}; | ||
|
||
template<typename T> | ||
constexpr bool is_tuple_like_v = is_tuple_like_<T>::value && !is_range_<T>::value; | ||
|
||
|
||
//=-------------------------------------------------------------------------------------------------------------------- | ||
template<size_t... Is, class Tuple, class F> | ||
void for_each(std::index_sequence<Is...>, Tuple&& tup, F&& f) noexcept { | ||
using std::get; | ||
// using free function get<I>(T) now. | ||
const int _[] = { 0, | ||
((void)f(get<Is>(tup)), | ||
0)... }; | ||
(void)_; // blocks warnings | ||
} | ||
//=-------------------------------------------------------------------------------------------------------------------- | ||
template<class T> | ||
constexpr std::make_index_sequence<std::tuple_size<T>::value> | ||
get_indexes(T const&) { return {}; } | ||
|
||
//=-------------------------------------------------------------------------------------------------------------------- | ||
template<class Tuple, class F> | ||
void for_each(Tuple&& tup, F&& f) { | ||
const auto indexes = get_indexes(tup); | ||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); | ||
} | ||
|
||
} // namespace meta | ||
} // namespace fmt | ||
|
||
namespace fmt { | ||
|
||
// ===================================================================================================================== | ||
template<typename TupleT, typename Char> | ||
struct formatter< TupleT, Char | ||
, std::enable_if_t<fmt::meta::is_tuple_like_v<TupleT>> > | ||
{ | ||
fmt::formatting_tuple<Char> formating; | ||
|
||
template <typename ParseContext> | ||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { | ||
return formating.parse(ctx); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't be needed if you inherit from formatting_tuple. |
||
|
||
template <typename FormatContext = format_context> | ||
auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) { | ||
auto out = ctx.out(); | ||
std::ptrdiff_t i = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not size_t? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, replaced by size_t. |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can be merged with the above namespace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
|
||
template<typename RangeT, typename Char> | ||
struct formatter <RangeT, Char, std::enable_if_t<fmt::meta::is_range_v<RangeT>> > | ||
{ | ||
static constexpr std::ptrdiff_t range_length_limit = FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not size_t? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, ditto. |
||
|
||
fmt::formatting_range<Char> formating; | ||
|
||
template <typename ParseContext> | ||
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { | ||
return formating.parse(ctx); | ||
} | ||
|
||
template <typename FormatContext> | ||
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, " ... <other elements>"); | ||
break; | ||
} | ||
} | ||
if (formating.add_spaces) { *out++ = ' '; } | ||
fmt::copy(formating.postfix, out); | ||
return ctx.out(); | ||
} | ||
}; | ||
|
||
} // namespace fmt | ||
|
||
|
||
#endif // FMT_RANGES_H_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be removed. |
||
#endif | ||
|
||
#include "fmt/ranges.h" | ||
|
||
#include "gtest/gtest.h" | ||
|
||
#include <vector> | ||
#include <array> | ||
#include <map> | ||
|
||
|
||
TEST(RangesTest, FormatVector) { | ||
std::vector<int32_t> 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<std::vector<int32_t>> 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<std::string, int32_t> simap{ {"one",1}, {"two",2} }; | ||
EXPECT_EQ("{ [ one, 1 ], [ two, 2 ] }", fmt::format("{}", simap)); | ||
} | ||
|
||
TEST(RangesTest, FormatPair) { | ||
std::pair<int64_t, float> pa1{42, 3.14159265358979f}; | ||
EXPECT_EQ("[ 42, 3.14159 ]", fmt::format("{}", pa1)); | ||
} | ||
|
||
TEST(RangesTest, FormatTuple) { | ||
std::tuple<int64_t, float, std::string> tu1{42, 3.14159265358979f, "this is tuple"}; | ||
EXPECT_EQ("[ 42, 3.14159, this is tuple ]", fmt::format("{}", tu1)); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Containers are usually formatted without embedded spaces, in particular in Python format which this library is modeled after:
so I suggest dropping this flag (or at least setting it to false).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, set add_spaces to false per default.