mirror of
https://github.com/fmtlib/fmt.git
synced 2025-12-28 01:38:20 +01:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a33701196a | ||
|
|
1f575fd5c9 | ||
|
|
c7635288f7 | ||
|
|
c8ed78e315 | ||
|
|
e07cfb2068 | ||
|
|
1dc7af5693 | ||
|
|
f7d21c3a1a | ||
|
|
a55bcb24bd | ||
|
|
30cb2b3122 | ||
|
|
cf8d3c3229 | ||
|
|
3c3cb6f6b1 | ||
|
|
91481f255c | ||
|
|
f98048b621 | ||
|
|
4a8e2949bb | ||
|
|
3a3b0709e2 | ||
|
|
e724bbea16 | ||
|
|
665d9779ec | ||
|
|
13d07c6a3d | ||
|
|
391f922acc | ||
|
|
dc59d3df3f | ||
|
|
489dabbd31 | ||
|
|
541cd21838 | ||
|
|
1f95c34381 | ||
|
|
779449fd99 | ||
|
|
fbb568bce0 | ||
|
|
36c23bd5fd | ||
|
|
9ff0f3a7d6 | ||
|
|
fd41110d38 | ||
|
|
fc23cfbf4e | ||
|
|
fd93b633b8 | ||
|
|
7fb8d33f9d | ||
|
|
8bd02e93b2 | ||
|
|
d9c1c7353a | ||
|
|
682e097bee | ||
|
|
b9087ee587 | ||
|
|
df56fdf883 | ||
|
|
90c48b8525 | ||
|
|
5a8b7cd742 | ||
|
|
36a25d75b4 | ||
|
|
6c9304b2c2 | ||
|
|
24ab9dd19e | ||
|
|
a95dc17017 | ||
|
|
5f774c0aed | ||
|
|
6567df7f24 | ||
|
|
6c6b1fbf6e | ||
|
|
9beddd08f9 | ||
|
|
6452e3c9eb | ||
|
|
756822ba39 | ||
|
|
0b2862a1e4 | ||
|
|
258000064d | ||
|
|
e9ca7ea472 | ||
|
|
81f1cc74a7 | ||
|
|
bbcb129e02 | ||
|
|
48e0a59222 | ||
|
|
bc5c7c50fd | ||
|
|
00adc7120d | ||
|
|
c48be439f1 | ||
|
|
371f9c71ca | ||
|
|
91abfcd6cf | ||
|
|
deeab54b40 | ||
|
|
688a627d6c | ||
|
|
9bb1605f10 | ||
|
|
8061d9afbe | ||
|
|
d82e1a108d | ||
|
|
defa04e730 | ||
|
|
92d36e82c4 | ||
|
|
0db43cf7fe | ||
|
|
05be7a0764 | ||
|
|
2a1b3ac629 | ||
|
|
e1d3d3a326 | ||
|
|
b761f1279e | ||
|
|
cc1926942f | ||
|
|
d5e9166f54 | ||
|
|
b31d1a75a0 |
57
.github/workflows/windows.yml
vendored
57
.github/workflows/windows.yml
vendored
@@ -15,24 +15,30 @@ jobs:
|
||||
# https://github.com/actions/virtual-environments.
|
||||
os: [windows-2019]
|
||||
platform: [Win32, x64]
|
||||
toolset: [v140, v141, v142]
|
||||
standard: [14, 17, 20]
|
||||
shared: ["", -DBUILD_SHARED_LIBS=ON]
|
||||
build_type: [Debug, Release]
|
||||
standard: [11, 17, 20]
|
||||
exclude:
|
||||
- { toolset: v140, standard: 17 }
|
||||
- { toolset: v140, standard: 20 }
|
||||
- { toolset: v141, standard: 14 }
|
||||
- { toolset: v141, standard: 20 }
|
||||
- { toolset: v142, standard: 14 }
|
||||
- { platform: Win32, toolset: v140 }
|
||||
- { platform: Win32, toolset: v141 }
|
||||
- { platform: Win32, standard: 14 }
|
||||
- { platform: Win32, standard: 20 }
|
||||
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
|
||||
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
|
||||
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
|
||||
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
|
||||
include:
|
||||
- os: windows-2019
|
||||
platform: Win32
|
||||
build_type: Debug
|
||||
shared: -DBUILD_SHARED_LIBS=ON
|
||||
- os: windows-2022
|
||||
platform: x64
|
||||
toolset: v143
|
||||
build_type: Debug
|
||||
standard: 20
|
||||
exclude:
|
||||
- os: windows-2019
|
||||
standard: 11
|
||||
platform: Win32
|
||||
- os: windows-2019
|
||||
standard: 20
|
||||
platform: Win32
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -45,9 +51,9 @@ jobs:
|
||||
shell: bash
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||
-A ${{matrix.platform}} \
|
||||
cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
|
||||
${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
$GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
@@ -61,3 +67,26 @@ jobs:
|
||||
run: ctest -C ${{matrix.build_type}} -V
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: True
|
||||
|
||||
mingw:
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
strategy:
|
||||
matrix:
|
||||
sys: [ mingw64, mingw32, ucrt64 ]
|
||||
steps:
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
release: false
|
||||
msystem: ${{matrix.sys}}
|
||||
pacboy: cc:p cmake:p ninja:p lld:p
|
||||
- uses: actions/checkout@v2
|
||||
- name: Configure
|
||||
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||
env: { LDFLAGS: -fuse-ld=lld }
|
||||
- name: Build
|
||||
run: cmake --build ../build
|
||||
- name: Test
|
||||
run: ctest -j `nproc` --test-dir ../build
|
||||
|
||||
@@ -262,12 +262,6 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
endif ()
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
|
||||
NOT EMSCRIPTEN)
|
||||
# Fix rpmlint warning:
|
||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||
target_link_libraries(fmt -Wl,--as-needed)
|
||||
endif ()
|
||||
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
||||
endif ()
|
||||
if (FMT_SAFE_DURATION_CAST)
|
||||
|
||||
128
ChangeLog.rst
128
ChangeLog.rst
@@ -1,3 +1,129 @@
|
||||
9.1.0 - 2022-08-27
|
||||
------------------
|
||||
|
||||
* ``fmt::formatted_size`` now works at compile time
|
||||
(`#3026 <https://github.com/fmtlib/fmt/pull/3026>`_). For example
|
||||
(`godbolt <https://godbolt.org/z/1MW5rMdf8>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/compile.h>
|
||||
|
||||
int main() {
|
||||
using namespace fmt::literals;
|
||||
constexpr size_t n = fmt::formatted_size("{}"_cf, 42);
|
||||
fmt::print("{}\n", n); // prints 2
|
||||
}
|
||||
|
||||
Thanks `@marksantaniello (Mark Santaniello)
|
||||
<https://github.com/marksantaniello>`_.
|
||||
|
||||
* Fixed handling of invalid UTF-8
|
||||
(`#3038 <https://github.com/fmtlib/fmt/pull/3038>`_,
|
||||
`#3044 <https://github.com/fmtlib/fmt/pull/3044>`_,
|
||||
`#3056 <https://github.com/fmtlib/fmt/pull/3056>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
|
||||
`@skeeto (Christopher Wellons) <https://github.com/skeeto>`_.
|
||||
|
||||
* Improved Unicode support in ``ostream`` overloads of ``print``
|
||||
(`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_,
|
||||
`#3001 <https://github.com/fmtlib/fmt/pull/3001>`_,
|
||||
`#3025 <https://github.com/fmtlib/fmt/pull/3025>`_).
|
||||
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_.
|
||||
|
||||
* Fixed handling of the sign specifier in localized formatting on systems with
|
||||
32-bit ``wchar_t`` (`#3041 <https://github.com/fmtlib/fmt/issues/3041>`_).
|
||||
|
||||
* Added support for wide streams to ``fmt::streamed``
|
||||
(`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Added the ``n`` specifier that disables the output of delimiters when
|
||||
formatting ranges (`#2981 <https://github.com/fmtlib/fmt/pull/2981>`_,
|
||||
`#2983 <https://github.com/fmtlib/fmt/pull/2983>`_).
|
||||
For example (`godbolt <https://godbolt.org/z/roKqGdj8c>`__):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
auto v = std::vector{1, 2, 3};
|
||||
fmt::print("{:n}\n", v); // prints 1, 2, 3
|
||||
}
|
||||
|
||||
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
|
||||
|
||||
* Worked around problematic ``std::string_view`` constructors introduced in
|
||||
C++23 (`#3030 <https://github.com/fmtlib/fmt/issues/3030>`_,
|
||||
`#3050 <https://github.com/fmtlib/fmt/issues/3050>`_).
|
||||
Thanks `@strega-nil-ms (nicole mazzuca) <https://github.com/strega-nil-ms>`_.
|
||||
|
||||
* Improve handling (exclusion) of recursive ranges
|
||||
(`#2968 <https://github.com/fmtlib/fmt/issues/2968>`_,
|
||||
`#2974 <https://github.com/fmtlib/fmt/pull/2974>`_).
|
||||
Thanks `@Dani-Hub (Daniel Krügler) <https://github.com/Dani-Hub>`_.
|
||||
|
||||
* Improved error reporting in format string compilation
|
||||
(`#3055 <https://github.com/fmtlib/fmt/issues/3055>`_).
|
||||
|
||||
* Improved the implementation of
|
||||
`Dragonbox <https://github.com/jk-jeon/dragonbox>`_, the algorithm used for
|
||||
the default floating-point formatting
|
||||
(`#2984 <https://github.com/fmtlib/fmt/pull/2984>`_).
|
||||
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
|
||||
|
||||
* Fixed issues with floating-point formatting on exotic platforms.
|
||||
|
||||
* Improved the implementation of chrono formatting
|
||||
(`#3010 <https://github.com/fmtlib/fmt/pull/3010>`_).
|
||||
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
* Improved documentation
|
||||
(`#2966 <https://github.com/fmtlib/fmt/pull/2966>`_,
|
||||
`#3009 <https://github.com/fmtlib/fmt/pull/3009>`_,
|
||||
`#3020 <https://github.com/fmtlib/fmt/issues/3020>`_,
|
||||
`#3037 <https://github.com/fmtlib/fmt/pull/3037>`_).
|
||||
Thanks `@mwinterb <https://github.com/mwinterb>`_,
|
||||
`@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_
|
||||
and `@remiburtin (Rémi Burtin) <https://github.com/remiburtin>`_.
|
||||
|
||||
* Improved build configuration
|
||||
(`#2991 <https://github.com/fmtlib/fmt/pull/2991>`_,
|
||||
`#2995 <https://github.com/fmtlib/fmt/pull/2995>`_,
|
||||
`#3004 <https://github.com/fmtlib/fmt/issues/3004>`_,
|
||||
`#3007 <https://github.com/fmtlib/fmt/pull/3007>`_,
|
||||
`#3040 <https://github.com/fmtlib/fmt/pull/3040>`_).
|
||||
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_ and
|
||||
`@hwhsu1231 (Haowei Hsu) <https://github.com/hwhsu1231>`_.
|
||||
|
||||
* Fixed various warnings and compilation issues
|
||||
(`#2969 <https://github.com/fmtlib/fmt/issues/2969>`_,
|
||||
`#2971 <https://github.com/fmtlib/fmt/pull/2971>`_,
|
||||
`#2975 <https://github.com/fmtlib/fmt/issues/2975>`_,
|
||||
`#2982 <https://github.com/fmtlib/fmt/pull/2982>`_,
|
||||
`#2985 <https://github.com/fmtlib/fmt/pull/2985>`_,
|
||||
`#2988 <https://github.com/fmtlib/fmt/issues/2988>`_,
|
||||
`#3000 <https://github.com/fmtlib/fmt/issues/3000>`_,
|
||||
`#3006 <https://github.com/fmtlib/fmt/issues/3006>`_,
|
||||
`#3014 <https://github.com/fmtlib/fmt/issues/3014>`_,
|
||||
`#3015 <https://github.com/fmtlib/fmt/issues/3015>`_,
|
||||
`#3021 <https://github.com/fmtlib/fmt/pull/3021>`_,
|
||||
`#3023 <https://github.com/fmtlib/fmt/issues/3023>`_,
|
||||
`#3024 <https://github.com/fmtlib/fmt/pull/3024>`_,
|
||||
`#3029 <https://github.com/fmtlib/fmt/pull/3029>`_,
|
||||
`#3043 <https://github.com/fmtlib/fmt/pull/3043>`_,
|
||||
`#3052 <https://github.com/fmtlib/fmt/issues/3052>`_,
|
||||
`#3053 <https://github.com/fmtlib/fmt/pull/3053>`_,
|
||||
`#3054 <https://github.com/fmtlib/fmt/pull/3054>`_).
|
||||
Thanks `@h-friederich (Hannes Friederich) <https://github.com/h-friederich>`_,
|
||||
`@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_,
|
||||
`@olupton (Olli Lupton) <https://github.com/olupton>`_,
|
||||
`@bernhardmgruber (Bernhard Manfred Gruber)
|
||||
<https://github.com/bernhardmgruber>`_,
|
||||
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
|
||||
|
||||
9.0.0 - 2022-07-04
|
||||
------------------
|
||||
|
||||
@@ -19,7 +145,7 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr auto answer = compile_time_itoa(0.42);
|
||||
constexpr auto answer = compile_time_dtoa(0.42);
|
||||
|
||||
works with the default settings.
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
|
||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true
|
||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
|
||||
:alt: fmt is continuously fuzzed at oss-fuzz
|
||||
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
find_program(DOXYGEN doxygen)
|
||||
find_program(DOXYGEN doxygen
|
||||
PATHS "$ENV{ProgramFiles}/doxygen/bin"
|
||||
"$ENV{ProgramFiles\(x86\)}/doxygen/bin")
|
||||
if (NOT DOXYGEN)
|
||||
message(STATUS "Target 'doc' disabled (requires doxygen)")
|
||||
return ()
|
||||
endif ()
|
||||
|
||||
find_package(PythonInterp QUIET REQUIRED)
|
||||
# Find the Python interpreter and set the PYTHON_EXECUTABLE variable.
|
||||
if (CMAKE_VERSION VERSION_LESS 3.12)
|
||||
# This logic is deprecated in CMake after 3.12.
|
||||
find_package(PythonInterp QUIET REQUIRED)
|
||||
else ()
|
||||
find_package(Python QUIET REQUIRED)
|
||||
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
|
||||
endif ()
|
||||
|
||||
add_custom_target(doc
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
|
||||
|
||||
27
doc/api.rst
27
doc/api.rst
@@ -7,7 +7,7 @@ API Reference
|
||||
The {fmt} library API consists of the following parts:
|
||||
|
||||
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
|
||||
for ``char``/UTF-8 with compile-time checks and minimal dependencies
|
||||
for ``char``/UTF-8 with C++20 compile-time checks and minimal dependencies
|
||||
* :ref:`fmt/format.h <format-api>`: the full format API providing additional
|
||||
formatting functions and locale support
|
||||
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
||||
@@ -28,10 +28,10 @@ macros have prefix ``FMT_``.
|
||||
Core API
|
||||
========
|
||||
|
||||
``fmt/core.h`` defines the core API which provides main formatting functions for
|
||||
``char``/UTF-8 with compile-time checks. It has minimal include dependencies for
|
||||
better compile times. This header is only beneficial when using {fmt} as a
|
||||
library and not in the header-only mode.
|
||||
``fmt/core.h`` defines the core API which provides main formatting functions
|
||||
for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include
|
||||
dependencies for better compile times. This header is only beneficial when
|
||||
using {fmt} as a library and not in the header-only mode.
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>`
|
||||
similar to that of Python's `str.format
|
||||
@@ -70,24 +70,23 @@ checked at compile time in C++20. To pass a runtime format string wrap it in
|
||||
Compile-Time Format String Checks
|
||||
---------------------------------
|
||||
|
||||
Compile-time checks are enabled when using ``FMT_STRING``. They support built-in
|
||||
and string types as well as user-defined types with ``constexpr`` ``parse``
|
||||
functions in their ``formatter`` specializations.
|
||||
Requires C++14 and is a no-op in C++11.
|
||||
Compile-time checks are enabled by default on compilers that support C++20
|
||||
``consteval``. On older compilers you can use the ``FMT_STRING`` macro defined
|
||||
in ``fmt/format.h`` instead. It requires C++14 and is a no-op in C++11.
|
||||
|
||||
.. doxygendefine:: FMT_STRING
|
||||
|
||||
To force the use of compile-time checks, define the preprocessor variable
|
||||
To force the use of legacy compile-time checks, define the preprocessor variable
|
||||
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
|
||||
will fail to compile with regular strings. Runtime-checked
|
||||
formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc.
|
||||
will fail to compile with regular strings. Runtime-checked formatting is still
|
||||
possible using ``fmt::vformat``, ``fmt::vprint``, etc.
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_string
|
||||
:members:
|
||||
|
||||
.. doxygentypedef:: fmt::format_string
|
||||
|
||||
.. doxygenfunction:: fmt::runtime(const S&)
|
||||
.. doxygenfunction:: fmt::runtime(string_view) -> basic_runtime<char>
|
||||
|
||||
Named Arguments
|
||||
---------------
|
||||
@@ -474,7 +473,7 @@ Standard Library Types Formatting
|
||||
|
||||
``fmt/std.h`` provides formatters for:
|
||||
|
||||
* `std::filesystem::path <std::filesystem::path>`_
|
||||
* `std::filesystem::path <https://en.cppreference.com/w/cpp/filesystem/path>`_
|
||||
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
|
||||
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
|
||||
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import errno, os, re, sys
|
||||
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
|
||||
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0']
|
||||
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0']
|
||||
|
||||
class Pip:
|
||||
def __init__(self, venv_dir):
|
||||
|
||||
@@ -39,7 +39,7 @@ The ``fmt::format`` function returns a string "The answer is 42.". You can use
|
||||
.. code:: c++
|
||||
|
||||
auto out = fmt::memory_buffer();
|
||||
format_to(std::back_inserter(out),
|
||||
fmt::format_to(std::back_inserter(out),
|
||||
"For a moment, {} happened.", "nothing");
|
||||
auto data = out.data(); // pointer to the formatted data
|
||||
auto size = out.size(); // size of the formatted data
|
||||
|
||||
@@ -356,8 +356,8 @@ Range Format Specifications
|
||||
|
||||
Format specifications for range types have the following syntax:
|
||||
|
||||
..productionlist:: sf
|
||||
range_format_spec: [":" [`underlying_spec`]]
|
||||
.. productionlist:: sf
|
||||
range_format_spec: [":" [`underlying_spec`]]
|
||||
|
||||
The `underlying_spec` is parsed based on the formatter of the range's
|
||||
reference type.
|
||||
@@ -366,7 +366,7 @@ By default, a range of characters or strings is printed escaped and quoted. But
|
||||
if any `underlying_spec` is provided (even if it is empty), then the characters
|
||||
or strings are printed according to the provided specification.
|
||||
|
||||
Examples:
|
||||
Examples::
|
||||
|
||||
fmt::format("{}", std::vector{10, 20, 30});
|
||||
// Result: [10, 20, 30]
|
||||
|
||||
@@ -203,7 +203,7 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
}
|
||||
const auto min1 =
|
||||
(std::numeric_limits<IntermediateRep>::min)() / Factor::num;
|
||||
if (count < min1) {
|
||||
if (!std::is_unsigned<IntermediateRep>::value && count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
@@ -1396,7 +1396,8 @@ inline bool isfinite(T) {
|
||||
// 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),
|
||||
FMT_ASSERT(std::is_unsigned<Int>::value ||
|
||||
(value >= 0 && to_unsigned(value) <= to_unsigned(upper)),
|
||||
"invalid value");
|
||||
(void)upper;
|
||||
return static_cast<Int>(value);
|
||||
@@ -1776,7 +1777,7 @@ struct chrono_formatter {
|
||||
format_to(std::back_inserter(buf), runtime("{:.{}f}"),
|
||||
std::fmod(val * static_cast<rep>(Period::num) /
|
||||
static_cast<rep>(Period::den),
|
||||
60),
|
||||
static_cast<rep>(60)),
|
||||
num_fractional_digits);
|
||||
if (negative) *out++ = '-';
|
||||
if (buf.size() < 2 || buf[1] == '.') *out++ = '0';
|
||||
@@ -2001,13 +2002,9 @@ template <typename Char, typename Duration>
|
||||
struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||
Char> : formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR formatter() {
|
||||
this->do_parse(default_specs,
|
||||
default_specs + sizeof(default_specs) / sizeof(Char));
|
||||
}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return this->do_parse(ctx.begin(), ctx.end(), true);
|
||||
basic_string_view<Char> default_specs =
|
||||
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
|
||||
this->do_parse(default_specs.begin(), default_specs.end());
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
@@ -2015,15 +2012,8 @@ struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||
FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
return formatter<std::tm, Char>::format(localtime(val), ctx);
|
||||
}
|
||||
|
||||
static constexpr const Char default_specs[] = {'%', 'F', ' ', '%', 'T'};
|
||||
};
|
||||
|
||||
template <typename Char, typename Duration>
|
||||
constexpr const Char
|
||||
formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||
Char>::default_specs[];
|
||||
|
||||
template <typename Char> struct formatter<std::tm, Char> {
|
||||
private:
|
||||
enum class spec {
|
||||
@@ -2035,13 +2025,18 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
basic_string_view<Char> specs;
|
||||
|
||||
protected:
|
||||
template <typename It>
|
||||
FMT_CONSTEXPR auto do_parse(It begin, It end, bool with_default = false)
|
||||
-> It {
|
||||
template <typename It> FMT_CONSTEXPR auto do_parse(It begin, It end) -> It {
|
||||
if (begin != end && *begin == ':') ++begin;
|
||||
end = detail::parse_chrono_format(begin, end, detail::tm_format_checker());
|
||||
if (!with_default || end != begin)
|
||||
specs = {begin, detail::to_unsigned(end - begin)};
|
||||
// Replace default spec only if the new spec is not empty.
|
||||
if (end != begin) specs = {begin, detail::to_unsigned(end - begin)};
|
||||
return end;
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto end = this->do_parse(ctx.begin(), ctx.end());
|
||||
// basic_string_view<>::compare isn't constexpr before C++17.
|
||||
if (specs.size() == 2 && specs[0] == Char('%')) {
|
||||
if (specs[1] == Char('F'))
|
||||
@@ -2052,12 +2047,6 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
return end;
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return this->do_parse(ctx.begin(), ctx.end());
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm& tm, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
|
||||
@@ -634,7 +634,7 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("Elapsed time: {s:.2f} seconds",
|
||||
fmt::print("Elapsed time: {0:.2f} seconds",
|
||||
fmt::styled(1.23, fmt::fg(fmt::color::green) |
|
||||
fmt::bg(fmt::color::blue)));
|
||||
\endrst
|
||||
|
||||
@@ -14,8 +14,8 @@ FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Char, typename InputIt>
|
||||
inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||
counting_iterator it) {
|
||||
FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||
counting_iterator it) {
|
||||
return it + (end - begin);
|
||||
}
|
||||
|
||||
@@ -341,7 +341,7 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
next_arg_id);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
|
||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
|
||||
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
||||
}
|
||||
|
||||
@@ -397,13 +397,20 @@ constexpr auto parse_replacement_field_then_tail(S format_str) {
|
||||
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
||||
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
} else if constexpr (c != ':') {
|
||||
FMT_THROW(format_error("expected ':'"));
|
||||
} else {
|
||||
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
||||
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
||||
return parse_tail<Args, result.end, result.next_arg_id>(
|
||||
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||
result.fmt},
|
||||
format_str);
|
||||
if constexpr (result.end >= str.size() || str[result.end] != '}') {
|
||||
FMT_THROW(format_error("expected '}'"));
|
||||
return 0;
|
||||
} else {
|
||||
return parse_tail<Args, result.end + 1, result.next_arg_id>(
|
||||
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||
result.fmt},
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,7 +575,8 @@ format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
size_t formatted_size(const S& format_str, const Args&... args) {
|
||||
FMT_CONSTEXPR20 size_t formatted_size(const S& format_str,
|
||||
const Args&... args) {
|
||||
return fmt::format_to(detail::counting_iterator(), format_str, args...)
|
||||
.count();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <type_traits>
|
||||
|
||||
// The fmt library version in the form major * 10000 + minor * 100 + patch.
|
||||
#define FMT_VERSION 90000
|
||||
#define FMT_VERSION 90100
|
||||
|
||||
#if defined(__clang__) && !defined(__ibmxl__)
|
||||
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
|
||||
@@ -200,6 +200,9 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// An inline std::forward replacement.
|
||||
#define FMT_FORWARD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define FMT_UNCHECKED_ITERATOR(It) \
|
||||
using _Unchecked_type = It // Mark iterator as checked.
|
||||
@@ -273,7 +276,8 @@
|
||||
#ifndef FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
# if defined(__cpp_nontype_template_args) && \
|
||||
((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \
|
||||
__cpp_nontype_template_args >= 201911L)
|
||||
__cpp_nontype_template_args >= 201911L) && \
|
||||
!defined(__NVCOMPILER)
|
||||
# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1
|
||||
# else
|
||||
# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0
|
||||
@@ -402,7 +406,7 @@ template <typename T> auto convert_for_visit(T) -> monostate { return {}; }
|
||||
template <typename Int>
|
||||
FMT_CONSTEXPR auto to_unsigned(Int value) ->
|
||||
typename std::make_unsigned<Int>::type {
|
||||
FMT_ASSERT(value >= 0, "negative value");
|
||||
FMT_ASSERT(std::is_unsigned<Int>::value || value >= 0, "negative value");
|
||||
return static_cast<typename std::make_unsigned<Int>::type>(value);
|
||||
}
|
||||
|
||||
@@ -707,8 +711,8 @@ class basic_format_parse_context : private ErrorHandler {
|
||||
next_arg_id_ = -1;
|
||||
do_check_arg_id(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {}
|
||||
FMT_CONSTEXPR void check_dynamic_spec(int arg_id);
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
ErrorHandler::on_error(message);
|
||||
@@ -735,7 +739,8 @@ class compile_parse_context
|
||||
ErrorHandler eh = {}, int next_arg_id = 0)
|
||||
: base(format_str, eh, next_arg_id), num_args_(num_args), types_(types) {}
|
||||
|
||||
constexpr int num_args() const { return num_args_; }
|
||||
constexpr auto num_args() const -> int { return num_args_; }
|
||||
constexpr auto arg_type(int id) const -> type { return types_[id]; }
|
||||
|
||||
FMT_CONSTEXPR auto next_arg_id() -> int {
|
||||
int id = base::next_arg_id();
|
||||
@@ -748,6 +753,11 @@ class compile_parse_context
|
||||
if (id >= num_args_) this->on_error("argument not found");
|
||||
}
|
||||
using base::check_arg_id;
|
||||
|
||||
FMT_CONSTEXPR void check_dynamic_spec(int arg_id) {
|
||||
if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id]))
|
||||
this->on_error("width/precision is not integer");
|
||||
}
|
||||
};
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
@@ -763,6 +773,15 @@ basic_format_parse_context<Char, ErrorHandler>::do_check_arg_id(int id) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename ErrorHandler>
|
||||
FMT_CONSTEXPR void
|
||||
basic_format_parse_context<Char, ErrorHandler>::check_dynamic_spec(int arg_id) {
|
||||
if (detail::is_constant_evaluated()) {
|
||||
using context = detail::compile_parse_context<Char, ErrorHandler>;
|
||||
static_cast<context*>(this)->check_dynamic_spec(arg_id);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Context> class basic_format_arg;
|
||||
template <typename Context> class basic_format_args;
|
||||
template <typename Context> class dynamic_format_arg_store;
|
||||
@@ -917,11 +936,11 @@ template <typename T> class buffer {
|
||||
/** Appends data to the end of the buffer. */
|
||||
template <typename U> void append(const U* begin, const U* end);
|
||||
|
||||
template <typename I> FMT_CONSTEXPR auto operator[](I index) -> T& {
|
||||
template <typename Idx> FMT_CONSTEXPR auto operator[](Idx index) -> T& {
|
||||
return ptr_[index];
|
||||
}
|
||||
template <typename I>
|
||||
FMT_CONSTEXPR auto operator[](I index) const -> const T& {
|
||||
template <typename Idx>
|
||||
FMT_CONSTEXPR auto operator[](Idx index) const -> const T& {
|
||||
return ptr_[index];
|
||||
}
|
||||
};
|
||||
@@ -1649,6 +1668,11 @@ auto copy_str(InputIt begin, InputIt end, appender out) -> appender {
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename R, typename OutputIt>
|
||||
FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt {
|
||||
return detail::copy_str<Char>(rng.begin(), rng.end(), out);
|
||||
}
|
||||
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
|
||||
// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
|
||||
template <typename... Ts> struct void_t_impl { using type = void; };
|
||||
@@ -1708,7 +1732,7 @@ constexpr auto encode_types() -> unsigned long long {
|
||||
|
||||
template <typename Context, typename T>
|
||||
FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value<Context> {
|
||||
const auto& arg = arg_mapper<Context>().map(std::forward<T>(val));
|
||||
const auto& arg = arg_mapper<Context>().map(FMT_FORWARD(val));
|
||||
|
||||
constexpr bool formattable_char =
|
||||
!std::is_same<decltype(arg), const unformattable_char&>::value;
|
||||
@@ -1875,7 +1899,7 @@ class format_arg_store
|
||||
data_{detail::make_arg<
|
||||
is_packed, Context,
|
||||
detail::mapped_type_constant<remove_cvref_t<T>, Context>::value>(
|
||||
std::forward<T>(args))...} {
|
||||
FMT_FORWARD(args))...} {
|
||||
detail::init_named_args(data_.named_args(), 0, 0, args...);
|
||||
}
|
||||
};
|
||||
@@ -1891,7 +1915,7 @@ class format_arg_store
|
||||
template <typename Context = format_context, typename... Args>
|
||||
constexpr auto make_format_args(Args&&... args)
|
||||
-> format_arg_store<Context, remove_cvref_t<Args>...> {
|
||||
return {std::forward<Args>(args)...};
|
||||
return {FMT_FORWARD(args)...};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2240,11 +2264,14 @@ class dynamic_specs_handler
|
||||
|
||||
FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type {
|
||||
context_.check_arg_id(arg_id);
|
||||
context_.check_dynamic_spec(arg_id);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type {
|
||||
return arg_ref_type(context_.next_arg_id());
|
||||
int arg_id = context_.next_arg_id();
|
||||
context_.check_dynamic_spec(arg_id);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR auto make_arg_ref(basic_string_view<char_type> arg_id)
|
||||
@@ -2270,12 +2297,15 @@ constexpr auto to_ascii(Char c) -> underlying_t<Char> {
|
||||
return c;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline auto code_point_length_impl(char c) -> int {
|
||||
return "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4"
|
||||
[static_cast<unsigned char>(c) >> 3];
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int {
|
||||
if (const_check(sizeof(Char) != 1)) return 1;
|
||||
auto lengths =
|
||||
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4";
|
||||
int len = lengths[static_cast<unsigned char>(*begin) >> 3];
|
||||
int len = code_point_length_impl(static_cast<char>(*begin));
|
||||
|
||||
// Compute the pointer to the next character early so that the next
|
||||
// iteration can start working on the next character. Neither Clang
|
||||
@@ -2803,7 +2833,8 @@ FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs<Char>& specs,
|
||||
template <typename ErrorHandler = error_handler>
|
||||
FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type,
|
||||
ErrorHandler&& eh = {}) -> bool {
|
||||
if (type == presentation_type::none || type == presentation_type::string)
|
||||
if (type == presentation_type::none || type == presentation_type::string ||
|
||||
type == presentation_type::debug)
|
||||
return true;
|
||||
if (type != presentation_type::pointer) eh.on_error("invalid type specifier");
|
||||
return false;
|
||||
@@ -2921,7 +2952,10 @@ class format_string_checker {
|
||||
basic_string_view<Char> format_str, ErrorHandler eh)
|
||||
: context_(format_str, num_args, types_, eh),
|
||||
parse_funcs_{&parse_format_specs<Args, parse_context_type>...},
|
||||
types_{type_constant<Args, char>::value...} {}
|
||||
types_{
|
||||
mapped_type_constant<Args,
|
||||
basic_format_context<Char*, Char>>::value...} {
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
||||
|
||||
@@ -3065,6 +3099,15 @@ struct formatter<T, Char,
|
||||
return it;
|
||||
}
|
||||
|
||||
template <detail::type U = detail::type_constant<T, Char>::value,
|
||||
enable_if_t<(U == detail::type::string_type ||
|
||||
U == detail::type::cstring_type ||
|
||||
U == detail::type::char_type),
|
||||
int> = 0>
|
||||
FMT_CONSTEXPR void set_debug_format() {
|
||||
specs_.type = presentation_type::debug;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const
|
||||
-> decltype(ctx.out());
|
||||
@@ -3127,7 +3170,7 @@ template <typename Char, typename... Args> class basic_format_string {
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround broken conversion on older gcc.
|
||||
template <typename...> using format_string = string_view;
|
||||
inline auto runtime(string_view s) -> basic_string_view<char> { return s; }
|
||||
inline auto runtime(string_view s) -> string_view { return s; }
|
||||
#else
|
||||
template <typename... Args>
|
||||
using format_string = basic_format_string<char, type_identity_t<Args>...>;
|
||||
|
||||
@@ -1337,7 +1337,7 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
|
||||
|
||||
if (r < deltai) {
|
||||
// Exclude the right endpoint if necessary.
|
||||
if (r == 0 && z_mul.is_integer && !include_right_endpoint) {
|
||||
if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) {
|
||||
--ret_value.significand;
|
||||
r = float_info<T>::big_divisor;
|
||||
goto small_divisor_case_label;
|
||||
@@ -1346,26 +1346,11 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
|
||||
goto small_divisor_case_label;
|
||||
} else {
|
||||
// r == deltai; compare fractional parts.
|
||||
const carrier_uint two_fl = two_fc - 1;
|
||||
const typename cache_accessor<T>::compute_mul_parity_result x_mul =
|
||||
cache_accessor<T>::compute_mul_parity(two_fc - 1, cache, beta);
|
||||
|
||||
if (!include_left_endpoint ||
|
||||
exponent < float_info<T>::case_fc_pm_half_lower_threshold ||
|
||||
exponent > float_info<T>::divisibility_check_by_5_threshold) {
|
||||
// If the left endpoint is not included, the condition for
|
||||
// success is z^(f) < delta^(f) (odd parity).
|
||||
// Otherwise, the inequalities on exponent ensure that
|
||||
// x is not an integer, so if z^(f) >= delta^(f) (even parity), we in fact
|
||||
// have strict inequality.
|
||||
if (!cache_accessor<T>::compute_mul_parity(two_fl, cache, beta).parity) {
|
||||
goto small_divisor_case_label;
|
||||
}
|
||||
} else {
|
||||
const typename cache_accessor<T>::compute_mul_parity_result x_mul =
|
||||
cache_accessor<T>::compute_mul_parity(two_fl, cache, beta);
|
||||
if (!x_mul.parity && !x_mul.is_integer) {
|
||||
goto small_divisor_case_label;
|
||||
}
|
||||
}
|
||||
if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint)))
|
||||
goto small_divisor_case_label;
|
||||
}
|
||||
ret_value.exponent = minus_k + float_info<T>::kappa + 1;
|
||||
|
||||
@@ -1404,7 +1389,7 @@ small_divisor_case_label:
|
||||
// or equivalently, when y is an integer.
|
||||
if (y_mul.parity != approx_y_parity)
|
||||
--ret_value.significand;
|
||||
else if (y_mul.is_integer && ret_value.significand % 2 != 0)
|
||||
else if (y_mul.is_integer & (ret_value.significand % 2 != 0))
|
||||
--ret_value.significand;
|
||||
return ret_value;
|
||||
}
|
||||
@@ -1488,17 +1473,13 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace detail {
|
||||
#ifdef _WIN32
|
||||
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
|
||||
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
|
||||
void*, const void*, dword, dword*, void*);
|
||||
} // namespace detail
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
FMT_FUNC void print(std::FILE* f, string_view text) {
|
||||
#ifdef _WIN32
|
||||
FMT_FUNC bool write_console(std::FILE* f, string_view text) {
|
||||
auto fd = _fileno(f);
|
||||
if (_isatty(fd)) {
|
||||
detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
|
||||
@@ -1506,11 +1487,20 @@ FMT_FUNC void print(std::FILE* f, string_view text) {
|
||||
if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
|
||||
u16.c_str(), static_cast<uint32_t>(u16.size()),
|
||||
&written, nullptr)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Fallback to fwrite on failure. It can happen if the output has been
|
||||
// redirected to NUL.
|
||||
}
|
||||
// We return false if the file descriptor was not TTY, or it was but
|
||||
// SetConsoleW failed which can happen if the output has been redirected to
|
||||
// NUL. In both cases when we return false, we should attempt to do regular
|
||||
// write via fwrite or std::ostream::write.
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void print(std::FILE* f, string_view text) {
|
||||
#ifdef _WIN32
|
||||
if (write_console(f, text)) return;
|
||||
#endif
|
||||
detail::fwrite_fully(text.data(), 1, text.size(), f);
|
||||
}
|
||||
|
||||
@@ -249,6 +249,18 @@ FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) {
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename CharT, CharT... C> struct string_literal {
|
||||
static constexpr CharT value[sizeof...(C)] = {C...};
|
||||
constexpr operator basic_string_view<CharT>() const {
|
||||
return {value, sizeof...(C)};
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_CPLUSPLUS < 201703L
|
||||
template <typename CharT, CharT... C>
|
||||
constexpr CharT string_literal<CharT, C...>::value[sizeof...(C)];
|
||||
#endif
|
||||
|
||||
template <typename Streambuf> class formatbuf : public Streambuf {
|
||||
private:
|
||||
using char_type = typename Streambuf::char_type;
|
||||
@@ -287,7 +299,8 @@ FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To {
|
||||
if (is_constant_evaluated()) return std::bit_cast<To>(from);
|
||||
#endif
|
||||
auto to = To();
|
||||
std::memcpy(&to, &from, sizeof(to));
|
||||
// The cast suppresses a bogus -Wclass-memaccess on GCC.
|
||||
std::memcpy(static_cast<void*>(&to), &from, sizeof(to));
|
||||
return to;
|
||||
}
|
||||
|
||||
@@ -366,10 +379,12 @@ class uint128_fallback {
|
||||
}
|
||||
FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback {
|
||||
if (shift == 64) return {0, hi_};
|
||||
if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64);
|
||||
return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)};
|
||||
}
|
||||
FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback {
|
||||
if (shift == 64) return {lo_, 0};
|
||||
if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64);
|
||||
return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)};
|
||||
}
|
||||
FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& {
|
||||
@@ -389,11 +404,11 @@ class uint128_fallback {
|
||||
hi_ += (lo_ < n ? 1 : 0);
|
||||
return *this;
|
||||
}
|
||||
#if FMT_HAS_BUILTIN(__builtin_addcll)
|
||||
#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__)
|
||||
unsigned long long carry;
|
||||
lo_ = __builtin_addcll(lo_, n, 0, &carry);
|
||||
hi_ += carry;
|
||||
#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64)
|
||||
#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__)
|
||||
unsigned long long result;
|
||||
auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result);
|
||||
lo_ = result;
|
||||
@@ -592,19 +607,23 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e)
|
||||
constexpr const int shiftc[] = {0, 18, 12, 6, 0};
|
||||
constexpr const int shifte[] = {0, 6, 4, 2, 0};
|
||||
|
||||
int len = code_point_length(s);
|
||||
const char* next = s + len;
|
||||
int len = code_point_length_impl(*s);
|
||||
// Compute the pointer to the next character early so that the next
|
||||
// iteration can start working on the next character. Neither Clang
|
||||
// nor GCC figure out this reordering on their own.
|
||||
const char* next = s + len + !len;
|
||||
|
||||
using uchar = unsigned char;
|
||||
|
||||
// Assume a four-byte character and load four bytes. Unused bits are
|
||||
// shifted out.
|
||||
*c = uint32_t(s[0] & masks[len]) << 18;
|
||||
*c |= uint32_t(s[1] & 0x3f) << 12;
|
||||
*c |= uint32_t(s[2] & 0x3f) << 6;
|
||||
*c |= uint32_t(s[3] & 0x3f) << 0;
|
||||
*c = uint32_t(uchar(s[0]) & masks[len]) << 18;
|
||||
*c |= uint32_t(uchar(s[1]) & 0x3f) << 12;
|
||||
*c |= uint32_t(uchar(s[2]) & 0x3f) << 6;
|
||||
*c |= uint32_t(uchar(s[3]) & 0x3f) << 0;
|
||||
*c >>= shiftc[len];
|
||||
|
||||
// Accumulate the various error conditions.
|
||||
using uchar = unsigned char;
|
||||
*e = (*c < mins[len]) << 6; // non-canonical encoding
|
||||
*e |= ((*c >> 11) == 0x1b) << 7; // surrogate half?
|
||||
*e |= (*c > 0x10FFFF) << 8; // out of range?
|
||||
@@ -628,8 +647,8 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) {
|
||||
auto error = 0;
|
||||
auto end = utf8_decode(buf_ptr, &cp, &error);
|
||||
bool result = f(error ? invalid_code_point : cp,
|
||||
string_view(ptr, to_unsigned(end - buf_ptr)));
|
||||
return result ? end : nullptr;
|
||||
string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr)));
|
||||
return result ? (error ? buf_ptr + 1 : end) : nullptr;
|
||||
};
|
||||
auto p = s.data();
|
||||
const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars.
|
||||
@@ -919,8 +938,11 @@ struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type {
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
#ifdef _WIN32
|
||||
FMT_API bool write_console(std::FILE* f, string_view text);
|
||||
#endif
|
||||
FMT_API void print(std::FILE*, string_view);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/** A formatting error such as invalid format string. */
|
||||
FMT_CLASS_API
|
||||
@@ -1213,7 +1235,7 @@ FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size)
|
||||
|
||||
template <typename Char, typename UInt, typename Iterator,
|
||||
FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<Iterator>>::value)>
|
||||
inline auto format_decimal(Iterator out, UInt value, int size)
|
||||
FMT_CONSTEXPR inline auto format_decimal(Iterator out, UInt value, int size)
|
||||
-> format_decimal_result<Iterator> {
|
||||
// Buffer is large enough to hold all digits (digits10 + 1).
|
||||
Char buffer[digits10<UInt>() + 1];
|
||||
@@ -1274,8 +1296,6 @@ template <> struct float_info<float> {
|
||||
static const int small_divisor = 10;
|
||||
static const int min_k = -31;
|
||||
static const int max_k = 46;
|
||||
static const int divisibility_check_by_5_threshold = 39;
|
||||
static const int case_fc_pm_half_lower_threshold = -1;
|
||||
static const int shorter_interval_tie_lower_threshold = -35;
|
||||
static const int shorter_interval_tie_upper_threshold = -35;
|
||||
};
|
||||
@@ -1288,8 +1308,6 @@ template <> struct float_info<double> {
|
||||
static const int small_divisor = 100;
|
||||
static const int min_k = -292;
|
||||
static const int max_k = 326;
|
||||
static const int divisibility_check_by_5_threshold = 86;
|
||||
static const int case_fc_pm_half_lower_threshold = -2;
|
||||
static const int shorter_interval_tie_lower_threshold = -77;
|
||||
static const int shorter_interval_tie_upper_threshold = -77;
|
||||
};
|
||||
@@ -1543,7 +1561,10 @@ FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
|
||||
const int dec_exp_step = 8;
|
||||
index = (index - first_dec_exp - 1) / dec_exp_step + 1;
|
||||
pow10_exponent = first_dec_exp + index * dec_exp_step;
|
||||
return {data::pow10_significands[index], data::pow10_exponents[index]};
|
||||
// Using *(x + index) instead of x[index] avoids an issue with some compilers
|
||||
// using the EDG frontend (e.g. nvhpc/22.3 in C++17 mode).
|
||||
return {*(data::pow10_significands + index),
|
||||
*(data::pow10_exponents + index)};
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
@@ -1724,18 +1745,18 @@ inline auto find_escape(const char* begin, const char* end)
|
||||
return result;
|
||||
}
|
||||
|
||||
#define FMT_STRING_IMPL(s, base, explicit) \
|
||||
[] { \
|
||||
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
|
||||
/* Use a macro-like name to avoid shadowing warnings. */ \
|
||||
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
|
||||
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
|
||||
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
|
||||
operator fmt::basic_string_view<char_type>() const { \
|
||||
return fmt::detail_exported::compile_string_to_view<char_type>(s); \
|
||||
} \
|
||||
}; \
|
||||
return FMT_COMPILE_STRING(); \
|
||||
#define FMT_STRING_IMPL(s, base, explicit) \
|
||||
[] { \
|
||||
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
|
||||
/* Use a macro-like name to avoid shadowing warnings. */ \
|
||||
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
|
||||
using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t<decltype(s[0])>; \
|
||||
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
|
||||
operator fmt::basic_string_view<char_type>() const { \
|
||||
return fmt::detail_exported::compile_string_to_view<char_type>(s); \
|
||||
} \
|
||||
}; \
|
||||
return FMT_COMPILE_STRING(); \
|
||||
}()
|
||||
|
||||
/**
|
||||
@@ -1981,7 +2002,10 @@ auto write_int_localized(OutputIt out, UInt value, unsigned prefix,
|
||||
grouping.count_separators(num_digits));
|
||||
return write_padded<align::right>(
|
||||
out, specs, size, size, [&](reserve_iterator<OutputIt> it) {
|
||||
if (prefix != 0) *it++ = static_cast<Char>(prefix);
|
||||
if (prefix != 0) {
|
||||
char sign = static_cast<char>(prefix);
|
||||
*it++ = static_cast<Char>(sign);
|
||||
}
|
||||
return grouping.apply(it, string_view(digits, to_unsigned(num_digits)));
|
||||
});
|
||||
}
|
||||
@@ -2123,29 +2147,30 @@ class counting_iterator {
|
||||
FMT_UNCHECKED_ITERATOR(counting_iterator);
|
||||
|
||||
struct value_type {
|
||||
template <typename T> void operator=(const T&) {}
|
||||
template <typename T> FMT_CONSTEXPR void operator=(const T&) {}
|
||||
};
|
||||
|
||||
counting_iterator() : count_(0) {}
|
||||
FMT_CONSTEXPR counting_iterator() : count_(0) {}
|
||||
|
||||
size_t count() const { return count_; }
|
||||
FMT_CONSTEXPR size_t count() const { return count_; }
|
||||
|
||||
counting_iterator& operator++() {
|
||||
FMT_CONSTEXPR counting_iterator& operator++() {
|
||||
++count_;
|
||||
return *this;
|
||||
}
|
||||
counting_iterator operator++(int) {
|
||||
FMT_CONSTEXPR counting_iterator operator++(int) {
|
||||
auto it = *this;
|
||||
++*this;
|
||||
return it;
|
||||
}
|
||||
|
||||
friend counting_iterator operator+(counting_iterator it, difference_type n) {
|
||||
FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it,
|
||||
difference_type n) {
|
||||
it.count_ += static_cast<size_t>(n);
|
||||
return it;
|
||||
}
|
||||
|
||||
value_type operator*() const { return {}; }
|
||||
FMT_CONSTEXPR value_type operator*() const { return {}; }
|
||||
};
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
@@ -2991,7 +3016,7 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp<uint128_t> value,
|
||||
upper = &upper_store;
|
||||
}
|
||||
}
|
||||
bool even = (value.f & 1) == 0;
|
||||
int even = static_cast<int>((value.f & 1) == 0);
|
||||
if (!upper) upper = &lower;
|
||||
if ((flags & dragon::fixup) != 0) {
|
||||
if (add_compare(numerator, *upper, denominator) + even <= 0) {
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <ostream>
|
||||
#if defined(_WIN32) && defined(__GLIBCXX__)
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
# include <__std_stream>
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
@@ -51,43 +57,50 @@ struct is_streamable<
|
||||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
|
||||
: std::false_type {};
|
||||
|
||||
template <typename Char> FILE* get_file(std::basic_filebuf<Char>&) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct dummy_filebuf {
|
||||
FILE* _Myfile;
|
||||
};
|
||||
template <typename T, typename U = int> struct ms_filebuf {
|
||||
using type = dummy_filebuf;
|
||||
};
|
||||
template <typename T> struct ms_filebuf<T, decltype(T::_Myfile, 0)> {
|
||||
using type = T;
|
||||
};
|
||||
using filebuf_type = ms_filebuf<std::filebuf>::type;
|
||||
|
||||
FILE* get_file(filebuf_type& buf);
|
||||
|
||||
// Generate a unique explicit instantion in every translation unit using a tag
|
||||
// type in an anonymous namespace.
|
||||
namespace {
|
||||
struct filebuf_access_tag {};
|
||||
struct file_access_tag {};
|
||||
} // namespace
|
||||
template <typename Tag, typename FileMemberPtr, FileMemberPtr file>
|
||||
class filebuf_access {
|
||||
friend FILE* get_file(filebuf_type& buf) { return buf.*file; }
|
||||
template <class Tag, class BufType, FILE* BufType::*FileMemberPtr>
|
||||
class file_access {
|
||||
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
||||
};
|
||||
template class filebuf_access<filebuf_access_tag,
|
||||
decltype(&filebuf_type::_Myfile),
|
||||
&filebuf_type::_Myfile>;
|
||||
|
||||
inline bool write(std::filebuf& buf, fmt::string_view data) {
|
||||
FILE* f = get_file(buf);
|
||||
if (!f) return false;
|
||||
print(f, data);
|
||||
return true;
|
||||
#if FMT_MSC_VERSION
|
||||
template class file_access<file_access_tag, std::filebuf,
|
||||
&std::filebuf::_Myfile>;
|
||||
auto get_file(std::filebuf&) -> FILE*;
|
||||
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
template class file_access<file_access_tag, std::__stdoutbuf<char>,
|
||||
&std::__stdoutbuf<char>::__file_>;
|
||||
auto get_file(std::__stdoutbuf<char>&) -> FILE*;
|
||||
#endif
|
||||
|
||||
inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
|
||||
#if FMT_MSC_VERSION
|
||||
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
|
||||
if (FILE* f = get_file(*buf)) return write_console(f, data);
|
||||
#elif defined(_WIN32) && defined(__GLIBCXX__)
|
||||
auto* rdbuf = os.rdbuf();
|
||||
FILE* c_file;
|
||||
if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||
c_file = fbuf->file();
|
||||
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
||||
c_file = fbuf->file();
|
||||
else
|
||||
return false;
|
||||
if (c_file) return write_console(c_file, data);
|
||||
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf()))
|
||||
if (FILE* f = get_file(*buf)) return write_console(f, data);
|
||||
#else
|
||||
ignore_unused(os, data);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) {
|
||||
inline bool write_ostream_unicode(std::wostream&,
|
||||
fmt::basic_string_view<wchar_t>) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,10 +108,6 @@ inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) {
|
||||
// It is a separate function rather than a part of vprint to simplify testing.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
if (const_check(FMT_MSC_VERSION)) {
|
||||
auto filebuf = dynamic_cast<std::basic_filebuf<Char>*>(os.rdbuf());
|
||||
if (filebuf && write(*filebuf, {buf.data(), buf.size()})) return;
|
||||
}
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
@@ -130,6 +139,8 @@ template <typename T> struct streamed_view { const T& value; };
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename Char>
|
||||
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
||||
void set_debug_format() = delete;
|
||||
|
||||
template <typename T, typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
|
||||
-> OutputIt {
|
||||
@@ -142,12 +153,13 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
||||
|
||||
using ostream_formatter = basic_ostream_formatter<char>;
|
||||
|
||||
template <typename T>
|
||||
struct formatter<detail::streamed_view<T>> : ostream_formatter {
|
||||
template <typename T, typename Char>
|
||||
struct formatter<detail::streamed_view<T>, Char>
|
||||
: basic_ostream_formatter<Char> {
|
||||
template <typename OutputIt>
|
||||
auto format(detail::streamed_view<T> view,
|
||||
basic_format_context<OutputIt, char>& ctx) const -> OutputIt {
|
||||
return ostream_formatter::format(view.value, ctx);
|
||||
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
|
||||
return basic_ostream_formatter<Char>::format(view.value, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,6 +187,13 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
using basic_ostream_formatter<Char>::format;
|
||||
};
|
||||
|
||||
inline void vprint_directly(std::ostream& os, string_view format_str,
|
||||
format_args args) {
|
||||
auto buffer = memory_buffer();
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT template <typename Char>
|
||||
@@ -183,6 +202,7 @@ void vprint(std::basic_ostream<Char>& os,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
@@ -197,7 +217,11 @@ void vprint(std::basic_ostream<Char>& os,
|
||||
*/
|
||||
FMT_MODULE_EXPORT template <typename... T>
|
||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
vprint(os, fmt, fmt::make_format_args(args...));
|
||||
const auto& vargs = fmt::make_format_args(args...);
|
||||
if (detail::is_utf8())
|
||||
vprint(os, fmt, vargs);
|
||||
else
|
||||
detail::vprint_directly(os, fmt, vargs);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
|
||||
@@ -270,8 +270,8 @@ template <typename Range>
|
||||
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
|
||||
|
||||
template <typename Range>
|
||||
using uncvref_first_type = remove_cvref_t<
|
||||
decltype(std::declval<range_reference_type<Range>>().first)>;
|
||||
using uncvref_first_type =
|
||||
remove_cvref_t<decltype(std::declval<range_reference_type<Range>>().first)>;
|
||||
|
||||
template <typename Range>
|
||||
using uncvref_second_type = remove_cvref_t<
|
||||
@@ -326,18 +326,37 @@ struct formatter<TupleT, Char,
|
||||
enable_if_t<fmt::is_tuple_like<TupleT>::value &&
|
||||
fmt::is_tuple_formattable<TupleT, Char>::value>> {
|
||||
private:
|
||||
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||
basic_string_view<Char> opening_bracket_ =
|
||||
detail::string_literal<Char, '('>{};
|
||||
basic_string_view<Char> closing_bracket_ =
|
||||
detail::string_literal<Char, ')'>{};
|
||||
|
||||
// C++11 generic lambda for format().
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
if (i > 0) out = detail::copy_str<Char>(separator, out);
|
||||
out = detail::write_range_entry<Char>(out, v);
|
||||
++i;
|
||||
}
|
||||
int i;
|
||||
typename FormatContext::iterator& out;
|
||||
basic_string_view<Char> separator;
|
||||
};
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR formatter() {}
|
||||
|
||||
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
|
||||
separator_ = sep;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
|
||||
basic_string_view<Char> close) {
|
||||
opening_bracket_ = open;
|
||||
closing_bracket_ = close;
|
||||
}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
@@ -347,9 +366,9 @@ struct formatter<TupleT, Char,
|
||||
auto format(const TupleT& values, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
*out++ = '(';
|
||||
detail::for_each(values, format_each<FormatContext>{0, out});
|
||||
*out++ = ')';
|
||||
out = detail::copy_str<Char>(opening_bracket_, out);
|
||||
detail::for_each(values, format_each<FormatContext>{0, out, separator_});
|
||||
out = detail::copy_str<Char>(closing_bracket_, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
@@ -357,9 +376,8 @@ struct formatter<TupleT, Char,
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static constexpr 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;
|
||||
!std::is_convertible<T, detail::std_string_view<Char>>::value;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
@@ -390,40 +408,88 @@ using range_formatter_type = conditional_t<
|
||||
template <typename R>
|
||||
using maybe_const_range =
|
||||
conditional_t<has_const_begin_end<R>::value, const R, R>;
|
||||
|
||||
// Workaround a bug in MSVC 2015 and earlier.
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
||||
template <typename R, typename Char>
|
||||
struct is_formattable_delayed
|
||||
: disjunction<
|
||||
is_formattable<uncvref_type<maybe_const_range<R>>, Char>,
|
||||
has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> {};
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename R, typename Char>
|
||||
struct formatter<
|
||||
R, Char,
|
||||
enable_if_t<
|
||||
conjunction<fmt::is_range<R, Char>
|
||||
// Workaround a bug in MSVC 2017 and earlier.
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
|
||||
,
|
||||
disjunction<
|
||||
is_formattable<detail::uncvref_type<detail::maybe_const_range<R>>,
|
||||
Char>,
|
||||
detail::has_fallback_formatter<
|
||||
detail::uncvref_type<detail::maybe_const_range<R>>, Char>
|
||||
>
|
||||
#endif
|
||||
>::value
|
||||
>> {
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
struct range_formatter;
|
||||
|
||||
using range_type = detail::maybe_const_range<R>;
|
||||
using formatter_type =
|
||||
detail::range_formatter_type<Char, detail::uncvref_type<range_type>>;
|
||||
formatter_type underlying_;
|
||||
template <typename T, typename Char>
|
||||
struct range_formatter<
|
||||
T, Char,
|
||||
enable_if_t<conjunction<
|
||||
std::is_same<T, remove_cvref_t<T>>,
|
||||
disjunction<is_formattable<T, Char>,
|
||||
detail::has_fallback_formatter<T, Char>>>::value>> {
|
||||
private:
|
||||
detail::range_formatter_type<Char, T> underlying_;
|
||||
bool custom_specs_ = false;
|
||||
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
|
||||
basic_string_view<Char> opening_bracket_ =
|
||||
detail::string_literal<Char, '['>{};
|
||||
basic_string_view<Char> closing_bracket_ =
|
||||
detail::string_literal<Char, ']'>{};
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, int)
|
||||
-> decltype(u.set_debug_format()) {
|
||||
u.set_debug_format();
|
||||
}
|
||||
|
||||
template <class U>
|
||||
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
|
||||
|
||||
FMT_CONSTEXPR void maybe_set_debug_format() {
|
||||
maybe_set_debug_format(underlying_, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR range_formatter() {}
|
||||
|
||||
FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
|
||||
return underlying_;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
|
||||
separator_ = sep;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
|
||||
basic_string_view<Char> close) {
|
||||
opening_bracket_ = open;
|
||||
closing_bracket_ = close;
|
||||
}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
if (it == end || *it == '}') {
|
||||
maybe_set_debug_format();
|
||||
return it;
|
||||
}
|
||||
|
||||
if (*it == 'n') {
|
||||
set_brackets({}, {});
|
||||
++it;
|
||||
}
|
||||
|
||||
if (*it == '}') {
|
||||
maybe_set_debug_format();
|
||||
return it;
|
||||
}
|
||||
|
||||
if (*it != ':')
|
||||
FMT_THROW(format_error("no top-level range formatters supported"));
|
||||
FMT_THROW(format_error("no other top-level range formatters supported"));
|
||||
|
||||
custom_specs_ = true;
|
||||
++it;
|
||||
@@ -431,75 +497,100 @@ struct formatter<
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(range_type& range, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
Char prefix = detail::is_set<R>::value ? '{' : '[';
|
||||
Char postfix = detail::is_set<R>::value ? '}' : ']';
|
||||
template <typename R, class FormatContext>
|
||||
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
detail::range_mapper<buffer_context<Char>> mapper;
|
||||
auto out = ctx.out();
|
||||
*out++ = prefix;
|
||||
out = detail::copy_str<Char>(opening_bracket_, out);
|
||||
int i = 0;
|
||||
auto it = detail::range_begin(range);
|
||||
auto end = detail::range_end(range);
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
if (custom_specs_) {
|
||||
ctx.advance_to(out);
|
||||
out = underlying_.format(mapper.map(*it), ctx);
|
||||
} else {
|
||||
out = detail::write_range_entry<Char>(out, *it);
|
||||
}
|
||||
if (i > 0) out = detail::copy_str<Char>(separator_, out);
|
||||
;
|
||||
ctx.advance_to(out);
|
||||
out = underlying_.format(mapper.map(*it), ctx);
|
||||
++i;
|
||||
}
|
||||
*out++ = postfix;
|
||||
out = detail::copy_str<Char>(closing_bracket_, out);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<conjunction<detail::is_map<T>
|
||||
// Workaround a bug in MSVC 2017 and earlier.
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
|
||||
,
|
||||
disjunction<
|
||||
is_formattable<detail::uncvref_first_type<T>, Char>,
|
||||
detail::has_fallback_formatter<detail::uncvref_first_type<T>, Char>
|
||||
>,
|
||||
disjunction<
|
||||
is_formattable<detail::uncvref_second_type<T>, Char>,
|
||||
detail::has_fallback_formatter<detail::uncvref_second_type<T>, Char>
|
||||
>
|
||||
#endif
|
||||
>::value
|
||||
>> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
enum class range_format { disabled, map, set, sequence, string, debug_string };
|
||||
|
||||
namespace detail {
|
||||
template <typename T> struct range_format_kind_ {
|
||||
static constexpr auto value = std::is_same<range_reference_type<T>, T>::value
|
||||
? range_format::disabled
|
||||
: is_map<T>::value ? range_format::map
|
||||
: is_set<T>::value ? range_format::set
|
||||
: range_format::sequence;
|
||||
};
|
||||
|
||||
template <range_format K, typename R, typename Char, typename Enable = void>
|
||||
struct range_default_formatter;
|
||||
|
||||
template <range_format K>
|
||||
using range_format_constant = std::integral_constant<range_format, K>;
|
||||
|
||||
template <range_format K, typename R, typename Char>
|
||||
struct range_default_formatter<
|
||||
K, R, Char,
|
||||
enable_if_t<(K == range_format::sequence || K == range_format::map ||
|
||||
K == range_format::set)>> {
|
||||
using range_type = detail::maybe_const_range<R>;
|
||||
range_formatter<detail::uncvref_type<range_type>, Char> underlying_;
|
||||
|
||||
FMT_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); }
|
||||
|
||||
FMT_CONSTEXPR void init(range_format_constant<range_format::set>) {
|
||||
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
|
||||
detail::string_literal<Char, '}'>{});
|
||||
}
|
||||
|
||||
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) const -> 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;
|
||||
FMT_CONSTEXPR void init(range_format_constant<range_format::map>) {
|
||||
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
|
||||
detail::string_literal<Char, '}'>{});
|
||||
underlying_.underlying().set_brackets({}, {});
|
||||
underlying_.underlying().set_separator(
|
||||
detail::string_literal<Char, ':', ' '>{});
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) {}
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(range_type& range, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return underlying_.format(range, ctx);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
struct range_format_kind
|
||||
: conditional_t<
|
||||
is_range<T, Char>::value, detail::range_format_kind_<T>,
|
||||
std::integral_constant<range_format, range_format::disabled>> {};
|
||||
|
||||
template <typename R, typename Char>
|
||||
struct formatter<
|
||||
R, Char,
|
||||
enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value !=
|
||||
range_format::disabled>
|
||||
// Workaround a bug in MSVC 2015 and earlier.
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
|
||||
,
|
||||
detail::is_formattable_delayed<R, Char>
|
||||
#endif
|
||||
>::value>>
|
||||
: detail::range_default_formatter<range_format_kind<R, Char>::value, R,
|
||||
Char> {
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||
|
||||
@@ -57,10 +57,6 @@ inline void write_escaped_path<std::filesystem::path::value_type>(
|
||||
|
||||
} // namespace detail
|
||||
|
||||
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
|
||||
// For MSVC 2017 and earlier using the partial specialization
|
||||
// would cause an ambiguity error, therefore we provide it only
|
||||
// conditionally.
|
||||
template <typename Char>
|
||||
struct formatter<std::filesystem::path, Char>
|
||||
: formatter<basic_string_view<Char>> {
|
||||
@@ -73,7 +69,6 @@ struct formatter<std::filesystem::path, Char>
|
||||
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#define FMT_XCHAR_H_
|
||||
|
||||
#include <cwchar>
|
||||
#include <tuple>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
@@ -30,9 +29,11 @@ using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround broken conversion on older gcc.
|
||||
template <typename... Args> using wformat_string = wstring_view;
|
||||
inline auto runtime(wstring_view s) -> wstring_view { return s; }
|
||||
#else
|
||||
template <typename... Args>
|
||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||
inline auto runtime(wstring_view s) -> basic_runtime<wchar_t> { return {{s}}; }
|
||||
#endif
|
||||
|
||||
template <> struct is_char<wchar_t> : std::true_type {};
|
||||
@@ -82,20 +83,16 @@ auto vformat(basic_string_view<Char> format_str,
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
|
||||
template <typename... Args>
|
||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||
#endif
|
||||
|
||||
template <typename... T>
|
||||
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
|
||||
return vformat(fmt, fmt::make_wformat_args(args...));
|
||||
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
// Pass char_t as a default template parameter instead of using
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
||||
!std::is_same<Char, wchar_t>::value)>
|
||||
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
||||
return vformat(detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Build the project on AppVeyor.
|
||||
|
||||
import os
|
||||
from subprocess import check_call
|
||||
|
||||
build = os.environ['BUILD']
|
||||
config = os.environ['CONFIGURATION']
|
||||
platform = os.environ['PLATFORM']
|
||||
path = os.environ['PATH']
|
||||
image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE']
|
||||
jobid = os.environ['APPVEYOR_JOB_ID']
|
||||
cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..']
|
||||
if build == 'mingw':
|
||||
cmake_command.append('-GMinGW Makefiles')
|
||||
build_command = ['mingw32-make', '-j4']
|
||||
test_command = ['mingw32-make', 'test']
|
||||
# Remove the path to Git bin directory from $PATH because it breaks
|
||||
# MinGW config.
|
||||
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
|
||||
os.environ['PATH'] = r'C:\MinGW\bin;' + path
|
||||
else:
|
||||
# Add MSBuild 14.0 to PATH as described in
|
||||
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
|
||||
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path
|
||||
if image == 'Visual Studio 2019':
|
||||
generator = 'Visual Studio 16 2019'
|
||||
if platform == 'x64':
|
||||
cmake_command.extend(['-A', 'x64'])
|
||||
else:
|
||||
if image == 'Visual Studio 2015':
|
||||
generator = 'Visual Studio 14 2015'
|
||||
elif image == 'Visual Studio 2017':
|
||||
generator = 'Visual Studio 15 2017'
|
||||
if platform == 'x64':
|
||||
generator += ' Win64'
|
||||
cmake_command.append('-G' + generator)
|
||||
build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
|
||||
test_command = ['ctest', '-C', config]
|
||||
|
||||
check_call(cmake_command)
|
||||
check_call(build_command)
|
||||
check_call(test_command)
|
||||
@@ -1,31 +0,0 @@
|
||||
configuration:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
image:
|
||||
- Visual Studio 2015
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
environment:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
MSVC_DEFAULT_OPTIONS: ON
|
||||
BUILD: msvc
|
||||
|
||||
before_build:
|
||||
- mkdir build
|
||||
- cd build
|
||||
|
||||
build_script:
|
||||
- python ../support/appveyor-build.py
|
||||
|
||||
on_failure:
|
||||
- appveyor PushArtifact Testing/Temporary/LastTest.log
|
||||
- appveyor AddTest test
|
||||
|
||||
# Uncomment this to debug AppVeyor failures.
|
||||
#on_finish:
|
||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
@@ -183,6 +183,12 @@ def update_site(env):
|
||||
with rewrite(index) as b:
|
||||
b.data = b.data.replace(
|
||||
'doc/latest/index.html#format-string-syntax', 'syntax.html')
|
||||
# Fix issues in syntax.rst.
|
||||
index = os.path.join(target_doc_dir, 'syntax.rst')
|
||||
with rewrite(index) as b:
|
||||
b.data = b.data.replace(
|
||||
'..productionlist:: sf\n', '.. productionlist:: sf\n ')
|
||||
b.data = b.data.replace('Examples:\n', 'Examples::\n')
|
||||
# Build the docs.
|
||||
html_dir = os.path.join(env.build_dir, 'html')
|
||||
if os.path.exists(html_dir):
|
||||
|
||||
@@ -8,9 +8,6 @@ target_link_libraries(test-main gtest fmt)
|
||||
|
||||
function(add_fmt_executable name)
|
||||
add_executable(${name} ${ARGN})
|
||||
if (MINGW)
|
||||
target_link_libraries(${name} -static-libgcc -static-libstdc++)
|
||||
endif ()
|
||||
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
|
||||
# Bogus -Wstringop-overflow warning
|
||||
@@ -19,6 +16,8 @@ function(add_fmt_executable name)
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
|
||||
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
|
||||
target_compile_options(${name} PRIVATE -Wno-stringop-overflow)
|
||||
# The linker flag is needed for LTO.
|
||||
target_link_libraries(${name} -Wno-stringop-overflow)
|
||||
endif ()
|
||||
endfunction()
|
||||
|
||||
@@ -121,6 +121,13 @@ TEST(chrono_test, format_tm) {
|
||||
make_tm(2000, 1, 2, 12, 14, 16), // W52
|
||||
make_tm(2000, 1, 3, 12, 14, 16) // W1
|
||||
};
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||
GTEST_SKIP() << "Skip the rest of this test because it relies on strftime() "
|
||||
"conforming to C99, but on this platform, MINGW + MSVCRT, "
|
||||
"the function conforms only to C89.";
|
||||
#endif
|
||||
|
||||
const std::string iso_week_spec = "%Y-%m-%d: %G %g %V";
|
||||
for (auto ctm : tm_list) {
|
||||
// Calculate tm_yday, tm_wday, etc.
|
||||
@@ -261,12 +268,16 @@ TEST(chrono_test, time_point) {
|
||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
||||
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
|
||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows because these formats are not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||
#elif defined(__MINGW32__) && !defined(_UCRT)
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
|
||||
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
|
||||
#endif
|
||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
|
||||
@@ -227,10 +227,14 @@ TEST(compile_test, format_to_n) {
|
||||
EXPECT_STREQ("2a", buffer);
|
||||
}
|
||||
|
||||
TEST(compile_test, formatted_size) {
|
||||
EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42));
|
||||
EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0));
|
||||
#ifdef __cpp_lib_bit_cast
|
||||
TEST(compile_test, constexpr_formatted_size) {
|
||||
FMT_CONSTEXPR20 size_t s1 = fmt::formatted_size(FMT_COMPILE("{0}"), 42);
|
||||
EXPECT_EQ(2, s1);
|
||||
FMT_CONSTEXPR20 size_t s2 = fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0);
|
||||
EXPECT_EQ(5, s2);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(compile_test, text_and_arg) {
|
||||
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include "test-assert.h"
|
||||
// clang-format on
|
||||
|
||||
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
|
||||
#include "fmt/core.h"
|
||||
#undef I
|
||||
|
||||
#include <algorithm> // std::copy_n
|
||||
#include <climits> // INT_MAX
|
||||
@@ -584,6 +586,7 @@ struct test_parse_context {
|
||||
|
||||
constexpr int next_arg_id() { return 11; }
|
||||
template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {}
|
||||
FMT_CONSTEXPR void check_dynamic_spec(int) {}
|
||||
|
||||
constexpr const char* begin() { return nullptr; }
|
||||
constexpr const char* end() { return nullptr; }
|
||||
|
||||
@@ -376,6 +376,14 @@ bool operator>=(const double_double& lhs, const double_double& rhs) {
|
||||
return lhs.a + lhs.b >= rhs.a + rhs.b;
|
||||
}
|
||||
|
||||
struct slow_float {
|
||||
float value;
|
||||
|
||||
explicit constexpr slow_float(float val = 0) : value(val) {}
|
||||
operator float() const { return value; }
|
||||
auto operator-() const -> slow_float { return slow_float(-value); }
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <> struct is_floating_point<double_double> : std::true_type {};
|
||||
template <> struct numeric_limits<double_double> {
|
||||
@@ -383,14 +391,35 @@ template <> struct numeric_limits<double_double> {
|
||||
static constexpr bool is_iec559 = true;
|
||||
static constexpr int digits = 106;
|
||||
};
|
||||
|
||||
template <> struct is_floating_point<slow_float> : std::true_type {};
|
||||
template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
|
||||
} // namespace std
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template <> struct is_fast_float<slow_float> : std::false_type {};
|
||||
namespace dragonbox {
|
||||
template <> struct float_info<slow_float> {
|
||||
using carrier_uint = uint32_t;
|
||||
static const int exponent_bits = 8;
|
||||
};
|
||||
} // namespace dragonbox
|
||||
} // namespace detail
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(format_impl_test, write_double_double) {
|
||||
auto s = std::string();
|
||||
fmt::detail::write<char>(std::back_inserter(s), double_double(42), {});
|
||||
#ifndef _MSC_VER // MSVC has an issue with specializing is_floating_point.
|
||||
EXPECT_EQ(s, "42");
|
||||
#endif
|
||||
// Specializing is_floating_point is broken in MSVC.
|
||||
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "42");
|
||||
}
|
||||
|
||||
TEST(format_impl_test, write_dragon_even) {
|
||||
auto s = std::string();
|
||||
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
|
||||
// Specializing is_floating_point is broken in MSVC.
|
||||
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -401,3 +430,118 @@ TEST(format_impl_test, write_console_signature) {
|
||||
(void)p;
|
||||
}
|
||||
#endif
|
||||
|
||||
// A public domain branchless UTF-8 decoder by Christopher Wellons:
|
||||
// https://github.com/skeeto/branchless-utf8
|
||||
constexpr bool unicode_is_surrogate(uint32_t c) {
|
||||
return c >= 0xD800U && c <= 0xDFFFU;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
|
||||
if (c >= (1UL << 16)) {
|
||||
s[0] = static_cast<char>(0xf0 | (c >> 18));
|
||||
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));
|
||||
s[2] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
|
||||
s[3] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
|
||||
return s + 4;
|
||||
} else if (c >= (1UL << 11)) {
|
||||
s[0] = static_cast<char>(0xe0 | (c >> 12));
|
||||
s[1] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
|
||||
s[2] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
|
||||
return s + 3;
|
||||
} else if (c >= (1UL << 7)) {
|
||||
s[0] = static_cast<char>(0xc0 | (c >> 6));
|
||||
s[1] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
|
||||
return s + 2;
|
||||
} else {
|
||||
s[0] = static_cast<char>(c);
|
||||
return s + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure it can decode every character
|
||||
TEST(format_impl_test, utf8_decode_decode_all) {
|
||||
for (uint32_t i = 0; i < 0x10ffff; i++) {
|
||||
if (!unicode_is_surrogate(i)) {
|
||||
int e;
|
||||
uint32_t c;
|
||||
char buf[8] = {0};
|
||||
char* end = utf8_encode(buf, i);
|
||||
const char* res = fmt::detail::utf8_decode(buf, &c, &e);
|
||||
EXPECT_EQ(end, res);
|
||||
EXPECT_EQ(c, i);
|
||||
EXPECT_EQ(e, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reject everything outside of U+0000..U+10FFFF
|
||||
TEST(format_impl_test, utf8_decode_out_of_range) {
|
||||
for (uint32_t i = 0x110000; i < 0x1fffff; i++) {
|
||||
int e;
|
||||
uint32_t c;
|
||||
char buf[8] = {0};
|
||||
utf8_encode(buf, i);
|
||||
const char* end = fmt::detail::utf8_decode(buf, &c, &e);
|
||||
EXPECT_NE(e, 0);
|
||||
EXPECT_EQ(end - buf, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// Does it reject all surrogate halves?
|
||||
TEST(format_impl_test, utf8_decode_surrogate_halves) {
|
||||
for (uint32_t i = 0xd800; i <= 0xdfff; i++) {
|
||||
int e;
|
||||
uint32_t c;
|
||||
char buf[8] = {0};
|
||||
utf8_encode(buf, i);
|
||||
fmt::detail::utf8_decode(buf, &c, &e);
|
||||
EXPECT_NE(e, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// How about non-canonical encodings?
|
||||
TEST(format_impl_test, utf8_decode_non_canonical_encodings) {
|
||||
int e;
|
||||
uint32_t c;
|
||||
const char* end;
|
||||
|
||||
char buf2[8] = {char(0xc0), char(0xA4)};
|
||||
end = fmt::detail::utf8_decode(buf2, &c, &e);
|
||||
EXPECT_NE(e, 0); // non-canonical len 2
|
||||
EXPECT_EQ(end, buf2 + 2); // non-canonical recover 2
|
||||
|
||||
char buf3[8] = {char(0xe0), char(0x80), char(0xA4)};
|
||||
end = fmt::detail::utf8_decode(buf3, &c, &e);
|
||||
EXPECT_NE(e, 0); // non-canonical len 3
|
||||
EXPECT_EQ(end, buf3 + 3); // non-canonical recover 3
|
||||
|
||||
char buf4[8] = {char(0xf0), char(0x80), char(0x80), char(0xA4)};
|
||||
end = fmt::detail::utf8_decode(buf4, &c, &e);
|
||||
EXPECT_NE(e, 0); // non-canonical encoding len 4
|
||||
EXPECT_EQ(end, buf4 + 4); // non-canonical recover 4
|
||||
}
|
||||
|
||||
// Let's try some bogus byte sequences
|
||||
TEST(format_impl_test, utf8_decode_bogus_byte_sequences) {
|
||||
int e;
|
||||
uint32_t c;
|
||||
|
||||
// Invalid first byte
|
||||
char buf0[4] = {char(0xff)};
|
||||
auto len = fmt::detail::utf8_decode(buf0, &c, &e) - buf0;
|
||||
EXPECT_NE(e, 0); // "bogus [ff] 0x%02x U+%04lx", e, (unsigned long)c);
|
||||
EXPECT_EQ(len, 1); // "bogus [ff] recovery %d", len);
|
||||
|
||||
// Invalid first byte
|
||||
char buf1[4] = {char(0x80)};
|
||||
len = fmt::detail::utf8_decode(buf1, &c, &e) - buf1;
|
||||
EXPECT_NE(e, 0); // "bogus [80] 0x%02x U+%04lx", e, (unsigned long)c);
|
||||
EXPECT_EQ(len, 1); // "bogus [80] recovery %d", len);
|
||||
|
||||
// Looks like a two-byte sequence but second byte is wrong
|
||||
char buf2[4] = {char(0xc0), char(0x0a)};
|
||||
len = fmt::detail::utf8_decode(buf2, &c, &e) - buf2;
|
||||
EXPECT_NE(e, 0); // "bogus [c0 0a] 0x%02x U+%04lx", e, (unsigned long)c
|
||||
EXPECT_EQ(len, 2); // "bogus [c0 0a] recovery %d", len);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ TEST(uint128_test, shift) {
|
||||
EXPECT_EQ(static_cast<uint64_t>(n), 0x8000000000000000);
|
||||
n = n >> 62;
|
||||
EXPECT_EQ(static_cast<uint64_t>(n), 42);
|
||||
EXPECT_EQ(uint128_fallback(1) << 112, uint128_fallback(0x1000000000000, 0));
|
||||
EXPECT_EQ(uint128_fallback(0x1000000000000, 0) >> 112, uint128_fallback(1));
|
||||
}
|
||||
|
||||
TEST(uint128_test, minus) {
|
||||
@@ -99,7 +101,7 @@ template <typename Float> void check_isfinite() {
|
||||
|
||||
TEST(float_test, isfinite) {
|
||||
check_isfinite<double>();
|
||||
#ifdef __SIZEOF_FLOAT128__
|
||||
#if FMT_USE_FLOAT128
|
||||
check_isfinite<fmt::detail::float128>();
|
||||
#endif
|
||||
}
|
||||
@@ -120,7 +122,7 @@ template <typename Float> void check_isnan() {
|
||||
|
||||
TEST(float_test, isnan) {
|
||||
check_isnan<double>();
|
||||
#ifdef __SIZEOF_FLOAT128__
|
||||
#if FMT_USE_FLOAT128
|
||||
check_isnan<fmt::detail::float128>();
|
||||
#endif
|
||||
}
|
||||
@@ -234,7 +236,7 @@ TEST(util_test, format_system_error) {
|
||||
throws_on_alloc = true;
|
||||
}
|
||||
if (!throws_on_alloc) {
|
||||
fmt::print("warning: std::allocator allocates {} chars", max_size);
|
||||
fmt::print("warning: std::allocator allocates {} chars\n", max_size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -893,6 +895,7 @@ TEST(format_test, runtime_width) {
|
||||
fmt::format("{0:{1}}", reinterpret_cast<void*>(0xcafe), 10));
|
||||
EXPECT_EQ("x ", fmt::format("{0:{1}}", 'x', 11));
|
||||
EXPECT_EQ("str ", fmt::format("{0:{1}}", "str", 12));
|
||||
EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), " 42");
|
||||
}
|
||||
|
||||
TEST(format_test, precision) {
|
||||
@@ -2175,6 +2178,7 @@ TEST(format_test, format_string_errors) {
|
||||
EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned);
|
||||
EXPECT_ERROR("{:{}}", "argument not found", int);
|
||||
EXPECT_ERROR("{:.{}}", "argument not found", double);
|
||||
EXPECT_ERROR("{:{}}", "width/precision is not integer", int, double);
|
||||
EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", int);
|
||||
EXPECT_ERROR("{:s}", "invalid type specifier", char);
|
||||
|
||||
@@ -2202,7 +2202,7 @@ bool UnitTestOptions::MatchesFilter(const std::string& name_str,
|
||||
|
||||
// Check if this pattern matches name_str.
|
||||
if (PatternMatchesString(name_str, pattern, pattern_end)) {
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Give up on this pattern. However, if we found a pattern separator (:),
|
||||
|
||||
@@ -40,6 +40,8 @@ TEST(ranges_test, format_2d_array) {
|
||||
TEST(ranges_test, format_array_of_literals) {
|
||||
const char* arr[] = {"1234", "abcd"};
|
||||
EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
|
||||
EXPECT_EQ(fmt::format("{:n}", arr), "\"1234\", \"abcd\"");
|
||||
EXPECT_EQ(fmt::format("{:n:}", arr), "1234, abcd");
|
||||
}
|
||||
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
|
||||
|
||||
@@ -47,17 +49,20 @@ TEST(ranges_test, format_vector) {
|
||||
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
|
||||
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
|
||||
EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
|
||||
EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
|
||||
}
|
||||
|
||||
TEST(ranges_test, format_vector2) {
|
||||
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
|
||||
EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
|
||||
EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]");
|
||||
EXPECT_EQ(fmt::format("{:n:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
|
||||
}
|
||||
|
||||
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("{:n}", m), "\"one\": 1, \"two\": 2");
|
||||
}
|
||||
|
||||
TEST(ranges_test, format_set) {
|
||||
@@ -375,8 +380,15 @@ TEST(ranges_test, escape_string) {
|
||||
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
|
||||
// Unassigned Unicode code points.
|
||||
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
|
||||
// Broken utf-8.
|
||||
EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}),
|
||||
"[\"\\xf4\\x8f\\xbf\\xc0\"]");
|
||||
EXPECT_EQ(fmt::format("{}", vec{"\xf0\x28"}), "[\"\\xf0(\"]");
|
||||
EXPECT_EQ(fmt::format("{}", vec{"\xe1\x28"}), "[\"\\xe1(\"]");
|
||||
EXPECT_EQ(fmt::format("{}", vec{std::string("\xf0\x28\0\0anything", 12)}),
|
||||
"[\"\\xf0(\\x00\\x00anything\"]");
|
||||
|
||||
// Correct utf-8.
|
||||
EXPECT_EQ(fmt::format("{}", vec{"понедельник"}), "[\"понедельник\"]");
|
||||
}
|
||||
}
|
||||
@@ -406,3 +418,7 @@ TEST(ranges_test, range_of_range_of_mixed_const) {
|
||||
fmt_ref_view<decltype(v)> r{&v};
|
||||
EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]");
|
||||
}
|
||||
|
||||
TEST(ranges_test, vector_char) {
|
||||
EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']");
|
||||
}
|
||||
|
||||
@@ -14,10 +14,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(std_test, path) {
|
||||
// Test ambiguity problem described in #2954. We need to exclude compilers
|
||||
// where the ambiguity problem cannot be solved for now.
|
||||
#if defined(__cpp_lib_filesystem) && \
|
||||
(!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920)
|
||||
#ifdef __cpp_lib_filesystem
|
||||
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" ");
|
||||
EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")),
|
||||
"\"foo\\\"bar.txt\"");
|
||||
@@ -37,10 +34,8 @@ TEST(std_test, path) {
|
||||
}
|
||||
|
||||
TEST(ranges_std_test, format_vector_path) {
|
||||
// Test ambiguity problem described in #2954. We need to exclude compilers
|
||||
// where the ambiguity problem cannot be solved for now.
|
||||
#if defined(__cpp_lib_filesystem) && \
|
||||
(!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920)
|
||||
// Test ambiguity problem described in #2954.
|
||||
#ifdef __cpp_lib_filesystem
|
||||
auto p = std::filesystem::path("foo/bar.txt");
|
||||
auto c = std::vector<std::string>{"abc", "def"};
|
||||
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
|
||||
|
||||
@@ -18,12 +18,12 @@ using testing::Contains;
|
||||
TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); }
|
||||
|
||||
TEST(unicode_test, legacy_locale) {
|
||||
auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251");
|
||||
auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");
|
||||
if (loc == std::locale::classic()) return;
|
||||
|
||||
auto s = std::string();
|
||||
try {
|
||||
s = fmt::format(loc, "День недели: {:L}", fmt::weekday(1));
|
||||
s = fmt::format(loc, "Дзень тыдня: {:L}", fmt::weekday(1));
|
||||
} catch (const fmt::format_error& e) {
|
||||
// Formatting can fail due to an unsupported encoding.
|
||||
fmt::print("Format error: {}\n", e.what());
|
||||
@@ -38,11 +38,11 @@ TEST(unicode_test, legacy_locale) {
|
||||
os << std::put_time(&tm, "%a");
|
||||
auto wd = os.str();
|
||||
if (wd == "??") {
|
||||
EXPECT_EQ(s, "День недели: ??");
|
||||
EXPECT_EQ(s, "Дзень тыдня: ??");
|
||||
fmt::print("std::locale gives ?? as a weekday.\n");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
EXPECT_THAT((std::vector<std::string>{"День недели: пн", "День недели: Пн"}),
|
||||
EXPECT_THAT((std::vector<std::string>{"Дзень тыдня: пн", "Дзень тыдня: Пан"}),
|
||||
Contains(s));
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ TEST(xchar_test, format) {
|
||||
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
|
||||
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
|
||||
EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
|
||||
EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error);
|
||||
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
|
||||
EXPECT_EQ(L"true", fmt::format(L"{}", true));
|
||||
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
|
||||
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
|
||||
@@ -98,8 +98,9 @@ TEST(xchar_test, is_formattable) {
|
||||
}
|
||||
|
||||
TEST(xchar_test, compile_time_string) {
|
||||
EXPECT_EQ(fmt::format(fmt::wformat_string<int>(L"{}"), 42), L"42");
|
||||
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
|
||||
EXPECT_EQ(fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42), L"42");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -229,11 +230,24 @@ TEST(xchar_test, enum) {
|
||||
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
|
||||
}
|
||||
|
||||
struct streamable_and_unformattable {};
|
||||
|
||||
auto operator<<(std::wostream& os, streamable_and_unformattable)
|
||||
-> std::wostream& {
|
||||
return os << L"foo";
|
||||
}
|
||||
|
||||
TEST(xchar_test, streamed) {
|
||||
EXPECT_FALSE(fmt::is_formattable<streamable_and_unformattable>());
|
||||
EXPECT_EQ(fmt::format(L"{}", fmt::streamed(streamable_and_unformattable())),
|
||||
L"foo");
|
||||
}
|
||||
|
||||
TEST(xchar_test, sign_not_truncated) {
|
||||
wchar_t format_str[] = {
|
||||
L'{', L':',
|
||||
'+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
|
||||
EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error);
|
||||
EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error);
|
||||
}
|
||||
|
||||
TEST(xchar_test, chrono) {
|
||||
@@ -269,7 +283,7 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(chrono_test, time_point) {
|
||||
TEST(chrono_test_wchar, time_point) {
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
|
||||
std::vector<std::wstring> spec_list = {
|
||||
@@ -279,12 +293,17 @@ TEST(chrono_test, time_point) {
|
||||
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
|
||||
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
|
||||
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"};
|
||||
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows, because these formats is not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
|
||||
#elif defined(__MINGW32__) && !defined(_UCRT)
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
|
||||
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
|
||||
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p", L"%Z"};
|
||||
#endif
|
||||
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
@@ -293,8 +312,8 @@ TEST(chrono_test, time_point) {
|
||||
auto sys_output = system_wcsftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt_spec, t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt_spec, tm));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,4 +514,8 @@ TEST(locale_test, chrono_weekday) {
|
||||
std::locale::global(loc_old);
|
||||
}
|
||||
|
||||
TEST(locale_test, sign) {
|
||||
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
Reference in New Issue
Block a user