Merge pull request #3 from wuganhao/feature/fix-no-wchar

Fix - no wide char string
This commit is contained in:
Wu, Ganhao 2021-12-24 04:00:20 +08:00 committed by GitHub
commit 9e2fbdcdc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 652 additions and 261 deletions

41
.github/workflows/nuget.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: nuget
on: pull_request
jobs:
build:
runs-on: windows-latest
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: build
run: |
cmake --build . --config Debug
cmake --build . --config Release
- 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 license.md
nuget pack bundle\nuget\fmt.nuspec -properties Id=wuganhao.fmt -version 1.0-dev -basepath . -OutputFileNamesWithoutVersion

55
.github/workflows/tag.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: tagging
on:
push:
tags:
- '*'
jobs:
build:
runs-on: windows-latest
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: build
run: |
cmake --build . --config Debug
cmake --build . --config Release
- 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 license.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 }}

View File

@ -9,52 +9,29 @@ 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
standard: [11, 17]
shared: [ -DBUILD_SHARED_LIBS=ON ]
steps:
- 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
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} -A ${{matrix.platform}} -DCMAKE_CXX_STANDARD=${{matrix.standard}} ..
- name: Build
working-directory: ${{runner.workspace}}/build
run: |
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
cmake --build . --config ${{matrix.build_type}} --parallel $threads
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

3
.gitignore vendored
View File

@ -42,3 +42,6 @@ fmt.pc
*.vcxproj
*.sln
/Release/
*.nupkg
/license.md
/readme.md

View File

@ -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}>)

View File

@ -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>`_).

30
bundle/nuget/fmt.nuspec Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>$Id$</id>
<version>$version$</version>
<authors>Wu Ganhao&lt;wuganhao@hotmail.com&gt;</authors>
<owners>Wu Ganhao&lt;wuganhao@hotmail.com&gt;</owners>
<developmentDependency>true</developmentDependency>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams.</description>
<repository type="git" url="https://github.com/$repository$/" />
<license type="file">docs\license.md</license>
<readme>docs\readme.md</readme>
<dependencies>
<group targetFramework="native0.0" />
</dependencies>
</metadata>
<files>
<file src="include\fmt\**\*" target="include\fmt\" />
<file src="build\Debug\fmtd.lib" target="lib\native\x64\debug\" />
<file src="build\bin\Debug\fmtd.dll" target="lib\native\x64\debug\" />
<file src="build\bin\Debug\fmtd.pdb" target="lib\native\x64\debug\" />
<file src="build\Release\fmt.lib" target="lib\native\x64\debug\" />
<file src="build\bin\Release\fmt.dll" target="lib\native\x64\debug\" />
<file src="bundle\nuget\fmt.props" target="build\$Id$.props" />
<file src="bundle\nuget\fmt.targets" target="build\$Id$.targets" />
<file src="license.md" target="docs\" />
<file src="readme.md" target="docs\" />
</files>
</package>

12
bundle/nuget/fmt.props Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<fmtIncludePaths>$(MSBuildThisFileDirectory)..\include\;$(MSBuildThisFileDirectory)..\lib\native\x64\$(Configuration)</fmtIncludePaths>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<fmtLibs>fmt.lib;</fmtLibs>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<fmtLibs>fmtd.lib;</fmtLibs>
</PropertyGroup>
</Project>

29
bundle/nuget/fmt.targets Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(fmtIncludePaths);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>%(fmtLibs);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<Link>
<AdditionalLibraryDirectories>$(MSBuildThisFileDirectory)..\lib\native\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<PropertyGroup Label="Globals">
<BuildDependsOn>$(BuildDependsOn);DeployfmtArtifacts</BuildDependsOn>
</PropertyGroup>
<ItemGroup>
<wxWidgetsArtifacts Include="$(MSBuildThisFileDirectory)..\lib\native\x64\$(Configuration)\*.dll" />
<wxWidgetsArtifacts Condition="'$(Configuration)'=='Debug'" Include="$(MSBuildThisFileDirectory)..\lib\native\x64\$(Configuration)\*.pdb" />
</ItemGroup>
<Target Name="DeployfmtArtifacts">
<Error Text="Package 'wxWidgets' currently only support 64bit build" Condition="'$(Platform)'!='x64'" />
<Copy SourceFiles="@(fmtArtifacts)" DestinationFolder="$(TargetDir)\" />
</Target>
</Project>

View File

@ -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

View File

@ -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() {

View File

@ -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, {}));

View File

@ -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) {

View File

@ -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}};
}

View File

@ -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>());

View File

@ -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;
}
};

View File

@ -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_

View File

@ -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)

View File

@ -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");
}

View File

@ -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

View File

@ -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"); }

View File

@ -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);

View File

@ -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";

View File

@ -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)

View File

@ -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>>());
}

View File

@ -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};