From c240d98ffd37b65ee7c27185c62a2cbc093ff185 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 5 Dec 2021 00:02:31 +0500 Subject: [PATCH 01/36] Optimize tm formatting (Non C-locales and %Z) (#2617) * Move fmt::detail::formatbuf to format.h * Replace std::basic_ostringstream to std::basic_ostream with custom formatbuf * Use tm.tm_zone --- include/fmt/chrono.h | 174 +++++++++++++++++++++++++++--------------- include/fmt/format.h | 32 ++++++++ include/fmt/ostream.h | 32 +------- 3 files changed, 147 insertions(+), 91 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index d54c8ae7..5825a810 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -11,8 +11,9 @@ #include #include #include +#include #include -#include +#include #include #include "format.h" @@ -290,36 +291,42 @@ inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } -template -inline auto do_write(const std::tm& time, const std::locale& loc, char format, - char modifier) -> std::basic_string { - auto&& os = std::basic_ostringstream(); - os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); - auto end = facet.put(os, os, Char(' '), &time, format, modifier); - if (end.failed()) FMT_THROW(format_error("failed to format time")); - return std::move(os).str(); -} - -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto str = do_write(time, loc, format, modifier); - return std::copy(str.begin(), str.end(), out); -} - inline const std::locale& get_classic_locale() { static const auto& locale = std::locale::classic(); return locale; } -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto str = do_write(time, loc, format, modifier); +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; +template +constexpr const size_t codecvt_result::max_size; + +template +void write_codecvt(codecvt_result& out, string_view in_buf, + const std::locale& loc) { + using codecvt = std::codecvt; +#if FMT_CLANG_VERSION +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated" + auto& f = std::use_facet(loc); +# pragma clang diagnostic pop +#else + auto& f = std::use_facet(loc); +#endif + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, + std::begin(out.buf), std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { if (detail::is_utf8() && loc != get_classic_locale()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. @@ -332,56 +339,89 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, using code_unit = char32_t; #endif - using codecvt = std::codecvt; -#if FMT_CLANG_VERSION -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated" - auto& f = std::use_facet(loc); -# pragma clang diagnostic pop -#else - auto& f = std::use_facet(loc); -#endif - - auto mb = std::mbstate_t(); - const char* from_next = nullptr; - code_unit* to_next = nullptr; - constexpr size_t buf_size = 32; - code_unit buf[buf_size] = {}; - auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf, - buf + buf_size, to_next); - if (result != std::codecvt_base::ok) - FMT_THROW(format_error("failed to format time")); - str.clear(); - for (code_unit* p = buf; p != to_next; ++p) { + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto&& buf = basic_memory_buffer(); + for (code_unit* p = unit.buf; p != unit.end; ++p) { uint32_t c = static_cast(*p); if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { // surrogate pair ++p; - if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (p == unit.end || (c & 0xfc00) != 0xd800 || + (*p & 0xfc00) != 0xdc00) { FMT_THROW(format_error("failed to format time")); } c = (c << 10) + static_cast(*p) - 0x35fdc00; } if (c < 0x80) { - str.push_back(static_cast(c)); + buf.push_back(static_cast(c)); } else if (c < 0x800) { - str.push_back(static_cast(0xc0 | (c >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - str.push_back(static_cast(0xe0 | (c >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if (c >= 0x10000 && c <= 0x10ffff) { - str.push_back(static_cast(0xf0 | (c >> 18))); - str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else { FMT_THROW(format_error("failed to format time")); } } + return copy_str(buf.data(), buf.data() + buf.size(), out); } - return std::copy(str.begin(), str.end(), out); + return copy_str(in.data(), in.data() + in.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy_str(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + using iterator = std::ostreambuf_iterator; + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return buf.out(); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); } } // namespace detail @@ -878,6 +918,12 @@ template struct has_member_data_tm_gmtoff> : std::true_type {}; +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + #if defined(_WIN32) inline void tzset_once() { static bool init = []() -> bool { @@ -1035,6 +1081,14 @@ template class tm_writer { #endif } + void format_tz_name_impl(std::true_type) { + if (is_classic_) + out_ = write_tm_str(out_, tm_.tm_zone, loc_); + else + format_localized('Z'); + } + void format_tz_name_impl(std::false_type) { format_localized('Z'); } + void format_localized(char format, char modifier = 0) { out_ = write(out_, tm_, loc_, format, modifier); } @@ -1144,7 +1198,7 @@ template class tm_writer { void on_utc_offset() { format_utc_offset_impl(has_member_data_tm_gmtoff{}); } - void on_tz_name() { format_localized('Z'); } + void on_tz_name() { format_tz_name_impl(has_member_data_tm_zone{}); } void on_year(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard) diff --git a/include/fmt/format.h b/include/fmt/format.h index 3cf123ee..52b16779 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -265,6 +265,38 @@ FMT_END_NAMESPACE FMT_BEGIN_NAMESPACE namespace detail { + +template class formatbuf : public Streambuf { + private: + using char_type = typename Streambuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename Streambuf::int_type; + using traits_type = typename Streambuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). // Example: uint64_t d = bit_cast(2.718); diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index dd005fa1..0ebdd60f 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -18,36 +18,6 @@ template class basic_printf_context; namespace detail { -template class formatbuf : public std::basic_streambuf { - private: - using int_type = typename std::basic_streambuf::int_type; - using traits_type = typename std::basic_streambuf::traits_type; - - buffer& buffer_; - - public: - explicit formatbuf(buffer& buf) : buffer_(buf) {} - - protected: - // The put area is always empty. This makes the implementation simpler and has - // the advantage that the streambuf and the buffer are always in sync and - // sputc never writes into uninitialized memory. A disadvantage is that each - // call to sputc always results in a (virtual) call to overflow. There is no - // disadvantage here for sputn since this always results in a call to xsputn. - - auto overflow(int_type ch = traits_type::eof()) -> int_type override { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast(ch)); - return ch; - } - - auto xsputn(const Char* s, std::streamsize count) - -> std::streamsize override { - buffer_.append(s, s + count); - return count; - } -}; - // Checks if T has a user-defined operator<<. template class is_streamable { @@ -97,7 +67,7 @@ void write_buffer(std::basic_ostream& os, buffer& buf) { template void format_value(buffer& buf, const T& value, locale_ref loc = locale_ref()) { - auto&& format_buf = formatbuf(buf); + auto&& format_buf = formatbuf>(buf); auto&& output = std::basic_ostream(&format_buf); #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) if (loc) output.imbue(loc.get()); From 215f21a0382d325efa66df53fbfbfddb020a2234 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 5 Dec 2021 07:12:10 -0800 Subject: [PATCH 02/36] Detect overflow on large precision --- include/fmt/format-inl.h | 7 ++++++- test/format-test.cc | 3 +++ test/fuzzing/float.cc | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index f4ffeb67..ec636247 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -704,7 +704,12 @@ FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits( if (handler.fixed) { // Adjust fixed precision by exponent because it is relative to decimal // point. - handler.precision += exp + handler.exp10; + int precision_offset = exp + handler.exp10; + if (precision_offset > 0 && + handler.precision > max_value() - precision_offset) { + throw format_error("number is too big"); + } + handler.precision += precision_offset; // Check if precision is satisfied just by leading zeros, e.g. // format("{:.2f}", 0.001) gives "0.00" without generating any digits. if (handler.precision <= 0) { diff --git a/test/format-test.cc b/test/format-test.cc index 9233a87b..0b551f99 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -934,6 +934,9 @@ TEST(format_test, precision) { EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0, fmt::detail::max_value()), format_error, "number is too big"); + EXPECT_THROW_MSG( + (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304), + format_error, "number is too big"); EXPECT_EQ("st", fmt::format("{0:.2}", "str")); } diff --git a/test/fuzzing/float.cc b/test/fuzzing/float.cc index b3780e1d..d4c9e4f5 100644 --- a/test/fuzzing/float.cc +++ b/test/fuzzing/float.cc @@ -12,7 +12,7 @@ void check_round_trip(fmt::string_view format_str, double value) { auto buffer = fmt::memory_buffer(); - fmt::format_to(buffer, format_str, value); + fmt::format_to(std::back_inserter(buffer), format_str, value); if (std::isnan(value)) { auto nan = std::signbit(value) ? "-nan" : "nan"; From 9d5b9defde1334726d35c0d21b6225511ed3976e Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Wed, 8 Dec 2021 04:22:36 +0500 Subject: [PATCH 03/36] Enable tzset only on Windows desktop app (#2633) --- include/fmt/chrono.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 5825a810..d277328f 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -20,6 +20,20 @@ FMT_BEGIN_NAMESPACE +// Enable tzset. +#ifndef FMT_USE_TZSET +// UWP doesn't provide _tzset. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# define FMT_USE_TZSET 1 +# else +# define FMT_USE_TZSET 0 +# endif +#endif + // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 @@ -924,7 +938,7 @@ template struct has_member_data_tm_zone> : std::true_type {}; -#if defined(_WIN32) +#if FMT_USE_TZSET inline void tzset_once() { static bool init = []() -> bool { _tzset(); @@ -1067,7 +1081,9 @@ template class tm_writer { } void format_utc_offset_impl(std::false_type) { #if defined(_WIN32) +# if FMT_USE_TZSET tzset_once(); +# endif long offset = 0; _get_timezone(&offset); if (tm_.tm_isdst) { From 0bbc9708f9708db2fdca06227bed2587a3b1a344 Mon Sep 17 00:00:00 2001 From: matrackif Date: Thu, 9 Dec 2021 15:45:13 +0100 Subject: [PATCH 04/36] Implement c++20 std::chrono::duration subsecond formatting (#2623) * Add support for subsecond printing for std::chrono::duration according to the c++20 standard * Remove assert test that overflows intmax_t * * Hopefully fix int64_t to int32_t conversion errors. * Allow proper Duration::rep type to propagate via template argument deduction * * Hopefully fix int64_t to int32_t conversion errors. * Allow proper Duration::rep type to propagate via template argument deduction * Fix sign conversion (-Wsign-conversion) warning treated as error in num_digits() * Format chrono.h with clang-format * Remove extra forward slash in doxygen style comment Co-authored-by: Victor Zverovich * Apply all suggestions from GitHub, except for replacing the utility subsecond_helper class with a function * * Move logic of handling subseconds from utility class to function with name write_fractional_seconds() * Revert write(Rep value, int width) function to previous state * Fix -Wshadow warning * Remove unsued get_subseconds() function, its logic has been moved to write_fractional_seconds() * Change comment from lowercase int to uppercase Int * Simplify test check * Integrate suggested changes * Remove static from detail functions, they are no longer member functions of a class and static is unnecessary. * Change comment from "amount" to "number" Co-authored-by: Victor Zverovich --- include/fmt/chrono.h | 98 +++++++++++++++++++++++++++++++------------- test/chrono-test.cc | 47 ++++++++++++++++++--- 2 files changed, 111 insertions(+), 34 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index d277328f..b71195d0 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1388,21 +1388,21 @@ inline bool isfinite(T) { return true; } -// Converts value to int and checks that it's in the range [0, upper). -template ::value)> -inline int to_nonnegative_int(T value, int upper) { +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper), "invalid value"); (void)upper; - return static_cast(value); + return static_cast(value); } -template ::value)> -inline int to_nonnegative_int(T value, int upper) { +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { FMT_ASSERT( std::isnan(value) || (value >= 0 && value <= static_cast(upper)), "invalid value"); (void)upper; - return static_cast(value); + return static_cast(value); } template ::value)> @@ -1459,15 +1459,35 @@ inline std::chrono::duration get_milliseconds( #endif } -template ::value)> -inline std::chrono::duration get_milliseconds( +// Returns the number of digits according to the c++ 20 spec +// In the range [0, 18], if more than 18 fractional digits are required, +// then we return 6 for microseconds precision. +constexpr int num_digits(long long num, long long den, int n = 0) { + return num % den == 0 ? n : (n > 18 ? 6 : num_digits(num * 10, den, n + 1)); +} + +constexpr long long pow10(std::uint32_t n) { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +template ::is_signed)> +constexpr std::chrono::duration abs( std::chrono::duration d) { - using common_type = typename std::common_type::type; - auto ms = mod(d.count() * static_cast(Period::num) / - static_cast(Period::den) * 1000, - 1000); - return std::chrono::duration(static_cast(ms)); + // We need to compare the duration using the count() method directly + // due to a compiler bug in clang-11 regarding the spaceship operator, + // when -Wzero-as-null-pointer-constant is enabled. + // In clang-12 the bug has been fixed. See + // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: + // https://www.godbolt.org/z/Knbb5joYx + return d.count() >= d.zero().count() ? d : -d; +} + +template ::is_signed)> +static constexpr std::chrono::duration abs( + std::chrono::duration d) { + return d; } template (out, n, num_digits).end; } + template void write_fractional_seconds(Duration d) { + constexpr auto fractional_width = + detail::num_digits(Duration::period::num, Duration::period::den); + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(fractional_width)>>; + // We could use c++ 17 if constexpr here. + if (std::ratio_less::value) { + *out++ = '.'; + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? (detail::abs(d) - + std::chrono::duration_cast(d)) + .count() + : std::chrono::duration_cast( + detail::abs(d) - + std::chrono::duration_cast(d)) + .count(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(subseconds, max_value())); + int num_digits = detail::count_digits(n); + if (fractional_width > num_digits) { + out = std::fill_n(out, fractional_width - num_digits, '0'); + } + out = format_decimal(out, n, num_digits).end; + } + } + void write_nan() { std::copy_n("nan", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } @@ -1707,19 +1759,7 @@ struct chrono_formatter { if (ns == numeric_system::standard) { write(second(), 2); -#if FMT_SAFE_DURATION_CAST - // convert rep->Rep - using duration_rep = std::chrono::duration; - using duration_Rep = std::chrono::duration; - auto tmpval = fmt_safe_duration_cast(duration_rep{val}); -#else - auto tmpval = std::chrono::duration(val); -#endif - auto ms = get_milliseconds(tmpval); - if (ms != std::chrono::milliseconds(0)) { - *out++ = '.'; - write(ms.count(), 3); - } + write_fractional_seconds(std::chrono::duration{val}); return; } auto time = tm(); @@ -1748,7 +1788,7 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - write(second(), 2); + on_second(numeric_system::standard); } void on_am_pm() { diff --git a/test/chrono-test.cc b/test/chrono-test.cc index a2bc20c2..42360e5f 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -543,15 +543,12 @@ TEST(chrono_test, negative_durations) { } TEST(chrono_test, special_durations) { - EXPECT_EQ( - "40.", - fmt::format("{:%S}", std::chrono::duration(1e20)).substr(0, 3)); + auto value = fmt::format("{:%S}", std::chrono::duration(1e20)); + EXPECT_EQ(value, "40"); auto nan = std::numeric_limits::quiet_NaN(); EXPECT_EQ( "nan nan nan nan nan:nan nan", fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration(nan))); - (void)fmt::format("{:%S}", - std::chrono::duration(1.79400457e+31f)); EXPECT_EQ(fmt::format("{}", std::chrono::duration(1)), "1Es"); EXPECT_EQ(fmt::format("{}", std::chrono::duration(1)), @@ -585,4 +582,44 @@ TEST(chrono_test, weekday) { } } +TEST(chrono_test, cpp20_duration_subsecond_support) { + using attoseconds = std::chrono::duration; + // Check that 18 digits of subsecond precision are supported. + EXPECT_EQ(fmt::format("{:%S}", attoseconds{999999999999999999}), + "00.999999999999999999"); + EXPECT_EQ(fmt::format("{:%S}", attoseconds{673231113420148734}), + "00.673231113420148734"); + EXPECT_EQ(fmt::format("{:%S}", attoseconds{-673231113420148734}), + "-00.673231113420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{13420148734}), + "13.420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}), + "-13.420148734"); + EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234"); + { + // Check that {:%H:%M:%S} is equivalent to {:%T}. + auto dur = std::chrono::milliseconds{3601234}; + auto formatted_dur = fmt::format("{:%T}", dur); + EXPECT_EQ(formatted_dur, "01:00:01.234"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + } + using nanoseconds_dbl = std::chrono::duration; + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789"); + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789"); + // Verify that only the seconds part is extracted and printed. + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789"); + EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000"); + { + // Now the hour is printed, and we also test if negative doubles work. + auto dur = nanoseconds_dbl{-99123456789}; + auto formatted_dur = fmt::format("{:%T}", dur); + EXPECT_EQ(formatted_dur, "-00:01:39.123456789"); + EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); + } + // Check that durations with precision greater than std::chrono::seconds have + // fixed precision, and print zeros even if there is no fractional part. + EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}), + "07.000000"); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR From 91533d3c3332d0adb76d0f70f82983ceb3f0497b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 9 Dec 2021 06:55:31 -0800 Subject: [PATCH 05/36] Minor tweaks to chrono subsecond formatting --- include/fmt/chrono.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index b71195d0..5285d420 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1459,11 +1459,13 @@ inline std::chrono::duration get_milliseconds( #endif } -// Returns the number of digits according to the c++ 20 spec -// In the range [0, 18], if more than 18 fractional digits are required, -// then we return 6 for microseconds precision. -constexpr int num_digits(long long num, long long den, int n = 0) { - return num % den == 0 ? n : (n > 18 ? 6 : num_digits(num * 10, den, n + 1)); +// Returns the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +constexpr int count_fractional_digits(long long num, long long den, int n = 0) { + return num % den == 0 + ? n + : (n > 18 ? 6 : count_fractional_digits(num * 10, den, n + 1)); } constexpr long long pow10(std::uint32_t n) { @@ -1479,13 +1481,13 @@ constexpr std::chrono::duration abs( // when -Wzero-as-null-pointer-constant is enabled. // In clang-12 the bug has been fixed. See // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: - // https://www.godbolt.org/z/Knbb5joYx + // https://www.godbolt.org/z/Knbb5joYx. return d.count() >= d.zero().count() ? d : -d; } template ::is_signed)> -static constexpr std::chrono::duration abs( +constexpr std::chrono::duration abs( std::chrono::duration d) { return d; } @@ -1651,14 +1653,13 @@ struct chrono_formatter { } template void write_fractional_seconds(Duration d) { - constexpr auto fractional_width = - detail::num_digits(Duration::period::num, Duration::period::den); + constexpr auto num_fractional_digits = + count_fractional_digits(Duration::period::num, Duration::period::den); using subsecond_precision = std::chrono::duration< typename std::common_type::type, - std::ratio<1, detail::pow10(fractional_width)>>; - // We could use c++ 17 if constexpr here. + std::ratio<1, detail::pow10(num_fractional_digits)>>; if (std::ratio_less::value) { *out++ = '.'; @@ -1675,9 +1676,8 @@ struct chrono_formatter { uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(subseconds, max_value())); int num_digits = detail::count_digits(n); - if (fractional_width > num_digits) { - out = std::fill_n(out, fractional_width - num_digits, '0'); - } + if (num_fractional_digits > num_digits) + out = std::fill_n(out, num_fractional_digits - num_digits, '0'); out = format_decimal(out, n, num_digits).end; } } From e4f0564aa64175eaef04fd1f4fb168a730b55072 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 9 Dec 2021 09:48:55 -0800 Subject: [PATCH 06/36] Disable is_streamable for string[_view] --- include/fmt/ostream.h | 2 ++ test/ostream-test.cc | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 0ebdd60f..3d716ece 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -45,6 +45,8 @@ struct is_streamable< enable_if_t< std::is_arithmetic::value || std::is_array::value || std::is_pointer::value || std::is_same::value || + std::is_same>::value || + std::is_same>::value || (std::is_convertible::value && !std::is_enum::value)>> : std::false_type {}; diff --git a/test/ostream-test.cc b/test/ostream-test.cc index a01182d1..f81039e5 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -294,3 +294,8 @@ struct abstract { void format_abstract_compiles(const abstract& a) { fmt::format(FMT_COMPILE("{}"), a); } + +TEST(ostream_test, is_formattable) { + EXPECT_TRUE(fmt::is_formattable()); + EXPECT_TRUE(fmt::is_formattable>()); +} From a9c7b9b8f7b8c2187601e22fcbf8d2644dfee873 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 9 Dec 2021 10:34:27 -0800 Subject: [PATCH 07/36] Clarify that _format is deprecated --- include/fmt/format.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 52b16779..0678f229 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3048,14 +3048,12 @@ constexpr auto operator"" _a(const char* s, size_t) -> detail::udl_arg { # endif /** - \rst - User-defined literal equivalent of :func:`fmt::format`. + DEPRECATED! User-defined literal equivalent of fmt::format. **Example**:: using namespace fmt::literals; std::string message = "The answer is {}"_format(42); - \endrst */ constexpr auto operator"" _format(const char* s, size_t n) -> detail::udl_formatter { From c652f8243a5f8e50584d2a81c8c5714e74a03955 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 9 Dec 2021 10:49:47 -0800 Subject: [PATCH 08/36] Make header guard consistent with header name --- include/fmt/xchar.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index a0dd032f..3a5bf209 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -5,8 +5,8 @@ // // For the license information refer to format.h. -#ifndef FMT_WCHAR_H_ -#define FMT_WCHAR_H_ +#ifndef FMT_XCHAR_H_ +#define FMT_XCHAR_H_ #include #include @@ -233,4 +233,4 @@ template inline auto to_wstring(const T& value) -> std::wstring { FMT_MODULE_EXPORT_END FMT_END_NAMESPACE -#endif // FMT_WCHAR_H_ +#endif // FMT_XCHAR_H_ From fd62fba9855c9cf8fe17eb9363e03f93563d9dc3 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 9 Dec 2021 11:11:17 -0800 Subject: [PATCH 09/36] Don't convert scoped enums to integers --- include/fmt/core.h | 10 ++++++++++ include/fmt/format.h | 1 + test/core-test.cc | 3 +++ test/ranges-test.cc | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index e87b706c..179adab1 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -8,6 +8,7 @@ #ifndef FMT_CORE_H_ #define FMT_CORE_H_ +#include // std::byte #include // std::FILE #include #include @@ -367,6 +368,12 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, # endif #endif +#ifdef __cpp_lib_byte +using byte = std::byte; +#else +enum class byte : unsigned char {}; +#endif + #if defined(FMT_USE_STRING_VIEW) template using std_string_view = std::basic_string_view; #elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) @@ -1403,6 +1410,9 @@ template struct arg_mapper { template ::value && + (std::is_convertible::value + || std::is_same::value + ) && !has_formatter::value && !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) diff --git a/include/fmt/format.h b/include/fmt/format.h index 0678f229..08fba7ca 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2606,6 +2606,7 @@ FMT_FORMAT_AS(unsigned long, unsigned long long); FMT_FORMAT_AS(Char*, const Char*); FMT_FORMAT_AS(std::basic_string, basic_string_view); FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(detail::byte, unsigned char); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); template diff --git a/test/core-test.cc b/test/core-test.cc index decf31a7..78ec3de2 100644 --- a/test/core-test.cc +++ b/test/core-test.cc @@ -737,6 +737,8 @@ struct convertible_to_pointer { operator const int*() const { return nullptr; } }; +enum class test_scoped_enum {}; + TEST(core_test, is_formattable) { static_assert(fmt::is_formattable::value, ""); static_assert(fmt::is_formattable::value, ""); @@ -777,6 +779,7 @@ TEST(core_test, is_formattable) { static_assert(!fmt::is_formattable::value, ""); static_assert(!fmt::is_formattable::value, ""); + static_assert(!fmt::is_formattable::value, ""); } TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } diff --git a/test/ranges-test.cc b/test/ranges-test.cc index f8e67390..de785c0f 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -190,7 +190,7 @@ TEST(ranges_test, range) { EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]"); } -enum class test_enum { foo }; +enum test_enum { foo }; TEST(ranges_test, enum_range) { auto v = std::vector{test_enum::foo}; From ac1b5f3da532fc9dca349c1bc06de65178f44eb3 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 9 Dec 2021 18:08:22 -0800 Subject: [PATCH 10/36] Refactor problematic trailing returns in arg_mapper --- include/fmt/core.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 179adab1..aad37683 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -8,8 +8,8 @@ #ifndef FMT_CORE_H_ #define FMT_CORE_H_ -#include // std::byte -#include // std::FILE +#include // std::byte +#include // std::FILE #include #include #include @@ -1364,20 +1364,21 @@ template struct arg_mapper { -> basic_string_view { return std_string_view(val); } - FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) - -> decltype(this->map("")) { + + using cstring_result = conditional_t::value, + const char*, unformattable_pointer>; + + FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) -> cstring_result { return map(reinterpret_cast(val)); } FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) - -> decltype(this->map("")) { + -> cstring_result { return map(reinterpret_cast(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) - -> decltype(this->map("")) { + FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) -> cstring_result { return map(reinterpret_cast(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) - -> decltype(this->map("")) { + FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) -> cstring_result { return map(reinterpret_cast(val)); } @@ -1410,9 +1411,8 @@ template struct arg_mapper { template ::value && - (std::is_convertible::value - || std::is_same::value - ) && + (std::is_convertible::value || + std::is_same::value) && !has_formatter::value && !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) From e0136fc8bd48f72d29d9276437a3bdca357789e0 Mon Sep 17 00:00:00 2001 From: Marek Kurdej Date: Fri, 10 Dec 2021 15:36:42 +0100 Subject: [PATCH 11/36] Qualify calls to make_wformat_args. Fixes #2639. (#2641) --- include/fmt/xchar.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 3a5bf209..55825077 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -217,11 +217,11 @@ inline void vprint(wstring_view fmt, wformat_args args) { template void print(std::FILE* f, wformat_string fmt, T&&... args) { - return vprint(f, wstring_view(fmt), make_wformat_args(args...)); + return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); } template void print(wformat_string fmt, T&&... args) { - return vprint(wstring_view(fmt), make_wformat_args(args...)); + return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); } /** From 3a951a66cb0fb53ff5a9d5ce9c77e05ef9d382ce Mon Sep 17 00:00:00 2001 From: Marek Kurdej Date: Fri, 10 Dec 2021 17:28:25 +0100 Subject: [PATCH 12/36] Avoid qualifying by inline namespace. Fixes #2642. (#2643) --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index aad37683..9e0e8964 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -567,7 +567,7 @@ constexpr auto to_string_view(const S& s) FMT_BEGIN_DETAIL_NAMESPACE void to_string_view(...); -using fmt::v8::to_string_view; +using fmt::to_string_view; // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in From 35f60377aad3cc697de3e47bb0164f63b503eea1 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 17 Dec 2021 06:49:29 -0800 Subject: [PATCH 13/36] Update ChangeLog.rst --- ChangeLog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog.rst b/ChangeLog.rst index d0a1aa1d..4b88f4a6 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -281,6 +281,9 @@ This doesn't introduce a dependency on ```` so there is virtually no compile time effect. +* Deprecated an undocumented ``format_to`` overload that takes + ``basic_memory_buffer``. + * Made parameter order in ``vformat_to`` consistent with ``format_to`` (`#2327 `_). From 82246b8766bc9f9ee00a6572245dfcdf43315af9 Mon Sep 17 00:00:00 2001 From: Alexey Ochapov Date: Sat, 18 Dec 2021 00:44:36 +0300 Subject: [PATCH 14/36] fix throw with exceptions disabled (#2647) --- include/fmt/format-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index ec636247..04ded482 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -707,7 +707,7 @@ FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits( int precision_offset = exp + handler.exp10; if (precision_offset > 0 && handler.precision > max_value() - precision_offset) { - throw format_error("number is too big"); + FMT_THROW(format_error("number is too big")); } handler.precision += precision_offset; // Check if precision is satisfied just by leading zeros, e.g. From ef72b471fca0ec27533f70bf123c46e3a800251d Mon Sep 17 00:00:00 2001 From: Alexey Ochapov Date: Sat, 18 Dec 2021 02:53:05 +0300 Subject: [PATCH 15/36] enable named arguments check in compile-time checks (#2649) works only if all named arguments are UDL-based --- include/fmt/core.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 9e0e8964..14ac2070 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1109,6 +1109,11 @@ template constexpr auto count_named_args() -> size_t { return count::value...>(); } +template +constexpr auto count_statically_named_args() -> size_t { + return count::value...>(); +} + enum class type { none_type, // Integer types should go first, @@ -3043,7 +3048,8 @@ template class basic_format_string { std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); #ifdef FMT_HAS_CONSTEVAL - if constexpr (detail::count_named_args() == 0) { + if constexpr (detail::count_named_args() == + detail::count_statically_named_args()) { using checker = detail::format_string_checker...>; detail::parse_format_string(str_, checker(s, {})); From 223a0fa55d54381545ed449fd17051301d47c8d4 Mon Sep 17 00:00:00 2001 From: Alexey Ochapov Date: Mon, 13 Dec 2021 04:53:43 +0300 Subject: [PATCH 16/36] move gtest-specific check into gtest/CMakeLists.txt --- test/CMakeLists.txt | 7 ------- test/gtest/CMakeLists.txt | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6a27e33f..4df9e614 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,13 +8,6 @@ target_link_libraries(test-main gtest) include(CheckCXXCompilerFlag) -# Workaround GTest bug https://github.com/google/googletest/issues/705. -check_cxx_compiler_flag( - -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) -if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) - target_compile_options(test-main PUBLIC -fno-delete-null-pointer-checks) -endif () - # Use less strict pedantic flags for the tests because GMock doesn't compile # cleanly with -pedantic and -std=c++98. if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 1282d62a..0cc4e1aa 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -17,6 +17,13 @@ else () target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) endif () +# Workaround GTest bug https://github.com/google/googletest/issues/705. +check_cxx_compiler_flag( + -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS) +if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS) + target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks) +endif () + if (MSVC) # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) From 51b14b6c0c8a782b55a913be418c02afacb91195 Mon Sep 17 00:00:00 2001 From: Alexey Ochapov Date: Mon, 13 Dec 2021 04:54:29 +0300 Subject: [PATCH 17/36] remove commented out lines --- test/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4df9e614..a0fbd05f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,12 +8,6 @@ target_link_libraries(test-main gtest) include(CheckCXXCompilerFlag) -# Use less strict pedantic flags for the tests because GMock doesn't compile -# cleanly with -pedantic and -std=c++98. -if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) - #set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros) -endif () - function(add_fmt_executable name) add_executable(${name} ${ARGN}) if (MINGW) From 659de779e6eceeee66b4b3165a84c928220ef378 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 17 Dec 2021 16:51:24 -0800 Subject: [PATCH 18/36] Fix a UB in parse_format_specs when begin is null --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 14ac2070..696b8676 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -2487,7 +2487,7 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, const Char* end, SpecHandler&& handler) -> const Char* { - if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && + if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') { presentation_type type = parse_presentation_type(*begin++); if (type == presentation_type::none) From be51ee1ceb0553411cae166335b59e7ae5126527 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 17 Dec 2021 17:13:08 -0800 Subject: [PATCH 19/36] Disable broken copy ctor of dynamic_format_arg_store --- include/fmt/args.h | 9 +-------- test/args-test.cc | 35 +++++++++++------------------------ 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/include/fmt/args.h b/include/fmt/args.h index 36f62220..99060040 100644 --- a/include/fmt/args.h +++ b/include/fmt/args.h @@ -146,14 +146,7 @@ class dynamic_format_arg_store constexpr dynamic_format_arg_store() = default; constexpr dynamic_format_arg_store( - const dynamic_format_arg_store& store) - : -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - basic_format_args(), -#endif - data_(store.data_), - named_info_(store.named_info_) { - } + const dynamic_format_arg_store& store) = delete; /** \rst diff --git a/test/args-test.cc b/test/args-test.cc index 2b1db8c8..e793cb5c 100644 --- a/test/args-test.cc +++ b/test/args-test.cc @@ -10,7 +10,7 @@ #include "gtest/gtest.h" TEST(args_test, basic) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(42); store.push_back("abc1"); store.push_back(1.5f); @@ -19,7 +19,7 @@ TEST(args_test, basic) { TEST(args_test, strings_and_refs) { // Unfortunately the tests are compiled with old ABI so strings use COW. - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char str[] = "1234567890"; store.push_back(str); store.push_back(std::cref(str)); @@ -48,7 +48,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, custom_format) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto c = custom_type(); store.push_back(c); ++c.i; @@ -77,7 +77,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, to_string_and_formatter) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto s = to_stringable(); store.push_back(s); store.push_back(std::cref(s)); @@ -85,13 +85,13 @@ TEST(args_test, to_string_and_formatter) { } TEST(args_test, named_int) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("a1", 42)); EXPECT_EQ("42", fmt::vformat("{a1}", store)); } TEST(args_test, named_strings) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char str[] = "1234567890"; store.push_back(fmt::arg("a1", str)); store.push_back(fmt::arg("a2", std::cref(str))); @@ -100,7 +100,7 @@ TEST(args_test, named_strings) { } TEST(args_test, named_arg_by_ref) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; char band[] = "Rolling Stones"; store.push_back(fmt::arg("band", std::cref(band))); band[9] = 'c'; // Changing band affects the output. @@ -108,7 +108,7 @@ TEST(args_test, named_arg_by_ref) { } TEST(args_test, named_custom_format) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; auto c = custom_type(); store.push_back(fmt::arg("c1", c)); ++c.i; @@ -121,7 +121,7 @@ TEST(args_test, named_custom_format) { } TEST(args_test, clear) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(42); auto result = fmt::vformat("{}", store); @@ -138,7 +138,7 @@ TEST(args_test, clear) { } TEST(args_test, reserve) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.reserve(2, 1); store.push_back(1.5f); store.push_back(fmt::arg("a1", 42)); @@ -163,7 +163,7 @@ template <> struct formatter { FMT_END_NAMESPACE TEST(args_test, throw_on_copy) { - auto store = fmt::dynamic_format_arg_store(); + fmt::dynamic_format_arg_store store; store.push_back(std::string("foo")); try { store.push_back(copy_throwable()); @@ -171,16 +171,3 @@ TEST(args_test, throw_on_copy) { } EXPECT_EQ(fmt::vformat("{}", store), "foo"); } - -TEST(args_test, copy_constructor) { - auto store = fmt::dynamic_format_arg_store(); - store.push_back(fmt::arg("test1", "value1")); - store.push_back(fmt::arg("test2", "value2")); - store.push_back(fmt::arg("test3", "value3")); - - auto store2 = store; - store2.push_back(fmt::arg("test4", "value4")); - - auto result = fmt::vformat("{test1} {test2} {test3} {test4}", store2); - EXPECT_EQ(result, "value1 value2 value3 value4"); -} From 121002d7002db78c38414cf2d8e8e01e089811ca Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 18 Dec 2021 07:12:53 -0800 Subject: [PATCH 20/36] Add a map formatter --- include/fmt/ranges.h | 54 +++++++++++++++++++++++++++++++++++++++++++- test/ranges-test.cc | 2 +- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index ee12bd95..3068649f 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -71,7 +71,7 @@ OutputIterator copy(wchar_t ch, OutputIterator out) { return out; } -/// Return true value if T has std::string interface, like std::string_view. +// Returns true if T has a std::string-like interface, like std::string_view. template class is_std_string_like { template static auto check(U* p) @@ -86,6 +86,19 @@ template class is_std_string_like { template struct is_std_string_like> : std::true_type {}; +template class is_map { + template static auto check(U*) -> typename U::key_type; + template static void check(...); + + public: +#ifdef FMT_FORMAT_MAP_AS_LIST + static FMT_CONSTEXPR_DECL const bool value = false; +#else + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value; +#endif +}; + template struct conditional_helper {}; template struct is_range_ : std::false_type {}; @@ -573,6 +586,7 @@ struct formatter::value>> { template struct is_range { static FMT_CONSTEXPR_DECL const bool value = detail::is_range_::value && !detail::is_std_string_like::value && + !detail::is_map::value && !std::is_convertible>::value && !std::is_constructible, T>::value; }; @@ -614,6 +628,44 @@ struct formatter< } }; +template +struct formatter< + T, Char, + enable_if_t< + detail::is_map::value +// Workaround a bug in MSVC 2019 and earlier. +#if !FMT_MSC_VER + && (is_formattable, Char>::value || + detail::has_fallback_formatter, Char>::value) +#endif + >> { + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template < + typename FormatContext, typename U, + FMT_ENABLE_IF( + std::is_same::value, + const T, T>>::value)> + auto format(U& map, FormatContext& ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + *out++ = '{'; + int i = 0; + for (const auto& item : map) { + if (i > 0) out = detail::write_delimiter(out); + out = detail::write_range_entry(out, item.first); + *out++ = ':'; + *out++ = ' '; + out = detail::write_range_entry(out, item.second); + ++i; + } + *out++ = '}'; + return out; + } +}; + template struct tuple_join_view : detail::view { const std::tuple& tuple; basic_string_view sep; diff --git a/test/ranges-test.cc b/test/ranges-test.cc index de785c0f..4481e2b4 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -55,7 +55,7 @@ TEST(ranges_test, format_vector2) { 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("{}", m), "{\"one\": 1, \"two\": 2}"); } TEST(ranges_test, format_pair) { From c882790a2e0134e7943fc70e42a99d210f5f2334 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 18 Dec 2021 07:35:40 -0800 Subject: [PATCH 21/36] Add a set formatter --- include/fmt/ranges.h | 55 ++++++++++++++++++++++++-------------------- test/ranges-test.cc | 5 ++++ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 3068649f..71474f32 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -19,21 +19,6 @@ FMT_BEGIN_NAMESPACE -template struct formatting_range { -#ifdef FMT_DEPRECATED_BRACED_RANGES - Char prefix = '{'; - Char postfix = '}'; -#else - Char prefix = '['; - Char postfix = ']'; -#endif - - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } -}; - template struct formatting_tuple { Char prefix = '('; Char postfix = ')'; @@ -87,7 +72,7 @@ template struct is_std_string_like> : std::true_type {}; template class is_map { - template static auto check(U*) -> typename U::key_type; + template static auto check(U*) -> typename U::mapped_type; template static void check(...); public: @@ -99,6 +84,19 @@ template class is_map { #endif }; +template class is_set { + template static auto check(U*) -> typename U::key_type; + template static void check(...); + + public: +#ifdef FMT_FORMAT_SET_AS_LIST + static FMT_CONSTEXPR_DECL const bool value = false; +#else + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void(nullptr))>::value && !is_map::value; +#endif +}; + template struct conditional_helper {}; template struct is_range_ : std::false_type {}; @@ -602,11 +600,9 @@ struct formatter< detail::has_fallback_formatter, Char>::value) #endif >> { - formatting_range formatting; - template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - return formatting.parse(ctx); + return ctx.begin(); } template < @@ -614,17 +610,26 @@ struct formatter< FMT_ENABLE_IF( std::is_same::value, const T, T>>::value)> - auto format(U& values, FormatContext& ctx) -> decltype(ctx.out()) { - auto out = detail::copy(formatting.prefix, ctx.out()); - size_t i = 0; - auto it = std::begin(values); - auto end = std::end(values); + auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) { +#ifdef FMT_DEPRECATED_BRACED_RANGES + Char prefix = '{'; + Char postfix = '}'; +#else + Char prefix = detail::is_set::value ? '{' : '['; + Char postfix = detail::is_set::value ? '}' : ']'; +#endif + auto out = ctx.out(); + *out++ = prefix; + int i = 0; + auto it = std::begin(range); + auto end = std::end(range); for (; it != end; ++it) { if (i > 0) out = detail::write_delimiter(out); out = detail::write_range_entry(out, *it); ++i; } - return detail::copy(formatting.postfix, out); + *out++ = postfix; + return out; } }; diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 4481e2b4..86dd5520 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -58,6 +58,11 @@ TEST(ranges_test, format_map) { EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}"); } +TEST(ranges_test, format_set) { + EXPECT_EQ(fmt::format("{}", std::set{"one", "two"}), + "{\"one\", \"two\"}"); +} + TEST(ranges_test, format_pair) { auto p = std::pair(42, 1.5f); EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)"); From e46392ea2c66969dc7fb3c86505a4b4a7d484c02 Mon Sep 17 00:00:00 2001 From: Alexey Ochapov Date: Sun, 12 Dec 2021 00:57:14 +0300 Subject: [PATCH 22/36] deprecate _format UDL in code using FMT_DEPRECATED --- include/fmt/format.h | 2 +- test/format-test.cc | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 08fba7ca..ea5b9ece 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3056,7 +3056,7 @@ constexpr auto operator"" _a(const char* s, size_t) -> detail::udl_arg { using namespace fmt::literals; std::string message = "The answer is {}"_format(42); */ -constexpr auto operator"" _format(const char* s, size_t n) +FMT_DEPRECATED constexpr auto operator"" _format(const char* s, size_t n) -> detail::udl_formatter { return {{s, n}}; } diff --git a/test/format-test.cc b/test/format-test.cc index 0b551f99..952dbe20 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1761,6 +1761,21 @@ TEST(format_test, custom_format_compile_time_string) { using namespace fmt::literals; +# if FMT_GCC_VERSION +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1 +# elif FMT_CLANG_VERSION && defined(__has_warning) +# if __has_warning("-Wdeprecated-declarations") +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 1 +# endif +# endif +# ifndef FMT_CHECK_DEPRECATED_UDL_FORMAT +# define FMT_CHECK_DEPRECATED_UDL_FORMAT 0 +# endif + +# if FMT_CHECK_DEPRECATED_UDL_FORMAT +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" + TEST(format_test, format_udl) { EXPECT_EQ("{}c{}"_format("ab", 1), fmt::format("{}c{}", "ab", 1)); EXPECT_EQ("foo"_format(), "foo"); @@ -1768,6 +1783,9 @@ TEST(format_test, format_udl) { EXPECT_EQ("{}"_format(date(2015, 10, 21)), "2015-10-21"); } +# pragma GCC diagnostic pop +# endif + TEST(format_test, named_arg_udl) { auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra", "second"_a = "cad", "third"_a = 99); From 2d445775865d8ef5b62e7d526657e1b6efae41e8 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 18 Dec 2021 08:51:21 -0800 Subject: [PATCH 23/36] Try fixing byte regression --- include/fmt/core.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 696b8676..5acd9923 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1415,17 +1415,20 @@ template struct arg_mapper { } template ::value && - (std::is_convertible::value || - std::is_same::value) && - !has_formatter::value && - !has_fallback_formatter::value)> + FMT_ENABLE_IF( + std::is_enum::value&& std::is_convertible::value && + !has_formatter::value && + !has_fallback_formatter::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(std::declval().map( static_cast::type>(val))) { return map(static_cast::type>(val)); } + FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned char { + return map(static_cast(val)); + } + template > struct formattable : bool_constant() || From eaddd1e3cd4cc26554133f8b036a0bb035c22f65 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 19 Dec 2021 06:46:24 -0800 Subject: [PATCH 24/36] Fix handling of byte --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 5acd9923..9ad32e91 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1425,7 +1425,7 @@ template struct arg_mapper { return map(static_cast::type>(val)); } - FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned char { + FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned { return map(static_cast(val)); } From c5aafd8f9003c5f0ac1d9bc00d566fefe60996d7 Mon Sep 17 00:00:00 2001 From: Alexey Ochapov Date: Mon, 13 Dec 2021 05:20:15 +0300 Subject: [PATCH 25/36] expose headers as SYSTEM depending on special configuration option --- CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e4a48b0d..3bf80f80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_MODULE "Build a module instead of a traditional library." OFF) +option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF) set(FMT_CAN_MODULE OFF) if (CMAKE_CXX_STANDARD GREATER 17 AND @@ -96,6 +97,10 @@ if (FMT_TEST AND FMT_MODULE) # The tests require {fmt} to be compiled as traditional library message(STATUS "Testing is incompatible with build mode 'module'.") endif () +set(FMT_SYSTEM_HEADERS_ATTRIBUTE "") +if (FMT_SYSTEM_HEADERS) + set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) +endif () # Get version from core.h file(READ include/fmt/core.h core_h) @@ -262,7 +267,7 @@ endif () target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) -target_include_directories(fmt PUBLIC +target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC $ $) @@ -298,7 +303,7 @@ add_library(fmt::fmt-header-only ALIAS fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) -target_include_directories(fmt-header-only INTERFACE +target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE $ $) From fc2a376d8ec74ec3c3f7a6f8eb4aeb2ed2c732d7 Mon Sep 17 00:00:00 2001 From: Stefan Weil Date: Wed, 22 Dec 2021 21:45:04 +0100 Subject: [PATCH 26/36] Remove two expressions which had no effect (reported by LGTM) Signed-off-by: Stefan Weil --- include/fmt/core.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 9ad32e91..6f75b353 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1500,14 +1500,11 @@ class appender : public std::back_insert_iterator> { using _Unchecked_type = appender; // Mark iterator as checked. auto operator++() -> appender& { - base::operator++(); return *this; } auto operator++(int) -> appender { - auto tmp = *this; - ++*this; - return tmp; + return *this; } }; From 227109eef1abb03523c116286f76480b2ab9fa3c Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 17:14:54 +0800 Subject: [PATCH 27/36] Create pipeline to pack and push nuget package. --- .github/workflows/nuget.yml | 44 +++++++++++++++++++++++++++++ .github/workflows/tag.yml | 55 +++++++++++++++++++++++++++++++++++++ .gitignore | 3 ++ bundle/nuget/fmt.nuspec | 30 ++++++++++++++++++++ bundle/nuget/fmt.props | 12 ++++++++ bundle/nuget/fmt.targets | 29 +++++++++++++++++++ 6 files changed, 173 insertions(+) create mode 100644 .github/workflows/nuget.yml create mode 100644 .github/workflows/tag.yml create mode 100644 bundle/nuget/fmt.nuspec create mode 100644 bundle/nuget/fmt.props create mode 100644 bundle/nuget/fmt.targets diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml new file mode 100644 index 00000000..b85b97a9 --- /dev/null +++ b/.github/workflows/nuget.yml @@ -0,0 +1,44 @@ +name: windows + +on: pull_request + +jobs: + build: + runs-on: windows-latest + strategy: + matrix: + + steps: + - uses: actions/checkout@v2 + + - uses: nuget/setup-nuget@v1 + with: + nuget-api-key: ${{ secrets.nuget_apikey }} + nuget-version: '5.x' + + - name: Create Build Environment + run: cmake -E make_directory build + + - name: Configure + working-directory: build + run: | + cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON -A x64 -DCMAKE_CXX_STANDARD=17 .. + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + cmake --build . --config Debug --parallel $threads + cmake --build . --config Release --parallel $threads + + - name: Test + working-directory: build + run: ctest -C Debug -V + env: + CTEST_OUTPUT_ON_FAILURE: True + + - name: NuGet Pack + run: | + cp readme.rst readme.md + cp license.rst readme.md + nuget pack bundle\nuget\fmt.nuspec -properties Id=wuganhao.fmt -version 1.0-dev -basepath . -OutputFileNamesWithoutVersion \ No newline at end of file diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 00000000..4f3113c6 --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,55 @@ +name: windows + +on: pull_request + +jobs: + build: + runs-on: windows-latest + strategy: + matrix: + + steps: + - uses: actions/checkout@v2 + + - uses: nuget/setup-nuget@v1 + with: + nuget-api-key: ${{ secrets.nuget_apikey }} + nuget-version: '5.x' + + - name: Create Build Environment + run: cmake -E make_directory build + + - name: Configure + working-directory: build + run: | + cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON -A x64 -DCMAKE_CXX_STANDARD=17 .. + + - name: Build + working-directory: ${{runner.workspace}}/build + run: | + $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + cmake --build . --config Debug --parallel $threads + cmake --build . --config Release --parallel $threads + + - name: Test + working-directory: build + run: ctest -C Debug -V + env: + CTEST_OUTPUT_ON_FAILURE: True + + - name: NuGet Pack + run: | + cp readme.rst readme.md + cp license.rst readme.md + $Tag = "${{ github.ref }}" -replace '(refs/tags/)(.+)', '$2' + $Version = $Tag -replace '((\d+)(\.\d+){1,3})(-.+|)', '$1' + $InformationalVersion = "$Tag SHA-${{ github.SHA }}" + $PackageVersion = $Tag + echo "Version: $Tag" + echo "Informational Version: $InformationalVersion" + echo "Package Version: $Tag" + nuget pack bundle\nuget\fmt.nuspec -properties Id=wuganhao.fmt -version $PackageVersion -basepath . -OutputFileNamesWithoutVersion -OutputDirectory dist + + - name: Nuget Push + run: | + nuget push dist\*.nupkg -source https://api.nuget.org/v3/index.json -apikey ${{ secrets.nuget_apikey }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a37cb98..4b621bf1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ FMT.build Makefile run-msbuild.bat fmt.pc +*.nupkg +/license.md +/readme.md diff --git a/bundle/nuget/fmt.nuspec b/bundle/nuget/fmt.nuspec new file mode 100644 index 00000000..79d588af --- /dev/null +++ b/bundle/nuget/fmt.nuspec @@ -0,0 +1,30 @@ + + + + $Id$ + $version$ + Wu Ganhao<wuganhao@hotmail.com> + Wu Ganhao<wuganhao@hotmail.com> + true + false + {fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams. + + docs\license.md + docs\readme.md + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bundle/nuget/fmt.props b/bundle/nuget/fmt.props new file mode 100644 index 00000000..dd9cfe30 --- /dev/null +++ b/bundle/nuget/fmt.props @@ -0,0 +1,12 @@ + + + + $(MSBuildThisFileDirectory)..\include\;$(MSBuildThisFileDirectory)..\lib\native\x64\$(Configuration) + + + fmt.lib; + + + fmtd.lib; + + diff --git a/bundle/nuget/fmt.targets b/bundle/nuget/fmt.targets new file mode 100644 index 00000000..233eb04f --- /dev/null +++ b/bundle/nuget/fmt.targets @@ -0,0 +1,29 @@ + + + + + $(fmtIncludePaths);%(AdditionalIncludeDirectories) + + + + + %(fmtLibs);%(AdditionalDependencies) + + + + + $(MSBuildThisFileDirectory)..\lib\native\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories) + + + + $(BuildDependsOn);DeployfmtArtifacts + + + + + + + + + + From 17bc23c72d454003ea39d723552e25af9c696abe Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 17:34:08 +0800 Subject: [PATCH 28/36] Rename pipelines. --- .github/workflows/nuget.yml | 2 +- .github/workflows/tag.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index b85b97a9..c8412b49 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -1,4 +1,4 @@ -name: windows +name: nuget on: pull_request diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 4f3113c6..f8a87ad9 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -1,4 +1,4 @@ -name: windows +name: tagging on: pull_request From 884c0d5bdd1bdeb76015a927d2c027632d90a1ce Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 17:48:15 +0800 Subject: [PATCH 29/36] Fix yaml syntax. --- .github/workflows/nuget.yml | 2 -- .github/workflows/tag.yml | 2 -- .github/workflows/windows.yml | 20 ++------------------ 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index c8412b49..817a59b7 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -5,8 +5,6 @@ on: pull_request jobs: build: runs-on: windows-latest - strategy: - matrix: steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index f8a87ad9..ff743f57 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -5,8 +5,6 @@ on: pull_request jobs: build: runs-on: windows-latest - strategy: - matrix: steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ece0372b..0bf2a92f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -9,27 +9,11 @@ jobs: matrix: # windows-2016 and windows-2019 have MSVC 2017 and 2019 installed # respectively: https://github.com/actions/virtual-environments. - os: [windows-2016, windows-2019] + os: [windows-2019, windows-latest] platform: [Win32, x64] build_type: [Debug, Release] standard: [11, 17, 20] - include: - - os: windows-2016 - platform: Win32 - build_type: Debug - shared: -DBUILD_SHARED_LIBS=ON - exclude: - - os: windows-2016 - platform: Win32 - - os: windows-2016 - standard: 17 - - os: windows-2016 - standard: 20 - - os: windows-2019 - standard: 11 - - os: windows-2019 - standard: 20 - platform: Win32 + shared: [ -DBUILD_SHARED_LIBS=ON ] steps: - uses: actions/checkout@v2 From 1846821a5f74d9707c398f0be6ef05b5617f109f Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 17:58:39 +0800 Subject: [PATCH 30/36] Fix yaml syntax. --- .github/workflows/nuget.yml | 5 ++--- .github/workflows/tag.yml | 5 ++--- .github/workflows/windows.yml | 4 +--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 817a59b7..3ef4f5c0 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -25,9 +25,8 @@ jobs: - name: Build working-directory: ${{runner.workspace}}/build run: | - $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors - cmake --build . --config Debug --parallel $threads - cmake --build . --config Release --parallel $threads + cmake --build . --config Debug + cmake --build . --config Release - name: Test working-directory: build diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index ff743f57..e1e17cf0 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -25,9 +25,8 @@ jobs: - name: Build working-directory: ${{runner.workspace}}/build run: | - $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors - cmake --build . --config Debug --parallel $threads - cmake --build . --config Release --parallel $threads + cmake --build . --config Debug + cmake --build . --config Release - name: Test working-directory: build diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0bf2a92f..76837c13 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -33,9 +33,7 @@ jobs: - name: Build working-directory: ${{runner.workspace}}/build - run: | - $threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors - cmake --build . --config ${{matrix.build_type}} --parallel $threads + run: cmake --build . --config ${{matrix.build_type}} - name: Test working-directory: ${{runner.workspace}}/build From 22d9268546aa337c539bd4536f8c0a8133d325a6 Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 18:04:53 +0800 Subject: [PATCH 31/36] Fix yaml syntax. --- .github/workflows/nuget.yml | 2 +- .github/workflows/tag.yml | 2 +- .github/workflows/windows.yml | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 3ef4f5c0..45d4312b 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -23,7 +23,7 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON -A x64 -DCMAKE_CXX_STANDARD=17 .. - name: Build - working-directory: ${{runner.workspace}}/build + working-directory: build run: | cmake --build . --config Debug cmake --build . --config Release diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index e1e17cf0..ddb5b3ce 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -23,7 +23,7 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON -A x64 -DCMAKE_CXX_STANDARD=17 .. - name: Build - working-directory: ${{runner.workspace}}/build + working-directory: build run: | cmake --build . --config Debug cmake --build . --config Release diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 76837c13..fc1ff2d1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -19,17 +19,15 @@ jobs: - uses: actions/checkout@v2 - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + run: cmake -E make_directory build - name: Configure - # Use a bash shell for $GITHUB_WORKSPACE. - shell: bash - working-directory: ${{runner.workspace}}/build + working-directory: build run: | cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ -A ${{matrix.platform}} \ -DCMAKE_CXX_STANDARD=${{matrix.standard}} \ - $GITHUB_WORKSPACE + .. - name: Build working-directory: ${{runner.workspace}}/build From 0f00658e3041109e4b746ee126732d2daeaf222c Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 18:09:32 +0800 Subject: [PATCH 32/36] Fix yaml syntax. --- .github/workflows/windows.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fc1ff2d1..49810e03 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -24,10 +24,7 @@ jobs: - name: Configure working-directory: build run: | - cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ - -A ${{matrix.platform}} \ - -DCMAKE_CXX_STANDARD=${{matrix.standard}} \ - .. + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} -A ${{matrix.platform}} -DCMAKE_CXX_STANDARD=${{matrix.standard}} .. - name: Build working-directory: ${{runner.workspace}}/build From 146e00d716639611101e0020c255817532471212 Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 18:14:48 +0800 Subject: [PATCH 33/36] Fix yaml syntax. --- .github/workflows/windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 49810e03..87bf736f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,11 +27,11 @@ jobs: cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} -A ${{matrix.platform}} -DCMAKE_CXX_STANDARD=${{matrix.standard}} .. - name: Build - working-directory: ${{runner.workspace}}/build + working-directory: build run: cmake --build . --config ${{matrix.build_type}} - name: Test - working-directory: ${{runner.workspace}}/build + working-directory: build run: ctest -C ${{matrix.build_type}} -V env: CTEST_OUTPUT_ON_FAILURE: True From 753cc919a254ce765b750e9f1ca757526a6c8a28 Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 18:18:37 +0800 Subject: [PATCH 34/36] Fix yaml syntax. --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 87bf736f..c79f7360 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -12,7 +12,7 @@ jobs: os: [windows-2019, windows-latest] platform: [Win32, x64] build_type: [Debug, Release] - standard: [11, 17, 20] + standard: [11, 17] shared: [ -DBUILD_SHARED_LIBS=ON ] steps: From 5613a2df4805e7faf66306761014545f7e1557de Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 18:33:33 +0800 Subject: [PATCH 35/36] Fix nuget packaging. --- .github/workflows/nuget.yml | 4 ++-- .github/workflows/tag.yml | 4 ++-- bundle/nuget/fmt.nuspec | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 45d4312b..cb2d24d4 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -36,6 +36,6 @@ jobs: - name: NuGet Pack run: | - cp readme.rst readme.md - cp license.rst readme.md + cp readme.rst readme.md + cp license.rst license.md nuget pack bundle\nuget\fmt.nuspec -properties Id=wuganhao.fmt -version 1.0-dev -basepath . -OutputFileNamesWithoutVersion \ No newline at end of file diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index ddb5b3ce..7b3950e3 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -36,8 +36,8 @@ jobs: - name: NuGet Pack run: | - cp readme.rst readme.md - cp license.rst readme.md + cp readme.rst readme.md + cp license.rst license.md $Tag = "${{ github.ref }}" -replace '(refs/tags/)(.+)', '$2' $Version = $Tag -replace '((\d+)(\.\d+){1,3})(-.+|)', '$1' $InformationalVersion = "$Tag SHA-${{ github.SHA }}" diff --git a/bundle/nuget/fmt.nuspec b/bundle/nuget/fmt.nuspec index 79d588af..935b0c99 100644 --- a/bundle/nuget/fmt.nuspec +++ b/bundle/nuget/fmt.nuspec @@ -24,7 +24,7 @@ - - + + \ No newline at end of file From f78268dd14868c07860d70b4a7e896d18c1e6f82 Mon Sep 17 00:00:00 2001 From: "Wu, Ganhao" Date: Thu, 23 Dec 2021 18:55:41 +0800 Subject: [PATCH 36/36] Fix yaml syntax. --- .github/workflows/tag.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 7b3950e3..6a99d5be 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -1,6 +1,9 @@ name: tagging -on: pull_request +on: + push: + tags: + - '*' jobs: build: