Merge remote-tracking branch 'github/master' into feature/fix-no-wchar
This commit is contained in:
commit
00884d94cd
@ -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
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
@ -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
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
|
||||
@ -281,6 +281,9 @@
|
||||
This doesn't introduce a dependency on ``<locale>`` 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 <https://github.com/fmtlib/fmt/issues/2327>`_).
|
||||
|
||||
|
||||
@ -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<Context>& store)
|
||||
:
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
basic_format_args<Context>(),
|
||||
#endif
|
||||
data_(store.data_),
|
||||
named_info_(store.named_info_) {
|
||||
}
|
||||
const dynamic_format_arg_store<Context>& store) = delete;
|
||||
|
||||
/**
|
||||
\rst
|
||||
|
||||
@ -11,14 +11,29 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <iterator>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <ostream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// Enable tzset.
|
||||
#ifndef FMT_USE_TZSET
|
||||
// UWP doesn't provide _tzset.
|
||||
# if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
# 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
|
||||
@ -290,36 +305,42 @@ inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
|
||||
template <typename Char>
|
||||
inline auto do_write(const std::tm& time, const std::locale& loc, char format,
|
||||
char modifier) -> std::basic_string<Char> {
|
||||
auto&& os = std::basic_ostringstream<Char>();
|
||||
os.imbue(loc);
|
||||
using iterator = std::ostreambuf_iterator<Char>;
|
||||
const auto& facet = std::use_facet<std::time_put<Char, iterator>>(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 <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
char format, char modifier = 0) -> OutputIt {
|
||||
auto str = do_write<Char>(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 <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(std::is_same<Char, char>::value)>
|
||||
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
char format, char modifier = 0) -> OutputIt {
|
||||
auto str = do_write<char>(time, loc, format, modifier);
|
||||
template <typename CodeUnit> struct codecvt_result {
|
||||
static constexpr const size_t max_size = 32;
|
||||
CodeUnit buf[max_size];
|
||||
CodeUnit* end;
|
||||
};
|
||||
template <typename CodeUnit>
|
||||
constexpr const size_t codecvt_result<CodeUnit>::max_size;
|
||||
|
||||
template <typename CodeUnit>
|
||||
void write_codecvt(codecvt_result<CodeUnit>& out, string_view in_buf,
|
||||
const std::locale& loc) {
|
||||
using codecvt = std::codecvt<CodeUnit, char, std::mbstate_t>;
|
||||
#if FMT_CLANG_VERSION
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wdeprecated"
|
||||
auto& f = std::use_facet<codecvt>(loc);
|
||||
# pragma clang diagnostic pop
|
||||
#else
|
||||
auto& f = std::use_facet<codecvt>(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 <typename OutputIt>
|
||||
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 +353,89 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
using code_unit = char32_t;
|
||||
#endif
|
||||
|
||||
using codecvt = std::codecvt<code_unit, char, std::mbstate_t>;
|
||||
#if FMT_CLANG_VERSION
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wdeprecated"
|
||||
auto& f = std::use_facet<codecvt>(loc);
|
||||
# pragma clang diagnostic pop
|
||||
#else
|
||||
auto& f = std::use_facet<codecvt>(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<code_unit>;
|
||||
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<char, unit_t::max_size * 4>();
|
||||
for (code_unit* p = unit.buf; p != unit.end; ++p) {
|
||||
uint32_t c = static_cast<uint32_t>(*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<uint32_t>(*p) - 0x35fdc00;
|
||||
}
|
||||
if (c < 0x80) {
|
||||
str.push_back(static_cast<char>(c));
|
||||
buf.push_back(static_cast<char>(c));
|
||||
} else if (c < 0x800) {
|
||||
str.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
buf.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
|
||||
str.push_back(static_cast<char>(0xe0 | (c >> 12)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
buf.push_back(static_cast<char>(0xe0 | (c >> 12)));
|
||||
buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else if (c >= 0x10000 && c <= 0x10ffff) {
|
||||
str.push_back(static_cast<char>(0xf0 | (c >> 18)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
|
||||
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
buf.push_back(static_cast<char>(0xf0 | (c >> 18)));
|
||||
buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
|
||||
buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||
} else {
|
||||
FMT_THROW(format_error("failed to format time"));
|
||||
}
|
||||
}
|
||||
return copy_str<char>(buf.data(), buf.data() + buf.size(), out);
|
||||
}
|
||||
return std::copy(str.begin(), str.end(), out);
|
||||
return copy_str<char>(in.data(), in.data() + in.size(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
|
||||
-> OutputIt {
|
||||
codecvt_result<Char> unit;
|
||||
write_codecvt(unit, sv, loc);
|
||||
return copy_str<Char>(unit.buf, unit.end, out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(std::is_same<Char, char>::value)>
|
||||
auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
|
||||
-> OutputIt {
|
||||
return write_encoded_tm_str(out, sv, loc);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void do_write(buffer<Char>& buf, const std::tm& time,
|
||||
const std::locale& loc, char format, char modifier) {
|
||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
||||
auto&& os = std::basic_ostream<Char>(&format_buf);
|
||||
os.imbue(loc);
|
||||
using iterator = std::ostreambuf_iterator<Char>;
|
||||
const auto& facet = std::use_facet<std::time_put<Char, iterator>>(loc);
|
||||
auto end = facet.put(os, os, Char(' '), &time, format, modifier);
|
||||
if (end.failed()) FMT_THROW(format_error("failed to format time"));
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
char format, char modifier = 0) -> OutputIt {
|
||||
auto&& buf = get_buffer<Char>(out);
|
||||
do_write<Char>(buf, time, loc, format, modifier);
|
||||
return buf.out();
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(std::is_same<Char, char>::value)>
|
||||
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
char format, char modifier = 0) -> OutputIt {
|
||||
auto&& buf = basic_memory_buffer<Char>();
|
||||
do_write<char>(buf, time, loc, format, modifier);
|
||||
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
@ -878,7 +932,13 @@ template <typename T>
|
||||
struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>
|
||||
: std::true_type {};
|
||||
|
||||
#if defined(_WIN32)
|
||||
template <typename T, typename = void>
|
||||
struct has_member_data_tm_zone : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>
|
||||
: std::true_type {};
|
||||
|
||||
#if FMT_USE_TZSET
|
||||
inline void tzset_once() {
|
||||
static bool init = []() -> bool {
|
||||
_tzset();
|
||||
@ -1021,7 +1081,9 @@ template <typename OutputIt, typename Char> 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) {
|
||||
@ -1035,6 +1097,14 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
#endif
|
||||
}
|
||||
|
||||
void format_tz_name_impl(std::true_type) {
|
||||
if (is_classic_)
|
||||
out_ = write_tm_str<Char>(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<Char>(out_, tm_, loc_, format, modifier);
|
||||
}
|
||||
@ -1144,7 +1214,7 @@ template <typename OutputIt, typename Char> class tm_writer {
|
||||
void on_utc_offset() {
|
||||
format_utc_offset_impl(has_member_data_tm_gmtoff<std::tm>{});
|
||||
}
|
||||
void on_tz_name() { format_localized('Z'); }
|
||||
void on_tz_name() { format_tz_name_impl(has_member_data_tm_zone<std::tm>{}); }
|
||||
|
||||
void on_year(numeric_system ns) {
|
||||
if (is_classic_ || ns == numeric_system::standard)
|
||||
@ -1318,21 +1388,21 @@ inline bool isfinite(T) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Converts value to int and checks that it's in the range [0, upper).
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::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 <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::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<int>(value);
|
||||
return static_cast<Int>(value);
|
||||
}
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
inline int to_nonnegative_int(T value, int upper) {
|
||||
template <typename T, typename Int, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
inline Int to_nonnegative_int(T value, Int upper) {
|
||||
FMT_ASSERT(
|
||||
std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)),
|
||||
"invalid value");
|
||||
(void)upper;
|
||||
return static_cast<int>(value);
|
||||
return static_cast<Int>(value);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
@ -1389,15 +1459,37 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period,
|
||||
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
|
||||
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
||||
// 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) {
|
||||
return n == 0 ? 1 : 10 * pow10(n - 1);
|
||||
}
|
||||
|
||||
template <class Rep, class Period,
|
||||
FMT_ENABLE_IF(std::numeric_limits<Rep>::is_signed)>
|
||||
constexpr std::chrono::duration<Rep, Period> abs(
|
||||
std::chrono::duration<Rep, Period> d) {
|
||||
using common_type = typename std::common_type<Rep, std::intmax_t>::type;
|
||||
auto ms = mod(d.count() * static_cast<common_type>(Period::num) /
|
||||
static_cast<common_type>(Period::den) * 1000,
|
||||
1000);
|
||||
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(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 <class Rep, class Period,
|
||||
FMT_ENABLE_IF(!std::numeric_limits<Rep>::is_signed)>
|
||||
constexpr std::chrono::duration<Rep, Period> abs(
|
||||
std::chrono::duration<Rep, Period> d) {
|
||||
return d;
|
||||
}
|
||||
|
||||
template <typename Char, typename Rep, typename OutputIt,
|
||||
@ -1560,6 +1652,36 @@ struct chrono_formatter {
|
||||
out = format_decimal<char_type>(out, n, num_digits).end;
|
||||
}
|
||||
|
||||
template <class Duration> void write_fractional_seconds(Duration d) {
|
||||
constexpr auto num_fractional_digits =
|
||||
count_fractional_digits(Duration::period::num, Duration::period::den);
|
||||
|
||||
using subsecond_precision = std::chrono::duration<
|
||||
typename std::common_type<typename Duration::rep,
|
||||
std::chrono::seconds::rep>::type,
|
||||
std::ratio<1, detail::pow10(num_fractional_digits)>>;
|
||||
if (std::ratio_less<typename subsecond_precision::period,
|
||||
std::chrono::seconds::period>::value) {
|
||||
*out++ = '.';
|
||||
const auto subseconds =
|
||||
std::chrono::treat_as_floating_point<
|
||||
typename subsecond_precision::rep>::value
|
||||
? (detail::abs(d) -
|
||||
std::chrono::duration_cast<std::chrono::seconds>(d))
|
||||
.count()
|
||||
: std::chrono::duration_cast<subsecond_precision>(
|
||||
detail::abs(d) -
|
||||
std::chrono::duration_cast<std::chrono::seconds>(d))
|
||||
.count();
|
||||
uint32_or_64_or_128_t<long long> n =
|
||||
to_unsigned(to_nonnegative_int(subseconds, max_value<long long>()));
|
||||
int num_digits = detail::count_digits(n);
|
||||
if (num_fractional_digits > num_digits)
|
||||
out = std::fill_n(out, num_fractional_digits - num_digits, '0');
|
||||
out = format_decimal<char_type>(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); }
|
||||
@ -1637,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<rep, Period>;
|
||||
using duration_Rep = std::chrono::duration<Rep, Period>;
|
||||
auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val});
|
||||
#else
|
||||
auto tmpval = std::chrono::duration<Rep, Period>(val);
|
||||
#endif
|
||||
auto ms = get_milliseconds(tmpval);
|
||||
if (ms != std::chrono::milliseconds(0)) {
|
||||
*out++ = '.';
|
||||
write(ms.count(), 3);
|
||||
}
|
||||
write_fractional_seconds(std::chrono::duration<rep, Period>{val});
|
||||
return;
|
||||
}
|
||||
auto time = tm();
|
||||
@ -1678,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() {
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
#ifndef FMT_CORE_H_
|
||||
#define FMT_CORE_H_
|
||||
|
||||
#include <cstdio> // std::FILE
|
||||
#include <cstddef> // std::byte
|
||||
#include <cstdio> // std::FILE
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
@ -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 <typename Char> using std_string_view = std::basic_string_view<Char>;
|
||||
#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW)
|
||||
@ -560,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
|
||||
@ -1102,6 +1109,11 @@ template <typename... Args> constexpr auto count_named_args() -> size_t {
|
||||
return count<is_named_arg<Args>::value...>();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
constexpr auto count_statically_named_args() -> size_t {
|
||||
return count<is_statically_named_arg<Args>::value...>();
|
||||
}
|
||||
|
||||
enum class type {
|
||||
none_type,
|
||||
// Integer types should go first,
|
||||
@ -1357,20 +1369,21 @@ template <typename Context> struct arg_mapper {
|
||||
-> basic_string_view<char_type> {
|
||||
return std_string_view<char_type>(val);
|
||||
}
|
||||
FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val)
|
||||
-> decltype(this->map("")) {
|
||||
|
||||
using cstring_result = conditional_t<std::is_same<char_type, char>::value,
|
||||
const char*, unformattable_pointer>;
|
||||
|
||||
FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) -> cstring_result {
|
||||
return map(reinterpret_cast<const char*>(val));
|
||||
}
|
||||
FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val)
|
||||
-> decltype(this->map("")) {
|
||||
-> cstring_result {
|
||||
return map(reinterpret_cast<const char*>(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<const char*>(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<const char*>(val));
|
||||
}
|
||||
|
||||
@ -1402,15 +1415,20 @@ template <typename Context> struct arg_mapper {
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(std::is_enum<T>::value &&
|
||||
!has_formatter<T, Context>::value &&
|
||||
!has_fallback_formatter<T, char_type>::value)>
|
||||
FMT_ENABLE_IF(
|
||||
std::is_enum<T>::value&& std::is_convertible<T, int>::value &&
|
||||
!has_formatter<T, Context>::value &&
|
||||
!has_fallback_formatter<T, char_type>::value)>
|
||||
FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
|
||||
-> decltype(std::declval<arg_mapper>().map(
|
||||
static_cast<typename std::underlying_type<T>::type>(val))) {
|
||||
return map(static_cast<typename std::underlying_type<T>::type>(val));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned {
|
||||
return map(static_cast<unsigned char>(val));
|
||||
}
|
||||
|
||||
template <typename T, typename U = remove_cvref_t<T>>
|
||||
struct formattable
|
||||
: bool_constant<has_const_formatter<U, Context>() ||
|
||||
@ -1482,14 +1500,11 @@ class appender : public std::back_insert_iterator<detail::buffer<char>> {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -2472,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)
|
||||
@ -3033,7 +3048,8 @@ template <typename Char, typename... Args> class basic_format_string {
|
||||
std::is_reference<Args>::value)...>() == 0,
|
||||
"passing views as lvalues is disallowed");
|
||||
#ifdef FMT_HAS_CONSTEVAL
|
||||
if constexpr (detail::count_named_args<Args...>() == 0) {
|
||||
if constexpr (detail::count_named_args<Args...>() ==
|
||||
detail::count_statically_named_args<Args...>()) {
|
||||
using checker = detail::format_string_checker<Char, detail::error_handler,
|
||||
remove_cvref_t<Args>...>;
|
||||
detail::parse_format_string<true>(str_, checker(s, {}));
|
||||
|
||||
@ -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<int>() - precision_offset) {
|
||||
FMT_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) {
|
||||
|
||||
@ -265,6 +265,38 @@ FMT_END_NAMESPACE
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Streambuf> class formatbuf : public Streambuf {
|
||||
private:
|
||||
using char_type = typename Streambuf::char_type;
|
||||
using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
|
||||
using int_type = typename Streambuf::int_type;
|
||||
using traits_type = typename Streambuf::traits_type;
|
||||
|
||||
buffer<char_type>& buffer_;
|
||||
|
||||
public:
|
||||
explicit formatbuf(buffer<char_type>& 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<char_type>(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<Dest*>(&source)` that doesn't have
|
||||
// undefined behavior (e.g. due to type aliasing).
|
||||
// Example: uint64_t d = bit_cast<uint64_t>(2.718);
|
||||
@ -2574,6 +2606,7 @@ FMT_FORMAT_AS(unsigned long, unsigned long long);
|
||||
FMT_FORMAT_AS(Char*, const Char*);
|
||||
FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>);
|
||||
FMT_FORMAT_AS(std::nullptr_t, const void*);
|
||||
FMT_FORMAT_AS(detail::byte, unsigned char);
|
||||
FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);
|
||||
|
||||
template <typename Char>
|
||||
@ -3016,16 +3049,14 @@ constexpr auto operator"" _a(const char* s, size_t) -> detail::udl_arg<char> {
|
||||
# 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)
|
||||
FMT_DEPRECATED constexpr auto operator"" _format(const char* s, size_t n)
|
||||
-> detail::udl_formatter<char> {
|
||||
return {{s, n}};
|
||||
}
|
||||
|
||||
@ -18,36 +18,6 @@ template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
using int_type = typename std::basic_streambuf<Char>::int_type;
|
||||
using traits_type = typename std::basic_streambuf<Char>::traits_type;
|
||||
|
||||
buffer<Char>& buffer_;
|
||||
|
||||
public:
|
||||
explicit formatbuf(buffer<Char>& 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<Char>(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 <typename T, typename Char, typename Enable = void>
|
||||
class is_streamable {
|
||||
@ -75,6 +45,8 @@ struct is_streamable<
|
||||
enable_if_t<
|
||||
std::is_arithmetic<T>::value || std::is_array<T>::value ||
|
||||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
|
||||
std::is_same<T, std::basic_string<Char>>::value ||
|
||||
std::is_same<T, std_string_view<Char>>::value ||
|
||||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
|
||||
: std::false_type {};
|
||||
|
||||
@ -97,7 +69,7 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
auto&& format_buf = formatbuf<Char>(buf);
|
||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
||||
auto&& output = std::basic_ostream<Char>(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
|
||||
@ -19,21 +19,6 @@
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char, typename Enable = void> struct formatting_range {
|
||||
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||
Char prefix = '{';
|
||||
Char postfix = '}';
|
||||
#else
|
||||
Char prefix = '[';
|
||||
Char postfix = ']';
|
||||
#endif
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void> struct formatting_tuple {
|
||||
Char prefix = '(';
|
||||
Char postfix = ')';
|
||||
@ -71,7 +56,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 <typename T> class is_std_string_like {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
@ -86,6 +71,32 @@ template <typename T> class is_std_string_like {
|
||||
template <typename Char>
|
||||
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename T> class is_map {
|
||||
template <typename U> static auto check(U*) -> typename U::mapped_type;
|
||||
template <typename> 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<decltype(check<T>(nullptr))>::value;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename T> class is_set {
|
||||
template <typename U> static auto check(U*) -> typename U::key_type;
|
||||
template <typename> 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<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
@ -573,6 +584,7 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
||||
!detail::is_map<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
@ -588,11 +600,9 @@ struct formatter<
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <
|
||||
@ -600,17 +610,64 @@ struct formatter<
|
||||
FMT_ENABLE_IF(
|
||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::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<T>::value ? '{' : '[';
|
||||
Char postfix = detail::is_set<T>::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<Char>(out, *it);
|
||||
++i;
|
||||
}
|
||||
return detail::copy(formatting.postfix, out);
|
||||
*out++ = postfix;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
detail::is_map<T>::value
|
||||
// Workaround a bug in MSVC 2019 and earlier.
|
||||
#if !FMT_MSC_VER
|
||||
&& (is_formattable<detail::value_type<T>, Char>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <
|
||||
typename FormatContext, typename U,
|
||||
FMT_ENABLE_IF(
|
||||
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::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<Char>(out, item.first);
|
||||
*out++ = ':';
|
||||
*out++ = ' ';
|
||||
out = detail::write_range_entry<Char>(out, item.second);
|
||||
++i;
|
||||
}
|
||||
*out++ = '}';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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 <cwchar>
|
||||
#include <tuple>
|
||||
@ -217,11 +217,11 @@ inline void vprint(wstring_view fmt, wformat_args args) {
|
||||
|
||||
template <typename... T>
|
||||
void print(std::FILE* f, wformat_string<T...> 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 <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(wstring_view(fmt), make_wformat_args(args...));
|
||||
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,4 +233,4 @@ template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_WCHAR_H_
|
||||
#endif // FMT_XCHAR_H_
|
||||
|
||||
@ -8,19 +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"))
|
||||
#set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros)
|
||||
endif ()
|
||||
|
||||
function(add_fmt_executable name)
|
||||
add_executable(${name} ${ARGN})
|
||||
if (MINGW)
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(args_test, basic) {
|
||||
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char str[] = "1234567890";
|
||||
store.push_back(str);
|
||||
store.push_back(std::cref(str));
|
||||
@ -48,7 +48,7 @@ template <> struct formatter<custom_type> {
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(args_test, custom_format) {
|
||||
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
auto c = custom_type();
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
@ -77,7 +77,7 @@ template <> struct formatter<to_stringable> {
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(args_test, to_string_and_formatter) {
|
||||
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.reserve(2, 1);
|
||||
store.push_back(1.5f);
|
||||
store.push_back(fmt::arg("a1", 42));
|
||||
@ -163,7 +163,7 @@ template <> struct formatter<copy_throwable> {
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(args_test, throw_on_copy) {
|
||||
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> 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<fmt::format_context>();
|
||||
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");
|
||||
}
|
||||
|
||||
@ -543,15 +543,12 @@ TEST(chrono_test, negative_durations) {
|
||||
}
|
||||
|
||||
TEST(chrono_test, special_durations) {
|
||||
EXPECT_EQ(
|
||||
"40.",
|
||||
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
|
||||
auto value = fmt::format("{:%S}", std::chrono::duration<double>(1e20));
|
||||
EXPECT_EQ(value, "40");
|
||||
auto nan = std::numeric_limits<double>::quiet_NaN();
|
||||
EXPECT_EQ(
|
||||
"nan nan nan nan nan:nan nan",
|
||||
fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan)));
|
||||
(void)fmt::format("{:%S}",
|
||||
std::chrono::duration<float, std::atto>(1.79400457e+31f));
|
||||
EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)),
|
||||
"1Es");
|
||||
EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)),
|
||||
@ -585,4 +582,44 @@ TEST(chrono_test, weekday) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
using attoseconds = std::chrono::duration<long long, std::atto>;
|
||||
// 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<double, std::nano>;
|
||||
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
|
||||
|
||||
@ -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<signed char*>::value, "");
|
||||
static_assert(fmt::is_formattable<unsigned char*>::value, "");
|
||||
@ -777,6 +779,7 @@ TEST(core_test, is_formattable) {
|
||||
|
||||
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
|
||||
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
|
||||
static_assert(!fmt::is_formattable<test_scoped_enum>::value, "");
|
||||
}
|
||||
|
||||
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
|
||||
|
||||
@ -934,6 +934,9 @@ TEST(format_test, precision) {
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
|
||||
fmt::detail::max_value<int>()),
|
||||
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"));
|
||||
}
|
||||
@ -1758,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");
|
||||
@ -1765,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);
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<std::string>());
|
||||
EXPECT_TRUE(fmt::is_formattable<fmt::detail::std_string_view<char>>());
|
||||
}
|
||||
|
||||
@ -55,7 +55,12 @@ TEST(ranges_test, format_vector2) {
|
||||
|
||||
TEST(ranges_test, format_map) {
|
||||
auto m = std::map<std::string, int>{{"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_set) {
|
||||
EXPECT_EQ(fmt::format("{}", std::set<std::string>{"one", "two"}),
|
||||
"{\"one\", \"two\"}");
|
||||
}
|
||||
|
||||
TEST(ranges_test, format_pair) {
|
||||
@ -190,7 +195,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>{test_enum::foo};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user