mirror of
https://github.com/fmtlib/fmt.git
synced 2025-06-25 09:21:41 +02:00
Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
353bd895a2 | |||
953cffa701 | |||
571c02d475 | |||
f4345467fc | |||
1ef8348070 | |||
a5dccffa56 | |||
4a149f513f | |||
067bc479b4 | |||
730fd4d9a7 | |||
5860688d7e | |||
46be88bc1e | |||
cc88914904 | |||
fc0c76a075 | |||
6332a38529 | |||
02de29e003 | |||
6d51c78c1e | |||
0f4e9d0bde | |||
d9d50495ac | |||
befbc5fdb8 | |||
8aa1d6a9fb | |||
6d79757a38 | |||
1ff0b7f5e1 | |||
ea985e84f8 | |||
f7033da09e | |||
b723c021df | |||
3ba3c390fb | |||
ab161a71c6 | |||
b5266fd3b9 | |||
9b0ebd4435 | |||
7af94e5597 | |||
2924fcf8f6 | |||
102752ad45 | |||
a6cd72c9eb | |||
07885271a1 | |||
4999416e5c | |||
55a8f6a4be | |||
eb9a95d426 | |||
d5c33e4f45 | |||
a2225f2887 | |||
b43b2f9537 | |||
1312b4a162 | |||
4404dc05dd | |||
7bb6fcb325 | |||
59259a5fde | |||
542ea7c40b | |||
40626af88b | |||
7fdd6846ba | |||
6caff7ed9c | |||
71a5483875 | |||
448929d491 | |||
26d87edab1 | |||
505ee058f7 | |||
ccab417195 | |||
ec1349d348 | |||
0ed2a65a8a | |||
e22c943070 | |||
b252bad3c6 | |||
2680831231 | |||
8978ab09b2 | |||
c936e2e44e | |||
a7d7b894cd | |||
e98155a6fb | |||
41b3bed4d2 | |||
67d9e49322 | |||
9db5e4df22 | |||
906eaf2ddb | |||
9f6c12c3dc | |||
2d0518b5f7 | |||
c81cbed2b7 | |||
c7925241c7 | |||
c709138359 | |||
db405954cd | |||
0a917ee2f5 | |||
969d4aef60 | |||
8061c7c8c4 | |||
7b59df4119 | |||
b8192d233a | |||
e814b5fabf | |||
ed0d216f7e | |||
bd9554a29e | |||
f086dc0d27 | |||
f10b6dd816 | |||
f470b9c566 | |||
b28214487d | |||
6d69f0c5f2 | |||
da776c9a66 | |||
64db979e38 | |||
5f2e61fdd5 | |||
b3d45e1d3f | |||
5f6fb96df1 | |||
5199e0f885 | |||
2f58430573 | |||
d5d32c1e81 | |||
204661287b | |||
e1ab383361 | |||
b9e0e94a01 | |||
a81842428d | |||
f53055efe0 | |||
b2dfcb2b80 | |||
7ac97cbd1d | |||
17898794a9 | |||
443a8ef342 | |||
3607e92dc9 | |||
43e31614cc | |||
989826ce50 | |||
9d6e24c64e | |||
0843317e08 | |||
784eac839d | |||
6fdf225a32 | |||
332da79bf3 | |||
7b273fbb54 | |||
191c504b10 | |||
d13fb6092f | |||
dd780fde44 | |||
37e6474718 | |||
77c0fc07d9 | |||
9212ff6ca1 | |||
864bdf9638 | |||
b776cf66fc | |||
bdbf957b9a | |||
577fd3be88 | |||
faac8b1fa9 |
@ -7,6 +7,7 @@ IndentCaseLabels: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
DerivePointerAlignment: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
QualifierAlignment: Left
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
|
5
.github/workflows/doc.yml
vendored
5
.github/workflows/doc.yml
vendored
@ -7,8 +7,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
@ -25,7 +24,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install doxygen
|
||||
pip install mkdocs-material==9.5.25 mkdocstrings==0.25.1 mike==2.1.1
|
||||
pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
# Workaround https://github.com/actions/checkout/issues/13:
|
||||
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"
|
||||
|
156
.github/workflows/linux.yml
vendored
156
.github/workflows/linux.yml
vendored
@ -7,87 +7,143 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
cxx: [g++-4.9, g++-10, clang++-9]
|
||||
cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11]
|
||||
build_type: [Debug, Release]
|
||||
std: [11]
|
||||
shared: [""]
|
||||
include:
|
||||
- cxx: g++-4.9
|
||||
install: sudo apt install g++-4.9
|
||||
- cxx: g++-8
|
||||
- cxx: clang++-3.6
|
||||
- cxx: g++-11
|
||||
build_type: Debug
|
||||
std: 14
|
||||
install: sudo apt install g++-8
|
||||
- cxx: g++-8
|
||||
build_type: Debug
|
||||
std: 17
|
||||
install: sudo apt install g++-8
|
||||
- cxx: g++-9
|
||||
build_type: Debug
|
||||
std: 17
|
||||
- cxx: g++-10
|
||||
install: sudo apt install g++-11
|
||||
- cxx: g++-11
|
||||
build_type: Debug
|
||||
std: 17
|
||||
- cxx: g++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
install: sudo apt install g++-11
|
||||
- cxx: clang++-8
|
||||
build_type: Debug
|
||||
std: 17
|
||||
cxxflags: -stdlib=libc++
|
||||
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
|
||||
- cxx: clang++-9
|
||||
install: sudo apt install clang-9
|
||||
- cxx: clang++-9
|
||||
build_type: Debug
|
||||
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||
std: 17
|
||||
install: sudo apt install clang-9
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
std: 20
|
||||
cxxflags: -stdlib=libc++
|
||||
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
||||
- cxx: g++-13
|
||||
build_type: Release
|
||||
std: 23
|
||||
install: sudo apt install g++-13
|
||||
shared: -DBUILD_SHARED_LIBS=ON
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
std: 17
|
||||
cxxflags: -stdlib=libc++
|
||||
install: sudo apt install clang-11 libc++-11-dev libc++abi-11-dev
|
||||
- cxx: clang++-11
|
||||
install: sudo apt install clang-11
|
||||
- cxx: clang++-11
|
||||
build_type: Debug
|
||||
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||
std: 17
|
||||
install: sudo apt install clang-11
|
||||
- cxx: clang++-14
|
||||
build_type: Debug
|
||||
std: 20
|
||||
- cxx: clang++-14
|
||||
build_type: Debug
|
||||
std: 20
|
||||
cxxflags: -stdlib=libc++
|
||||
install: sudo apt install libc++-14-dev libc++abi-14-dev
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
|
||||
- name: Set timezone
|
||||
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
|
||||
run: sudo timedatectl set-timezone 'Europe/Kyiv'
|
||||
|
||||
- name: Add repositories for older GCC
|
||||
- name: Install GCC 4.9
|
||||
run: |
|
||||
# Below repo provides GCC 4.9.
|
||||
sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial main'
|
||||
sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe'
|
||||
sudo apt update
|
||||
sudo apt install libatomic1 libc6-dev libgomp1 libitm1 libmpc3
|
||||
# https://launchpad.net/ubuntu/xenial/amd64/g++-4.9/4.9.3-13ubuntu2
|
||||
wget --no-verbose \
|
||||
http://launchpadlibrarian.net/230069137/libmpfr4_3.1.3-2_amd64.deb \
|
||||
http://launchpadlibrarian.net/253728424/libasan1_4.9.3-13ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/253728426/libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/253728432/libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/253728314/gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/253728399/cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/253728404/gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/253728401/g++-4.9_4.9.3-13ubuntu2_amd64.deb
|
||||
sudo dpkg -i \
|
||||
libmpfr4_3.1.3-2_amd64.deb \
|
||||
libasan1_4.9.3-13ubuntu2_amd64.deb \
|
||||
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||
libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
|
||||
gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
|
||||
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||
gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
|
||||
g++-4.9_4.9.3-13ubuntu2_amd64.deb
|
||||
if: ${{ matrix.cxx == 'g++-4.9' }}
|
||||
|
||||
- name: Install Clang 3.6
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libtinfo5
|
||||
# https://code.launchpad.net/ubuntu/xenial/amd64/clang-3.6/1:3.6.2-3ubuntu2
|
||||
wget --no-verbose \
|
||||
http://launchpadlibrarian.net/230019046/libffi6_3.2.1-4_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346109/libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346128/libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346113/libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346131/libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/445346022/libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/254405108/libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/254405097/libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/254405101/libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
|
||||
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
http://launchpadlibrarian.net/254405091/clang-3.6_3.6.2-3ubuntu2_amd64.deb
|
||||
sudo dpkg -i \
|
||||
libffi6_3.2.1-4_amd64.deb \
|
||||
libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
|
||||
libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
|
||||
libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
|
||||
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
|
||||
clang-3.6_3.6.2-3ubuntu2_amd64.deb
|
||||
if: ${{ matrix.cxx == 'clang++-3.6' }}
|
||||
|
||||
- name: Add repositories for newer GCC
|
||||
run: |
|
||||
sudo apt-add-repository ppa:ubuntu-toolchain-r/test
|
||||
if: ${{ matrix.cxx == 'g++-11' || matrix.cxx == 'g++-13' }}
|
||||
if: ${{ matrix.cxx == 'g++-13' }}
|
||||
|
||||
- name: Add Ubuntu mirrors
|
||||
run: |
|
||||
# Github Actions caching proxy is at times unreliable
|
||||
# see https://github.com/actions/runner-images/issues/7048
|
||||
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
|
||||
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
|
||||
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
|
||||
# GitHub Actions caching proxy is at times unreliable
|
||||
# see https://github.com/actions/runner-images/issues/7048.
|
||||
mirrors=/etc/apt/mirrors.txt
|
||||
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | \
|
||||
sudo tee $mirrors
|
||||
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append $mirrors
|
||||
sudo sed -i \
|
||||
"s~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:$mirrors~" \
|
||||
/etc/apt/sources.list
|
||||
|
||||
- name: Create Build Environment
|
||||
- name: Create build environment
|
||||
run: |
|
||||
sudo apt update
|
||||
${{matrix.install}}
|
||||
@ -100,10 +156,12 @@ jobs:
|
||||
CXX: ${{matrix.cxx}}
|
||||
CXXFLAGS: ${{matrix.cxxflags}}
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \
|
||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
|
||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
|
||||
-DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \
|
||||
${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
|
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
|
||||
- name: Set timezone
|
||||
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
|
||||
run: sudo systemsetup -settimezone 'Europe/Minsk'
|
||||
|
||||
- name: Select Xcode 14.3 (macOS 13)
|
||||
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"
|
||||
|
4
.github/workflows/scorecard.yml
vendored
4
.github/workflows/scorecard.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@ -60,6 +60,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
|
||||
|
||||
- name: Set timezone
|
||||
run: tzutil /s "Ekaterinburg Standard Time"
|
||||
run: tzutil /s "FLE Standard Time"
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
@ -78,7 +78,7 @@ jobs:
|
||||
- name: Set timezone
|
||||
run: tzutil /s "Ekaterinburg Standard Time"
|
||||
shell: cmd
|
||||
- uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0
|
||||
- uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0
|
||||
with:
|
||||
release: false
|
||||
msystem: ${{matrix.sys}}
|
||||
|
@ -205,8 +205,7 @@ if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(JoinPaths)
|
||||
@ -432,7 +431,7 @@ if (FMT_INSTALL)
|
||||
|
||||
# Install the library and headers.
|
||||
install(TARGETS ${INSTALL_TARGETS}
|
||||
COMPONENT fmt-core
|
||||
COMPONENT fmt_core
|
||||
EXPORT ${targets_export_name}
|
||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||
@ -448,13 +447,13 @@ if (FMT_INSTALL)
|
||||
# Install version, config and target files.
|
||||
install(FILES ${project_config} ${version_config}
|
||||
DESTINATION ${FMT_CMAKE_DIR}
|
||||
COMPONENT fmt-core)
|
||||
COMPONENT fmt_core)
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||
NAMESPACE fmt::
|
||||
COMPONENT fmt-core)
|
||||
COMPONENT fmt_core)
|
||||
|
||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
|
||||
COMPONENT fmt-core)
|
||||
COMPONENT fmt_core)
|
||||
endif ()
|
||||
|
||||
function(add_doc_target)
|
||||
@ -491,7 +490,7 @@ function(add_doc_target)
|
||||
include(GNUInstallDirs)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
|
||||
COMPONENT fmt-doc OPTIONAL)
|
||||
COMPONENT fmt_doc OPTIONAL)
|
||||
endfunction()
|
||||
|
||||
if (FMT_DOC)
|
||||
|
100
ChangeLog.md
100
ChangeLog.md
@ -1,3 +1,103 @@
|
||||
# 11.2.0 - 2025-05-03
|
||||
|
||||
- Added the `s` specifier for `std::error_code`. It allows formatting an error
|
||||
message as a string. For example:
|
||||
|
||||
```c++
|
||||
#include <fmt/std.h>
|
||||
|
||||
int main() {
|
||||
auto ec = std::make_error_code(std::errc::no_such_file_or_directory);
|
||||
fmt::print("{:s}\n", ec);
|
||||
}
|
||||
```
|
||||
|
||||
prints
|
||||
|
||||
```
|
||||
No such file or directory
|
||||
```
|
||||
(The actual message is platform-specific.)
|
||||
|
||||
- Fixed formatting of `std::chrono::local_time` and `tm`
|
||||
(https://github.com/fmtlib/fmt/issues/3815,
|
||||
https://github.com/fmtlib/fmt/issues/4350).
|
||||
For example ([godbolt](https://www.godbolt.org/z/8o4b1PPn5)):
|
||||
|
||||
```c++
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
std::chrono::zoned_time zt(
|
||||
std::chrono::current_zone(),
|
||||
std::chrono::system_clock::now());
|
||||
fmt::print("{}", zt.get_local_time());
|
||||
}
|
||||
```
|
||||
|
||||
is now formatted consistenly across platforms.
|
||||
|
||||
- Added diagnostics for cases when timezone information is not available.
|
||||
For example:
|
||||
|
||||
```c++
|
||||
fmt::print("{:Z}", std::chrono::local_seconds());
|
||||
```
|
||||
|
||||
now gives a compile-time error.
|
||||
|
||||
- Deprecated `fmt::localtime` in favor of `std::localtime`.
|
||||
|
||||
- Fixed compilation with GCC 15 and C++20 modules enabled
|
||||
(https://github.com/fmtlib/fmt/pull/4347). Thanks @tkhyn.
|
||||
|
||||
- Fixed handling of named arguments in format specs
|
||||
(https://github.com/fmtlib/fmt/issues/4360,
|
||||
https://github.com/fmtlib/fmt/pull/4361). Thanks @dinomight.
|
||||
|
||||
- Added error reporting for duplicate named arguments
|
||||
(https://github.com/fmtlib/fmt/pull/4367). Thanks @dinomight.
|
||||
|
||||
- Fixed formatting of `long` with `FMT_BUILTIN_TYPES=0`
|
||||
(https://github.com/fmtlib/fmt/issues/4375,
|
||||
https://github.com/fmtlib/fmt/issues/4394).
|
||||
|
||||
- Optimized `text_style` using bit packing
|
||||
(https://github.com/fmtlib/fmt/pull/4363). Thanks @LocalSpook.
|
||||
|
||||
- Added support for incomplete types (https://github.com/fmtlib/fmt/issues/3180,
|
||||
https://github.com/fmtlib/fmt/pull/4383). Thanks @LocalSpook.
|
||||
|
||||
- Fixed a flush issue in `fmt::print` when using libstdc++
|
||||
(https://github.com/fmtlib/fmt/issues/4398).
|
||||
|
||||
- Fixed `fmt::println` usage with `FMT_ENFORCE_COMPILE_STRING` and legacy
|
||||
compile-time checks (https://github.com/fmtlib/fmt/pull/4407).
|
||||
Thanks @madmaxoft.
|
||||
|
||||
- Removed legacy header `fmt/core.h` from docs
|
||||
(https://github.com/fmtlib/fmt/pull/4421,
|
||||
https://github.com/fmtlib/fmt/pull/4422). Thanks @krzysztofkortas.
|
||||
|
||||
- Worked around limitations of `__builtin_strlen` during constant evaluation
|
||||
(https://github.com/fmtlib/fmt/issues/4423,
|
||||
https://github.com/fmtlib/fmt/pull/4429). Thanks @BRevzin.
|
||||
|
||||
- Worked around a bug in MSVC v141 (https://github.com/fmtlib/fmt/issues/4412,
|
||||
https://github.com/fmtlib/fmt/pull/4413). Thanks @hirohira9119.
|
||||
|
||||
- Removed the `fmt_detail` namespace
|
||||
(https://github.com/fmtlib/fmt/issues/4324).
|
||||
|
||||
- Removed specializations of `std::is_floating_point` in tests
|
||||
(https://github.com/fmtlib/fmt/issues/4417).
|
||||
|
||||
- Fixed a CMake error when setting `CMAKE_MODULE_PATH` in the pedantic mode
|
||||
(https://github.com/fmtlib/fmt/pull/4426). Thanks @rlalik.
|
||||
|
||||
- Updated the Bazel config (https://github.com/fmtlib/fmt/pull/4400).
|
||||
Thanks @Vertexwahn.
|
||||
|
||||
# 11.1.4 - 2025-02-26
|
||||
|
||||
- Fixed ABI compatibility with earlier 11.x versions on Windows
|
||||
|
@ -47,7 +47,7 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
|
||||
hundred million integers to strings per
|
||||
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
|
||||
- Small code size both in terms of source code with the minimum
|
||||
configuration consisting of just three files, `core.h`, `format.h`
|
||||
configuration consisting of just three files, `base.h`, `format.h`
|
||||
and `format-inl.h`, and compiled code; see [Compile time and code
|
||||
bloat](#compile-time-and-code-bloat)
|
||||
- Reliability: the library has an extensive set of
|
||||
@ -74,7 +74,7 @@ See the [documentation](https://fmt.dev) for more details.
|
||||
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
|
||||
|
||||
``` c++
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/base.h>
|
||||
|
||||
int main() {
|
||||
fmt::print("Hello, world!\n");
|
||||
|
@ -674,7 +674,7 @@
|
||||
https://github.com/fmtlib/fmt/issues/1747,
|
||||
https://github.com/fmtlib/fmt/pull/1750).
|
||||
Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10,
|
||||
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink,
|
||||
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime,
|
||||
@tohammer and @0x8000-0000.
|
||||
|
||||
# 6.2.1 - 2020-05-09
|
||||
|
14
doc/api.md
14
doc/api.md
@ -79,6 +79,8 @@ time formatting and [`fmt/std.h`](#std-api) for other standard library types.
|
||||
|
||||
There are two ways to make a user-defined type formattable: providing a
|
||||
`format_as` function or specializing the `formatter` struct template.
|
||||
Formatting of non-void pointer types is intentionally disallowed and they
|
||||
cannot be made formattable via either extension API.
|
||||
|
||||
Use `format_as` if you want to make your type formattable as some other
|
||||
type with the same format specifiers. The `format_as` function should
|
||||
@ -220,7 +222,7 @@ You can also write a formatter for a hierarchy of classes:
|
||||
```c++
|
||||
// demo.h:
|
||||
#include <type_traits>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
struct A {
|
||||
virtual ~A() {}
|
||||
@ -403,7 +405,7 @@ All formatting is locale-independent by default. Use the `'L'` format
|
||||
specifier to insert the appropriate number separator characters from the
|
||||
locale:
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <locale>
|
||||
|
||||
std::locale::global(std::locale("en_US.UTF-8"));
|
||||
@ -473,9 +475,9 @@ chrono-format-specifications).
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
std::time_t t = std::time(nullptr);
|
||||
auto now = std::chrono::system_clock::now();
|
||||
|
||||
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
||||
fmt::print("The date is {:%Y-%m-%d}.\n", now);
|
||||
// Output: The date is 2020-11-07.
|
||||
// (with 2020-11-07 replaced by the current date)
|
||||
|
||||
@ -488,8 +490,6 @@ chrono-format-specifications).
|
||||
// Output: strftime-like format: 03:15:30
|
||||
}
|
||||
|
||||
::: localtime(std::time_t)
|
||||
|
||||
::: gmtime(std::time_t)
|
||||
|
||||
<a id="std-api"></a>
|
||||
@ -580,7 +580,7 @@ performance bottleneck.
|
||||
|
||||
`fmt/color.h` provides support for terminal color and text style output.
|
||||
|
||||
::: print(const text_style&, format_string<T...>, T&&...)
|
||||
::: print(text_style, format_string<T...>, T&&...)
|
||||
|
||||
::: fg(detail::color_type)
|
||||
|
||||
|
@ -122,7 +122,7 @@ hide:
|
||||
</p>
|
||||
<p>
|
||||
The library is highly portable and requires only a minimal <b>subset of
|
||||
C++11</b> features which are available in GCC 4.9, Clang 3.4, MSVC 19.10
|
||||
C++11</b> features which are available in GCC 4.9, Clang 3.6, MSVC 19.10
|
||||
(2017) and later. Newer compiler and standard library features are used
|
||||
if available, and enable additional functionality.
|
||||
</p>
|
||||
|
@ -71,7 +71,7 @@ class dynamic_arg_list {
|
||||
* It can be implicitly converted into `fmt::basic_format_args` for passing
|
||||
* into type-erased formatting functions such as `fmt::vformat`.
|
||||
*/
|
||||
template <typename Context> class dynamic_format_arg_store {
|
||||
FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
#endif
|
||||
|
||||
// The fmt library version in the form major * 10000 + minor * 100 + patch.
|
||||
#define FMT_VERSION 110104
|
||||
#define FMT_VERSION 110201
|
||||
|
||||
// Detect compiler versions.
|
||||
#if defined(__clang__) && !defined(__ibmxl__)
|
||||
@ -209,20 +209,6 @@
|
||||
# define FMT_DEPRECATED /* deprecated */
|
||||
#endif
|
||||
|
||||
#ifdef FMT_ALWAYS_INLINE
|
||||
// Use the provided definition.
|
||||
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
|
||||
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
# define FMT_ALWAYS_INLINE inline
|
||||
#endif
|
||||
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
|
||||
#ifdef NDEBUG
|
||||
# define FMT_INLINE FMT_ALWAYS_INLINE
|
||||
#else
|
||||
# define FMT_INLINE inline
|
||||
#endif
|
||||
|
||||
#if FMT_GCC_VERSION || FMT_CLANG_VERSION
|
||||
# define FMT_VISIBILITY(value) __attribute__((visibility(value)))
|
||||
#else
|
||||
@ -249,6 +235,28 @@
|
||||
# define FMT_MSC_WARNING(...)
|
||||
#endif
|
||||
|
||||
// Enable minimal optimizations for more compact code in debug mode.
|
||||
FMT_PRAGMA_GCC(push_options)
|
||||
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
|
||||
FMT_PRAGMA_GCC(optimize("Og"))
|
||||
# define FMT_GCC_OPTIMIZED
|
||||
#endif
|
||||
FMT_PRAGMA_CLANG(diagnostic push)
|
||||
|
||||
#ifdef FMT_ALWAYS_INLINE
|
||||
// Use the provided definition.
|
||||
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
|
||||
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
# define FMT_ALWAYS_INLINE inline
|
||||
#endif
|
||||
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
|
||||
#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED)
|
||||
# define FMT_INLINE FMT_ALWAYS_INLINE
|
||||
#else
|
||||
# define FMT_INLINE inline
|
||||
#endif
|
||||
|
||||
#ifndef FMT_BEGIN_NAMESPACE
|
||||
# define FMT_BEGIN_NAMESPACE \
|
||||
namespace fmt { \
|
||||
@ -297,13 +305,6 @@
|
||||
using unused = int[]; \
|
||||
(void)unused { 0, (expr, 0)... }
|
||||
|
||||
// Enable minimal optimizations for more compact code in debug mode.
|
||||
FMT_PRAGMA_GCC(push_options)
|
||||
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
|
||||
FMT_PRAGMA_GCC(optimize("Og"))
|
||||
#endif
|
||||
FMT_PRAGMA_CLANG(diagnostic push)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// Implementations of enable_if_t and other metafunctions for older systems.
|
||||
@ -325,8 +326,8 @@ using underlying_t = typename std::underlying_type<T>::type;
|
||||
template <typename T> using decay_t = typename std::decay<T>::type;
|
||||
using nullptr_t = decltype(nullptr);
|
||||
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
|
||||
// A workaround for gcc 4.9 to make void_t work in a SFINAE context.
|
||||
#if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION
|
||||
// A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context.
|
||||
template <typename...> struct void_t_impl {
|
||||
using type = void;
|
||||
};
|
||||
@ -466,8 +467,7 @@ template <typename T> constexpr const char* narrow(const T*) { return nullptr; }
|
||||
constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; }
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n)
|
||||
-> int {
|
||||
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int {
|
||||
if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n);
|
||||
for (; n != 0; ++s1, ++s2, --n) {
|
||||
if (*s1 < *s2) return -1;
|
||||
@ -526,20 +526,20 @@ template <typename Char> class basic_string_view {
|
||||
|
||||
constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {}
|
||||
|
||||
/// Constructs a string reference object from a C string and a size.
|
||||
/// Constructs a string view object from a C string and a size.
|
||||
constexpr basic_string_view(const Char* s, size_t count) noexcept
|
||||
: data_(s), size_(count) {}
|
||||
|
||||
constexpr basic_string_view(nullptr_t) = delete;
|
||||
|
||||
/// Constructs a string reference object from a C string.
|
||||
/// Constructs a string view object from a C string.
|
||||
#if FMT_GCC_VERSION
|
||||
FMT_ALWAYS_INLINE
|
||||
#endif
|
||||
FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {
|
||||
#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
|
||||
if (std::is_same<Char, char>::value) {
|
||||
size_ = __builtin_strlen(detail::narrow(s));
|
||||
if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) {
|
||||
size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr.
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@ -548,7 +548,7 @@ template <typename Char> class basic_string_view {
|
||||
size_ = len;
|
||||
}
|
||||
|
||||
/// Constructs a string reference from a `std::basic_string` or a
|
||||
/// Constructs a string view from a `std::basic_string` or a
|
||||
/// `std::basic_string_view` object.
|
||||
template <typename S,
|
||||
FMT_ENABLE_IF(detail::is_std_string_like<S>::value&& std::is_same<
|
||||
@ -585,7 +585,6 @@ template <typename Char> class basic_string_view {
|
||||
return starts_with(basic_string_view<Char>(s));
|
||||
}
|
||||
|
||||
// Lexicographically compare this string reference to other.
|
||||
FMT_CONSTEXPR auto compare(basic_string_view other) const -> int {
|
||||
int result =
|
||||
detail::compare(data_, other.data_, min_of(size_, other.size_));
|
||||
@ -616,7 +615,7 @@ template <typename Char> class basic_string_view {
|
||||
|
||||
using string_view = basic_string_view<char>;
|
||||
|
||||
/// Specifies if `T` is an extended character type. Can be specialized by users.
|
||||
// DEPRECATED! Will be merged with is_char and moved to detail.
|
||||
template <typename T> struct is_xchar : std::false_type {};
|
||||
template <> struct is_xchar<wchar_t> : std::true_type {};
|
||||
template <> struct is_xchar<char16_t> : std::true_type {};
|
||||
@ -625,7 +624,7 @@ template <> struct is_xchar<char32_t> : std::true_type {};
|
||||
template <> struct is_xchar<char8_t> : std::true_type {};
|
||||
#endif
|
||||
|
||||
// DEPRECATED! Will be replaced with an alias to prevent specializations.
|
||||
// Specifies if `T` is a character (code unit) type.
|
||||
template <typename T> struct is_char : is_xchar<T> {};
|
||||
template <> struct is_char<char> : std::true_type {};
|
||||
|
||||
@ -1032,6 +1031,11 @@ enum {
|
||||
|
||||
struct view {};
|
||||
|
||||
template <typename T, typename Enable = std::true_type>
|
||||
struct is_view : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_view<T, bool_constant<sizeof(T) != 0>> : std::is_base_of<view, T> {};
|
||||
|
||||
template <typename Char, typename T> struct named_arg;
|
||||
template <typename T> struct is_named_arg : std::false_type {};
|
||||
template <typename T> struct is_static_named_arg : std::false_type {};
|
||||
@ -1064,6 +1068,16 @@ template <typename Char> struct named_arg_info {
|
||||
int id;
|
||||
};
|
||||
|
||||
// named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13.
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args,
|
||||
int named_arg_index,
|
||||
basic_string_view<Char> arg_name) {
|
||||
for (int i = 0; i < named_arg_index; ++i) {
|
||||
if (named_args[i].name == arg_name) report_error("duplicate named arg");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename T, FMT_ENABLE_IF(!is_named_arg<T>::value)>
|
||||
void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
|
||||
++arg_index;
|
||||
@ -1071,6 +1085,7 @@ void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
|
||||
template <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
|
||||
void init_named_arg(named_arg_info<Char>* named_args, int& arg_index,
|
||||
int& named_arg_index, const T& arg) {
|
||||
check_for_duplicate<Char>(named_args, named_arg_index, arg.name);
|
||||
named_args[named_arg_index++] = {arg.name, arg_index++};
|
||||
}
|
||||
|
||||
@ -1084,12 +1099,13 @@ template <typename T, typename Char,
|
||||
FMT_ENABLE_IF(is_static_named_arg<T>::value)>
|
||||
FMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args,
|
||||
int& arg_index, int& named_arg_index) {
|
||||
check_for_duplicate<Char>(named_args, named_arg_index, T::name);
|
||||
named_args[named_arg_index++] = {T::name, arg_index++};
|
||||
}
|
||||
|
||||
// To minimize the number of types we need to deal with, long is translated
|
||||
// either to int or to long long depending on its size.
|
||||
enum { long_short = sizeof(long) == sizeof(int) };
|
||||
enum { long_short = sizeof(long) == sizeof(int) && FMT_BUILTIN_TYPES };
|
||||
using long_type = conditional_t<long_short, int, long long>;
|
||||
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
|
||||
|
||||
@ -1662,12 +1678,12 @@ template <typename... T> struct arg_pack {};
|
||||
template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES>
|
||||
class format_string_checker {
|
||||
private:
|
||||
type types_[max_of(1, NUM_ARGS)];
|
||||
named_arg_info<Char> named_args_[max_of(1, NUM_NAMED_ARGS)];
|
||||
type types_[max_of<size_t>(1, NUM_ARGS)];
|
||||
named_arg_info<Char> named_args_[max_of<size_t>(1, NUM_NAMED_ARGS)];
|
||||
compile_parse_context<Char> context_;
|
||||
|
||||
using parse_func = auto (*)(parse_context<Char>&) -> const Char*;
|
||||
parse_func parse_funcs_[max_of(1, NUM_ARGS)];
|
||||
parse_func parse_funcs_[max_of<size_t>(1, NUM_ARGS)];
|
||||
|
||||
public:
|
||||
template <typename... T>
|
||||
@ -1706,7 +1722,17 @@ class format_string_checker {
|
||||
-> const Char* {
|
||||
context_.advance_to(begin);
|
||||
if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_);
|
||||
while (begin != end && *begin != '}') ++begin;
|
||||
|
||||
// If id is out of range, it means we do not know the type and cannot parse
|
||||
// the format at compile time. Instead, skip over content until we finish
|
||||
// the format spec, accounting for any nested replacements.
|
||||
for (int bracket_count = 0;
|
||||
begin != end && (bracket_count > 0 || *begin != '}'); ++begin) {
|
||||
if (*begin == '{')
|
||||
++bracket_count;
|
||||
else if (*begin == '}')
|
||||
--bracket_count;
|
||||
}
|
||||
return begin;
|
||||
}
|
||||
|
||||
@ -2006,6 +2032,17 @@ struct has_back_insert_iterator_container_append<
|
||||
.append(std::declval<InputIt>(),
|
||||
std::declval<InputIt>()))>> : std::true_type {};
|
||||
|
||||
template <typename OutputIt, typename InputIt, typename = void>
|
||||
struct has_back_insert_iterator_container_insert_at_end : std::false_type {};
|
||||
|
||||
template <typename OutputIt, typename InputIt>
|
||||
struct has_back_insert_iterator_container_insert_at_end<
|
||||
OutputIt, InputIt,
|
||||
void_t<decltype(get_container(std::declval<OutputIt>())
|
||||
.insert(get_container(std::declval<OutputIt>()).end(),
|
||||
std::declval<InputIt>(),
|
||||
std::declval<InputIt>()))>> : std::true_type {};
|
||||
|
||||
// An optimized version of std::copy with the output value type (T).
|
||||
template <typename T, typename InputIt, typename OutputIt,
|
||||
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&
|
||||
@ -2020,6 +2057,8 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
|
||||
template <typename T, typename InputIt, typename OutputIt,
|
||||
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&
|
||||
!has_back_insert_iterator_container_append<
|
||||
OutputIt, InputIt>::value &&
|
||||
has_back_insert_iterator_container_insert_at_end<
|
||||
OutputIt, InputIt>::value)>
|
||||
FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
|
||||
-> OutputIt {
|
||||
@ -2029,7 +2068,11 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
|
||||
}
|
||||
|
||||
template <typename T, typename InputIt, typename OutputIt,
|
||||
FMT_ENABLE_IF(!is_back_insert_iterator<OutputIt>::value)>
|
||||
FMT_ENABLE_IF(!(is_back_insert_iterator<OutputIt>::value &&
|
||||
(has_back_insert_iterator_container_append<
|
||||
OutputIt, InputIt>::value ||
|
||||
has_back_insert_iterator_container_insert_at_end<
|
||||
OutputIt, InputIt>::value)))>
|
||||
FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {
|
||||
while (begin != end) *out++ = static_cast<T>(*begin++);
|
||||
return out;
|
||||
@ -2236,6 +2279,7 @@ template <typename Context> class value {
|
||||
}
|
||||
|
||||
// Formats an argument of a custom type, such as a user-defined class.
|
||||
// DEPRECATED! Formatter template parameter will be removed.
|
||||
template <typename T, typename Formatter>
|
||||
static void format_custom(void* arg, parse_context<char_type>& parse_ctx,
|
||||
Context& ctx) {
|
||||
@ -2311,8 +2355,9 @@ template <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,
|
||||
unsigned long long DESC>
|
||||
struct named_arg_store {
|
||||
// args_[0].named_args points to named_args to avoid bloating format_args.
|
||||
arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS];
|
||||
named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS];
|
||||
arg_t<Context, NUM_ARGS> args[1u + NUM_ARGS];
|
||||
named_arg_info<typename Context::char_type>
|
||||
named_args[static_cast<size_t>(NUM_NAMED_ARGS)];
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values)
|
||||
@ -2345,7 +2390,7 @@ struct format_arg_store {
|
||||
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
|
||||
using type =
|
||||
conditional_t<NUM_NAMED_ARGS == 0,
|
||||
arg_t<Context, NUM_ARGS>[max_of(1, NUM_ARGS)],
|
||||
arg_t<Context, NUM_ARGS>[max_of<size_t>(1, NUM_ARGS)],
|
||||
named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>;
|
||||
type args;
|
||||
};
|
||||
@ -2703,7 +2748,7 @@ template <typename... T> struct fstring {
|
||||
template <size_t N>
|
||||
FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) {
|
||||
using namespace detail;
|
||||
static_assert(count<(std::is_base_of<view, remove_reference_t<T>>::value &&
|
||||
static_assert(count<(is_view<remove_cvref_t<T>>::value &&
|
||||
std::is_reference<T>::value)...>() == 0,
|
||||
"passing views as lvalues is disallowed");
|
||||
if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
|
||||
|
@ -22,21 +22,6 @@
|
||||
|
||||
#include "format.h"
|
||||
|
||||
namespace fmt_detail {
|
||||
struct time_zone {
|
||||
template <typename Duration, typename T>
|
||||
auto to_sys(T)
|
||||
-> std::chrono::time_point<std::chrono::system_clock, Duration> {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
template <typename... T> inline auto current_zone(T...) -> time_zone* {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename... T> inline void _tzset(T...) {}
|
||||
} // namespace fmt_detail
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// Enable safe chrono durations, unless explicitly disabled.
|
||||
@ -341,7 +326,7 @@ inline auto get_classic_locale() -> const std::locale& {
|
||||
}
|
||||
|
||||
template <typename CodeUnit> struct codecvt_result {
|
||||
static constexpr const size_t max_size = 32;
|
||||
static constexpr size_t max_size = 32;
|
||||
CodeUnit buf[max_size];
|
||||
CodeUnit* end;
|
||||
};
|
||||
@ -435,14 +420,11 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
|
||||
}
|
||||
|
||||
template <typename Rep1, typename Rep2>
|
||||
struct is_same_arithmetic_type
|
||||
: public std::integral_constant<bool,
|
||||
(std::is_integral<Rep1>::value &&
|
||||
std::is_integral<Rep2>::value) ||
|
||||
(std::is_floating_point<Rep1>::value &&
|
||||
std::is_floating_point<Rep2>::value)> {
|
||||
};
|
||||
template <typename T, typename U>
|
||||
using is_similar_arithmetic_type =
|
||||
bool_constant<(std::is_integral<T>::value && std::is_integral<U>::value) ||
|
||||
(std::is_floating_point<T>::value &&
|
||||
std::is_floating_point<U>::value)>;
|
||||
|
||||
FMT_NORETURN inline void throw_duration_error() {
|
||||
FMT_THROW(format_error("cannot format duration"));
|
||||
@ -501,9 +483,9 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
#endif
|
||||
}
|
||||
|
||||
template <
|
||||
typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(
|
||||
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
|
||||
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
// Mixed integer <-> float cast is not supported by safe_duration_cast.
|
||||
return std::chrono::duration_cast<To>(from);
|
||||
@ -519,12 +501,30 @@ auto to_time_t(sys_time<Duration> time_point) -> std::time_t {
|
||||
.count();
|
||||
}
|
||||
|
||||
// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without
|
||||
// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160.
|
||||
template <typename T> FMT_CONSTEXPR auto has_current_zone() -> bool {
|
||||
using namespace std::chrono;
|
||||
using namespace fmt_detail;
|
||||
return !std::is_same<decltype(current_zone()), fmt_detail::time_zone*>::value;
|
||||
namespace tz {
|
||||
|
||||
// DEPRECATED!
|
||||
struct time_zone {
|
||||
template <typename Duration, typename LocalTime>
|
||||
auto to_sys(LocalTime) -> sys_time<Duration> {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
template <typename... T> auto current_zone(T...) -> time_zone* {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename... T> void _tzset(T...) {}
|
||||
} // namespace tz
|
||||
|
||||
// DEPRECATED!
|
||||
inline void tzset_once() {
|
||||
static bool init = []() {
|
||||
using namespace tz;
|
||||
_tzset();
|
||||
return false;
|
||||
}();
|
||||
ignore_unused(init);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
@ -535,7 +535,7 @@ FMT_BEGIN_EXPORT
|
||||
* expressed in local time. Unlike `std::localtime`, this function is
|
||||
* thread-safe on most platforms.
|
||||
*/
|
||||
inline auto localtime(std::time_t time) -> std::tm {
|
||||
FMT_DEPRECATED inline auto localtime(std::time_t time) -> std::tm {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
@ -572,11 +572,11 @@ inline auto localtime(std::time_t time) -> std::tm {
|
||||
}
|
||||
|
||||
#if FMT_USE_LOCAL_TIME
|
||||
template <typename Duration,
|
||||
FMT_ENABLE_IF(detail::has_current_zone<Duration>())>
|
||||
inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
|
||||
template <typename Duration>
|
||||
FMT_DEPRECATED auto localtime(std::chrono::local_time<Duration> time)
|
||||
-> std::tm {
|
||||
using namespace std::chrono;
|
||||
using namespace fmt_detail;
|
||||
using namespace detail::tz;
|
||||
return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time)));
|
||||
}
|
||||
#endif
|
||||
@ -652,7 +652,7 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
|
||||
// Add ASCII '0' to each digit byte and insert separators.
|
||||
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
|
||||
|
||||
constexpr const size_t len = 8;
|
||||
constexpr size_t len = 8;
|
||||
if (const_check(is_big_endian())) {
|
||||
char tmp[len];
|
||||
std::memcpy(tmp, &digits, len);
|
||||
@ -911,7 +911,14 @@ template <typename Derived> struct null_chrono_spec_handler {
|
||||
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
|
||||
};
|
||||
|
||||
struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
|
||||
class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
|
||||
private:
|
||||
bool has_timezone_ = false;
|
||||
|
||||
public:
|
||||
constexpr explicit tm_format_checker(bool has_timezone)
|
||||
: has_timezone_(has_timezone) {}
|
||||
|
||||
FMT_NORETURN inline void unsupported() {
|
||||
FMT_THROW(format_error("no format"));
|
||||
}
|
||||
@ -949,8 +956,12 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
|
||||
FMT_CONSTEXPR void on_24_hour_time() {}
|
||||
FMT_CONSTEXPR void on_iso_time() {}
|
||||
FMT_CONSTEXPR void on_am_pm() {}
|
||||
FMT_CONSTEXPR void on_utc_offset(numeric_system) {}
|
||||
FMT_CONSTEXPR void on_tz_name() {}
|
||||
FMT_CONSTEXPR void on_utc_offset(numeric_system) {
|
||||
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
|
||||
}
|
||||
FMT_CONSTEXPR void on_tz_name() {
|
||||
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
|
||||
}
|
||||
};
|
||||
|
||||
inline auto tm_wday_full_name(int wday) -> const char* {
|
||||
@ -980,24 +991,27 @@ inline auto tm_mon_short_name(int mon) -> const char* {
|
||||
}
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_member_data_tm_gmtoff : std::false_type {};
|
||||
struct has_tm_gmtoff : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>
|
||||
: std::true_type {};
|
||||
struct has_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>> : std::true_type {};
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct has_member_data_tm_zone : std::false_type {};
|
||||
template <typename T, typename = void> struct has_tm_zone : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>
|
||||
: std::true_type {};
|
||||
struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {};
|
||||
|
||||
inline void tzset_once() {
|
||||
static bool init = []() {
|
||||
using namespace fmt_detail;
|
||||
_tzset();
|
||||
return false;
|
||||
}();
|
||||
ignore_unused(init);
|
||||
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
|
||||
bool set_tm_zone(T& time, char* tz) {
|
||||
time.tm_zone = tz;
|
||||
return true;
|
||||
}
|
||||
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
|
||||
bool set_tm_zone(T&, char*) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline char* utc() {
|
||||
static char tz[] = "UTC";
|
||||
return tz;
|
||||
}
|
||||
|
||||
// Converts value to Int and checks that it's in the range [0, upper).
|
||||
@ -1005,7 +1019,7 @@ template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
inline auto to_nonnegative_int(T value, Int upper) -> Int {
|
||||
if (!std::is_unsigned<Int>::value &&
|
||||
(value < 0 || to_unsigned(value) > to_unsigned(upper))) {
|
||||
FMT_THROW(fmt::format_error("chrono value is out of range"));
|
||||
FMT_THROW(format_error("chrono value is out of range"));
|
||||
}
|
||||
return static_cast<Int>(value);
|
||||
}
|
||||
@ -1090,7 +1104,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
|
||||
|
||||
// Format subseconds which are given as a floating point type with an
|
||||
// appropriate number of digits. We cannot pass the Duration here, as we
|
||||
// explicitly need to pass the Rep value in the chrono_formatter.
|
||||
// explicitly need to pass the Rep value in the duration_formatter.
|
||||
template <typename Duration>
|
||||
void write_floating_seconds(memory_buffer& buf, Duration duration,
|
||||
int num_fractional_digits = -1) {
|
||||
@ -1124,7 +1138,7 @@ class tm_writer {
|
||||
static constexpr int days_per_week = 7;
|
||||
|
||||
const std::locale& loc_;
|
||||
const bool is_classic_;
|
||||
bool is_classic_;
|
||||
OutputIt out_;
|
||||
const Duration* subsecs_;
|
||||
const std::tm& tm_;
|
||||
@ -1160,8 +1174,8 @@ class tm_writer {
|
||||
}
|
||||
|
||||
auto tm_hour12() const noexcept -> int {
|
||||
const auto h = tm_hour();
|
||||
const auto z = h < 12 ? h : h - 12;
|
||||
auto h = tm_hour();
|
||||
auto z = h < 12 ? h : h - 12;
|
||||
return z == 0 ? 12 : z;
|
||||
}
|
||||
|
||||
@ -1177,11 +1191,11 @@ class tm_writer {
|
||||
|
||||
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
|
||||
auto iso_year_weeks(long long curr_year) const noexcept -> int {
|
||||
const auto prev_year = curr_year - 1;
|
||||
const auto curr_p =
|
||||
auto prev_year = curr_year - 1;
|
||||
auto curr_p =
|
||||
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
|
||||
days_per_week;
|
||||
const auto prev_p =
|
||||
auto prev_p =
|
||||
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
|
||||
days_per_week;
|
||||
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
|
||||
@ -1191,15 +1205,15 @@ class tm_writer {
|
||||
days_per_week;
|
||||
}
|
||||
auto tm_iso_week_year() const noexcept -> long long {
|
||||
const auto year = tm_year();
|
||||
const auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
auto year = tm_year();
|
||||
auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
if (w < 1) return year - 1;
|
||||
if (w > iso_year_weeks(year)) return year + 1;
|
||||
return year;
|
||||
}
|
||||
auto tm_iso_week_of_year() const noexcept -> int {
|
||||
const auto year = tm_year();
|
||||
const auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
auto year = tm_year();
|
||||
auto w = iso_week_num(tm_yday(), tm_wday());
|
||||
if (w < 1) return iso_year_weeks(year - 1);
|
||||
if (w > iso_year_weeks(year)) return 1;
|
||||
return w;
|
||||
@ -1236,9 +1250,8 @@ class tm_writer {
|
||||
uint32_or_64_or_128_t<long long> n = to_unsigned(year);
|
||||
const int num_digits = count_digits(n);
|
||||
if (negative && pad == pad_type::zero) *out_++ = '-';
|
||||
if (width > num_digits) {
|
||||
if (width > num_digits)
|
||||
out_ = detail::write_padding(out_, pad, width - num_digits);
|
||||
}
|
||||
if (negative && pad != pad_type::zero) *out_++ = '-';
|
||||
out_ = format_decimal<Char>(out_, n, num_digits);
|
||||
}
|
||||
@ -1259,45 +1272,22 @@ class tm_writer {
|
||||
write2(static_cast<int>(offset % 60));
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)>
|
||||
void format_utc_offset_impl(const T& tm, numeric_system ns) {
|
||||
template <typename T, FMT_ENABLE_IF(has_tm_gmtoff<T>::value)>
|
||||
void format_utc_offset(const T& tm, numeric_system ns) {
|
||||
write_utc_offset(tm.tm_gmtoff, ns);
|
||||
}
|
||||
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)>
|
||||
void format_utc_offset_impl(const T& tm, numeric_system ns) {
|
||||
#if defined(_WIN32) && defined(_UCRT)
|
||||
tzset_once();
|
||||
long offset = 0;
|
||||
_get_timezone(&offset);
|
||||
if (tm.tm_isdst) {
|
||||
long dstbias = 0;
|
||||
_get_dstbias(&dstbias);
|
||||
offset += dstbias;
|
||||
}
|
||||
write_utc_offset(-offset, ns);
|
||||
#else
|
||||
if (ns == numeric_system::standard) return format_localized('z');
|
||||
|
||||
// Extract timezone offset from timezone conversion functions.
|
||||
std::tm gtm = tm;
|
||||
std::time_t gt = std::mktime(>m);
|
||||
std::tm ltm = gmtime(gt);
|
||||
std::time_t lt = std::mktime(<m);
|
||||
long long offset = gt - lt;
|
||||
write_utc_offset(offset, ns);
|
||||
#endif
|
||||
template <typename T, FMT_ENABLE_IF(!has_tm_gmtoff<T>::value)>
|
||||
void format_utc_offset(const T&, numeric_system ns) {
|
||||
write_utc_offset(0, ns);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)>
|
||||
void format_tz_name_impl(const T& tm) {
|
||||
if (is_classic_)
|
||||
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
|
||||
else
|
||||
format_localized('Z');
|
||||
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
|
||||
void format_tz_name(const T& tm) {
|
||||
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
|
||||
}
|
||||
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)>
|
||||
void format_tz_name_impl(const T&) {
|
||||
format_localized('Z');
|
||||
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
|
||||
void format_tz_name(const T&) {
|
||||
out_ = std::copy_n(utc(), 3, out_);
|
||||
}
|
||||
|
||||
void format_localized(char format, char modifier = 0) {
|
||||
@ -1408,8 +1398,8 @@ class tm_writer {
|
||||
out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_);
|
||||
}
|
||||
|
||||
void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); }
|
||||
void on_tz_name() { format_tz_name_impl(tm_); }
|
||||
void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); }
|
||||
void on_tz_name() { format_tz_name(tm_); }
|
||||
|
||||
void on_year(numeric_system ns, pad_type pad) {
|
||||
if (is_classic_ || ns == numeric_system::standard)
|
||||
@ -1483,11 +1473,10 @@ class tm_writer {
|
||||
void on_day_of_year(pad_type pad) {
|
||||
auto yday = tm_yday() + 1;
|
||||
auto digit1 = yday / 100;
|
||||
if (digit1 != 0) {
|
||||
if (digit1 != 0)
|
||||
write1(digit1);
|
||||
} else {
|
||||
else
|
||||
out_ = detail::write_padding(out_, pad);
|
||||
}
|
||||
write2(yday % 100, pad);
|
||||
}
|
||||
|
||||
@ -1624,18 +1613,16 @@ template <typename Rep, typename Period,
|
||||
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
||||
inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
|
||||
-> std::chrono::duration<Rep, std::milli> {
|
||||
// this may overflow and/or the result may not fit in the
|
||||
// target type.
|
||||
// This may overflow and/or the result may not fit in the target type.
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
using CommonSecondsType =
|
||||
using common_seconds_type =
|
||||
typename std::common_type<decltype(d), std::chrono::seconds>::type;
|
||||
const auto d_as_common = detail::duration_cast<CommonSecondsType>(d);
|
||||
const auto d_as_whole_seconds =
|
||||
auto d_as_common = detail::duration_cast<common_seconds_type>(d);
|
||||
auto d_as_whole_seconds =
|
||||
detail::duration_cast<std::chrono::seconds>(d_as_common);
|
||||
// this conversion should be nonproblematic
|
||||
const auto diff = d_as_common - d_as_whole_seconds;
|
||||
const auto ms =
|
||||
detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
|
||||
// This conversion should be nonproblematic.
|
||||
auto diff = d_as_common - d_as_whole_seconds;
|
||||
auto ms = detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
|
||||
return ms;
|
||||
#else
|
||||
auto s = detail::duration_cast<std::chrono::seconds>(d);
|
||||
@ -1707,32 +1694,28 @@ class get_locale {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FormatContext, typename OutputIt, typename Rep,
|
||||
typename Period>
|
||||
struct chrono_formatter {
|
||||
FormatContext& context;
|
||||
OutputIt out;
|
||||
int precision;
|
||||
bool localized = false;
|
||||
template <typename Char, typename Rep, typename Period>
|
||||
struct duration_formatter {
|
||||
using iterator = basic_appender<Char>;
|
||||
iterator out;
|
||||
// rep is unsigned to avoid overflow.
|
||||
using rep =
|
||||
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
|
||||
unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
|
||||
rep val;
|
||||
int precision;
|
||||
locale_ref locale;
|
||||
bool localized = false;
|
||||
using seconds = std::chrono::duration<rep>;
|
||||
seconds s;
|
||||
using milliseconds = std::chrono::duration<rep, std::milli>;
|
||||
bool negative;
|
||||
|
||||
using char_type = typename FormatContext::char_type;
|
||||
using tm_writer_type = tm_writer<OutputIt, char_type>;
|
||||
using tm_writer_type = tm_writer<iterator, Char>;
|
||||
|
||||
chrono_formatter(FormatContext& ctx, OutputIt o,
|
||||
std::chrono::duration<Rep, Period> d)
|
||||
: context(ctx),
|
||||
out(o),
|
||||
val(static_cast<rep>(d.count())),
|
||||
negative(false) {
|
||||
duration_formatter(iterator o, std::chrono::duration<Rep, Period> d,
|
||||
locale_ref loc)
|
||||
: out(o), val(static_cast<rep>(d.count())), locale(loc), negative(false) {
|
||||
if (d.count() < 0) {
|
||||
val = 0 - val;
|
||||
negative = true;
|
||||
@ -1746,19 +1729,16 @@ struct chrono_formatter {
|
||||
|
||||
// returns true if nan or inf, writes to out.
|
||||
auto handle_nan_inf() -> bool {
|
||||
if (isfinite(val)) {
|
||||
return false;
|
||||
}
|
||||
if (isfinite(val)) return false;
|
||||
if (isnan(val)) {
|
||||
write_nan();
|
||||
return true;
|
||||
}
|
||||
// must be +-inf
|
||||
if (val > 0) {
|
||||
write_pinf();
|
||||
} else {
|
||||
write_ninf();
|
||||
}
|
||||
if (val > 0)
|
||||
std::copy_n("inf", 3, out);
|
||||
else
|
||||
std::copy_n("-inf", 4, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1786,10 +1766,9 @@ struct chrono_formatter {
|
||||
}
|
||||
|
||||
void write_sign() {
|
||||
if (negative) {
|
||||
*out++ = '-';
|
||||
negative = false;
|
||||
}
|
||||
if (!negative) return;
|
||||
*out++ = '-';
|
||||
negative = false;
|
||||
}
|
||||
|
||||
void write(Rep value, int width, pad_type pad = pad_type::zero) {
|
||||
@ -1801,24 +1780,22 @@ struct chrono_formatter {
|
||||
if (width > num_digits) {
|
||||
out = detail::write_padding(out, pad, width - num_digits);
|
||||
}
|
||||
out = format_decimal<char_type>(out, n, num_digits);
|
||||
out = format_decimal<Char>(out, n, num_digits);
|
||||
}
|
||||
|
||||
void write_nan() { std::copy_n("nan", 3, out); }
|
||||
void write_pinf() { std::copy_n("inf", 3, out); }
|
||||
void write_ninf() { std::copy_n("-inf", 4, out); }
|
||||
|
||||
template <typename Callback, typename... Args>
|
||||
void format_tm(const tm& time, Callback cb, Args... args) {
|
||||
if (isnan(val)) return write_nan();
|
||||
get_locale loc(localized, context.locale());
|
||||
get_locale loc(localized, locale);
|
||||
auto w = tm_writer_type(loc, out, time);
|
||||
(w.*cb)(args...);
|
||||
out = w.out();
|
||||
}
|
||||
|
||||
void on_text(const char_type* begin, const char_type* end) {
|
||||
copy<char_type>(begin, end, out);
|
||||
void on_text(const Char* begin, const Char* end) {
|
||||
copy<Char>(begin, end, out);
|
||||
}
|
||||
|
||||
// These are not implemented because durations don't have date information.
|
||||
@ -1888,13 +1865,12 @@ struct chrono_formatter {
|
||||
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
|
||||
precision);
|
||||
if (negative) *out++ = '-';
|
||||
if (buf.size() < 2 || buf[1] == '.') {
|
||||
if (buf.size() < 2 || buf[1] == '.')
|
||||
out = detail::write_padding(out, pad);
|
||||
}
|
||||
out = copy<char_type>(buf.begin(), buf.end(), out);
|
||||
out = copy<Char>(buf.begin(), buf.end(), out);
|
||||
} else {
|
||||
write(second(), 2, pad);
|
||||
write_fractional_seconds<char_type>(
|
||||
write_fractional_seconds<Char>(
|
||||
out, std::chrono::duration<rep, Period>(val), precision);
|
||||
}
|
||||
return;
|
||||
@ -1936,12 +1912,10 @@ struct chrono_formatter {
|
||||
void on_duration_value() {
|
||||
if (handle_nan_inf()) return;
|
||||
write_sign();
|
||||
out = format_duration_value<char_type>(out, val, precision);
|
||||
out = format_duration_value<Char>(out, val, precision);
|
||||
}
|
||||
|
||||
void on_duration_unit() {
|
||||
out = format_duration_unit<char_type, Period>(out);
|
||||
}
|
||||
void on_duration_unit() { out = format_duration_unit<Char, Period>(out); }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
@ -2011,12 +1985,11 @@ class year_month_day {
|
||||
constexpr auto month() const noexcept -> fmt::month { return month_; }
|
||||
constexpr auto day() const noexcept -> fmt::day { return day_; }
|
||||
};
|
||||
#endif
|
||||
#endif // __cpp_lib_chrono >= 201907
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<weekday, Char> : private formatter<std::tm, Char> {
|
||||
private:
|
||||
bool localized_ = false;
|
||||
bool use_tm_formatter_ = false;
|
||||
|
||||
public:
|
||||
@ -2024,8 +1997,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it != end && *it == 'L') {
|
||||
++it;
|
||||
localized_ = true;
|
||||
return it;
|
||||
this->set_localized();
|
||||
}
|
||||
use_tm_formatter_ = it != end && *it != '}';
|
||||
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
|
||||
@ -2036,7 +2008,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
|
||||
auto time = std::tm();
|
||||
time.tm_wday = static_cast<int>(wd.c_encoding());
|
||||
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
|
||||
detail::get_locale loc(localized_, ctx.locale());
|
||||
detail::get_locale loc(this->localized(), ctx.locale());
|
||||
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
|
||||
w.on_abbr_weekday();
|
||||
return w.out();
|
||||
@ -2070,7 +2042,6 @@ struct formatter<day, Char> : private formatter<std::tm, Char> {
|
||||
template <typename Char>
|
||||
struct formatter<month, Char> : private formatter<std::tm, Char> {
|
||||
private:
|
||||
bool localized_ = false;
|
||||
bool use_tm_formatter_ = false;
|
||||
|
||||
public:
|
||||
@ -2078,8 +2049,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it != end && *it == 'L') {
|
||||
++it;
|
||||
localized_ = true;
|
||||
return it;
|
||||
this->set_localized();
|
||||
}
|
||||
use_tm_formatter_ = it != end && *it != '}';
|
||||
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
|
||||
@ -2090,7 +2060,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
|
||||
auto time = std::tm();
|
||||
time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1;
|
||||
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
|
||||
detail::get_locale loc(localized_, ctx.locale());
|
||||
detail::get_locale loc(this->localized(), ctx.locale());
|
||||
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
|
||||
w.on_abbr_month();
|
||||
return w.out();
|
||||
@ -2154,7 +2124,6 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
format_specs specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
detail::arg_ref<Char> precision_ref_;
|
||||
bool localized_ = false;
|
||||
basic_string_view<Char> fmt_;
|
||||
|
||||
public:
|
||||
@ -2177,7 +2146,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
it = detail::parse_precision(it, end, specs_, precision_ref_, ctx);
|
||||
}
|
||||
if (it != end && *it == 'L') {
|
||||
localized_ = true;
|
||||
specs_.set_localized();
|
||||
++it;
|
||||
}
|
||||
end = detail::parse_chrono_format(it, end, checker);
|
||||
@ -2204,11 +2173,10 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
out = detail::format_duration_value<Char>(out, d.count(), precision);
|
||||
detail::format_duration_unit<Char, Period>(out);
|
||||
} else {
|
||||
using chrono_formatter =
|
||||
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>;
|
||||
auto f = chrono_formatter(ctx, out, d);
|
||||
auto f =
|
||||
detail::duration_formatter<Char, Rep, Period>(out, d, ctx.locale());
|
||||
f.precision = precision;
|
||||
f.localized = localized_;
|
||||
f.localized = specs_.localized();
|
||||
detail::parse_chrono_format(begin, end, f);
|
||||
}
|
||||
return detail::write(
|
||||
@ -2220,30 +2188,15 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
basic_string_view<Char> fmt_ =
|
||||
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
|
||||
|
||||
protected:
|
||||
basic_string_view<Char> fmt_;
|
||||
auto localized() const -> bool { return specs_.localized(); }
|
||||
FMT_CONSTEXPR void set_localized() { specs_.set_localized(); }
|
||||
|
||||
template <typename Duration, typename FormatContext>
|
||||
auto do_format(const std::tm& tm, FormatContext& ctx,
|
||||
const Duration* subsecs) const -> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
auto out = basic_appender<Char>(buf);
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
|
||||
auto loc_ref = ctx.locale();
|
||||
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
|
||||
auto w =
|
||||
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
|
||||
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
|
||||
return detail::write(
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, bool has_timezone)
|
||||
-> const Char* {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
|
||||
@ -2256,12 +2209,41 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
if (it == end) return it;
|
||||
}
|
||||
|
||||
end = detail::parse_chrono_format(it, end, detail::tm_format_checker());
|
||||
if (*it == 'L') {
|
||||
specs_.set_localized();
|
||||
++it;
|
||||
}
|
||||
|
||||
end = detail::parse_chrono_format(it, end,
|
||||
detail::tm_format_checker(has_timezone));
|
||||
// Replace the default format string only if the new spec is not empty.
|
||||
if (end != it) fmt_ = {it, detail::to_unsigned(end - it)};
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename Duration, typename FormatContext>
|
||||
auto do_format(const std::tm& tm, FormatContext& ctx,
|
||||
const Duration* subsecs) const -> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
auto out = basic_appender<Char>(buf);
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
|
||||
auto loc_ref = specs.localized() ? ctx.locale() : detail::locale_ref();
|
||||
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
|
||||
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
|
||||
loc, out, tm, subsecs);
|
||||
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
|
||||
return detail::write(
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return do_parse(ctx, detail::has_tm_gmtoff<std::tm>::value);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm& tm, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
@ -2269,10 +2251,11 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
// DEPRECATED! Reversed order of template parameters.
|
||||
template <typename Char, typename Duration>
|
||||
struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR formatter() {
|
||||
this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
|
||||
struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return this->do_parse(ctx, true);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
@ -2283,6 +2266,7 @@ struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
if (detail::const_check(
|
||||
period::num == 1 && period::den == 1 &&
|
||||
!std::is_floating_point<typename Duration::rep>::value)) {
|
||||
detail::set_tm_zone(tm, detail::utc());
|
||||
return formatter<std::tm, Char>::format(tm, ctx);
|
||||
}
|
||||
Duration epoch = val.time_since_epoch();
|
||||
@ -2290,11 +2274,13 @@ struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
epoch - detail::duration_cast<std::chrono::seconds>(epoch));
|
||||
if (subsecs.count() < 0) {
|
||||
auto second = detail::duration_cast<Duration>(std::chrono::seconds(1));
|
||||
if (tm.tm_sec != 0)
|
||||
if (tm.tm_sec != 0) {
|
||||
--tm.tm_sec;
|
||||
else
|
||||
} else {
|
||||
tm = gmtime(val - second);
|
||||
subsecs += detail::duration_cast<Duration>(std::chrono::seconds(1));
|
||||
detail::set_tm_zone(tm, detail::utc());
|
||||
}
|
||||
subsecs += second;
|
||||
}
|
||||
return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs);
|
||||
}
|
||||
@ -2312,23 +2298,29 @@ struct formatter<utc_time<Duration>, Char>
|
||||
};
|
||||
|
||||
template <typename Duration, typename Char>
|
||||
struct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR formatter() {
|
||||
this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
|
||||
struct formatter<local_time<Duration>, Char>
|
||||
: private formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return this->do_parse(ctx, false);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(local_time<Duration> val, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto time_since_epoch = val.time_since_epoch();
|
||||
auto seconds_since_epoch =
|
||||
detail::duration_cast<std::chrono::seconds>(time_since_epoch);
|
||||
// Use gmtime to prevent time zone conversion since local_time has an
|
||||
// unspecified time zone.
|
||||
std::tm t = gmtime(seconds_since_epoch.count());
|
||||
using period = typename Duration::period;
|
||||
if (period::num == 1 && period::den == 1 &&
|
||||
!std::is_floating_point<typename Duration::rep>::value) {
|
||||
return formatter<std::tm, Char>::format(localtime(val), ctx);
|
||||
return formatter<std::tm, Char>::format(t, ctx);
|
||||
}
|
||||
auto epoch = val.time_since_epoch();
|
||||
auto subsecs = detail::duration_cast<Duration>(
|
||||
epoch - detail::duration_cast<std::chrono::seconds>(epoch));
|
||||
return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);
|
||||
auto subsecs =
|
||||
detail::duration_cast<Duration>(time_since_epoch - seconds_since_epoch);
|
||||
return formatter<std::tm, Char>::do_format(t, ctx, &subsecs);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -190,11 +190,11 @@ enum class emphasis : uint8_t {
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
||||
constexpr rgb() : r(0), g(0), b(0) {}
|
||||
constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
constexpr rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||
FMT_CONSTEXPR rgb(color hex)
|
||||
constexpr rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||
g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
@ -205,97 +205,135 @@ struct rgb {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
// A bit-packed variant of an RGB color, a terminal color, or unset color.
|
||||
// see text_style for the bit-packing scheme.
|
||||
struct color_type {
|
||||
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
|
||||
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
|
||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
||||
constexpr color_type() noexcept = default;
|
||||
constexpr color_type(color rgb_color) noexcept
|
||||
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
|
||||
constexpr color_type(rgb rgb_color) noexcept
|
||||
: color_type(static_cast<color>(
|
||||
(static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
|
||||
constexpr color_type(terminal_color term_color) noexcept
|
||||
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
|
||||
|
||||
constexpr auto is_terminal_color() const noexcept -> bool {
|
||||
return (value_ & (1 << 25)) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
|
||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
||||
|
||||
constexpr auto value() const noexcept -> uint32_t {
|
||||
return value_ & 0xFFFFFF;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
|
||||
: is_rgb(), value{} {
|
||||
value.term_color = static_cast<uint8_t>(term_color);
|
||||
}
|
||||
bool is_rgb;
|
||||
union color_union {
|
||||
uint8_t term_color;
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
|
||||
constexpr color_type(uint32_t value) noexcept : value_(value) {}
|
||||
|
||||
uint32_t value_ = 0;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// A text style consisting of foreground and background colors and emphasis.
|
||||
class text_style {
|
||||
// The information is packed as follows:
|
||||
// ┌──┐
|
||||
// │ 0│─┐
|
||||
// │..│ ├── foreground color value
|
||||
// │23│─┘
|
||||
// ├──┤
|
||||
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
|
||||
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
|
||||
// ├──┤
|
||||
// │26│──── overflow bit, always zero (see below)
|
||||
// ├──┤
|
||||
// │27│─┐
|
||||
// │..│ │
|
||||
// │50│ │
|
||||
// ├──┤ │
|
||||
// │51│ ├── background color (same format as the foreground color)
|
||||
// │52│ │
|
||||
// ├──┤ │
|
||||
// │53│─┘
|
||||
// ├──┤
|
||||
// │54│─┐
|
||||
// │..│ ├── emphases
|
||||
// │61│─┘
|
||||
// ├──┤
|
||||
// │62│─┬── unused
|
||||
// │63│─┘
|
||||
// └──┘
|
||||
// The overflow bits are there to make operator|= efficient.
|
||||
// When ORing, we must throw if, for either the foreground or background,
|
||||
// one style specifies a terminal color and the other specifies any color
|
||||
// (terminal or RGB); in other words, if one discriminator is 11 and the
|
||||
// other is 11 or 01.
|
||||
//
|
||||
// We do that check by adding the styles. Consider what adding does to each
|
||||
// possible pair of discriminators:
|
||||
// 00 + 00 = 000
|
||||
// 01 + 00 = 001
|
||||
// 11 + 00 = 011
|
||||
// 01 + 01 = 010
|
||||
// 11 + 01 = 100 (!!)
|
||||
// 11 + 11 = 110 (!!)
|
||||
// In the last two cases, the ones we want to catch, the third bit——the
|
||||
// overflow bit——is set. Bingo.
|
||||
//
|
||||
// We must take into account the possible carry bit from the bits
|
||||
// before the discriminator. The only potentially problematic case is
|
||||
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
|
||||
// bit is impossible in that case, because 00 (unset color) means the
|
||||
// 24 bits that precede the discriminator are all zero.
|
||||
//
|
||||
// This test can be applied to both colors simultaneously.
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
|
||||
: set_foreground_color(), set_background_color(), ems(em) {}
|
||||
: style_(static_cast<uint64_t>(em) << 54) {}
|
||||
|
||||
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
report_error("can't OR a terminal color");
|
||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
report_error("can't OR a terminal color");
|
||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
|
||||
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
|
||||
report_error("can't OR a terminal color");
|
||||
style_ |= rhs.style_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
|
||||
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
|
||||
-> text_style {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
|
||||
return style_ == rhs.style_;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
|
||||
return set_foreground_color;
|
||||
return (style_ & (1 << 24)) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
|
||||
return set_background_color;
|
||||
return (style_ & (1ULL << 51)) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
return (style_ >> 54) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
return style_ & 0x3FFFFFF;
|
||||
}
|
||||
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
return (style_ >> 27) & 0x3FFFFFF;
|
||||
}
|
||||
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
return static_cast<emphasis>(style_ >> 54);
|
||||
}
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
detail::color_type text_color) noexcept
|
||||
: set_foreground_color(), set_background_color(), ems() {
|
||||
if (is_foreground) {
|
||||
foreground_color = text_color;
|
||||
set_foreground_color = true;
|
||||
} else {
|
||||
background_color = text_color;
|
||||
set_background_color = true;
|
||||
}
|
||||
}
|
||||
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
|
||||
|
||||
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style;
|
||||
@ -303,23 +341,19 @@ class text_style {
|
||||
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
|
||||
-> text_style;
|
||||
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
uint64_t style_ = 0;
|
||||
};
|
||||
|
||||
/// Creates a text style from the foreground (text) color.
|
||||
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style {
|
||||
return text_style(true, foreground);
|
||||
return foreground.value_;
|
||||
}
|
||||
|
||||
/// Creates a text style from the background color.
|
||||
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
|
||||
-> text_style {
|
||||
return text_style(false, background);
|
||||
return static_cast<uint64_t>(background.value_) << 27;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
|
||||
@ -334,9 +368,9 @@ template <typename Char> struct ansi_color_escape {
|
||||
const char* esc) noexcept {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
if (text_color.is_terminal_color()) {
|
||||
bool is_background = esc == string_view("\x1b[48;2;");
|
||||
uint32_t value = text_color.value.term_color;
|
||||
uint32_t value = text_color.value();
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
@ -360,7 +394,7 @@ template <typename Char> struct ansi_color_escape {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
buffer[i] = static_cast<Char>(esc[i]);
|
||||
}
|
||||
rgb color(text_color.value.rgb_color);
|
||||
rgb color(text_color.value());
|
||||
to_esc(color.r, buffer + 7, ';');
|
||||
to_esc(color.g, buffer + 11, ';');
|
||||
to_esc(color.b, buffer + 15, 'm');
|
||||
@ -441,32 +475,26 @@ template <typename T> struct styled_arg : view {
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> fmt,
|
||||
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
|
||||
basic_format_args<buffered_context<Char>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground = make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background = make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
vformat_to(buf, fmt, args);
|
||||
if (has_style) reset_color<Char>(buf);
|
||||
if (ts != text_style()) reset_color<Char>(buf);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
|
||||
format_args args) {
|
||||
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
|
||||
auto buf = memory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
|
||||
@ -482,8 +510,7 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt,
|
||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... T>
|
||||
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
|
||||
T&&... args) {
|
||||
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
|
||||
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
|
||||
}
|
||||
|
||||
@ -497,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string<T...> fmt,
|
||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename... T>
|
||||
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
|
||||
void print(text_style ts, format_string<T...> fmt, T&&... args) {
|
||||
return print(stdout, ts, fmt, std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
|
||||
inline auto vformat(text_style ts, string_view fmt, format_args args)
|
||||
-> std::string {
|
||||
auto buf = memory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
@ -521,7 +548,7 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args)
|
||||
* ```
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
|
||||
inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
|
||||
-> std::string {
|
||||
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
|
||||
}
|
||||
@ -529,8 +556,8 @@ inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
|
||||
/// Formats a string with the given text_style and writes the output to `out`.
|
||||
template <typename OutputIt,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
|
||||
format_args args) -> OutputIt {
|
||||
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<char>(out);
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
return detail::get_iterator(buf, out);
|
||||
@ -548,8 +575,8 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
|
||||
*/
|
||||
template <typename OutputIt, typename... T,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||
inline auto format_to(OutputIt out, const text_style& ts,
|
||||
format_string<T...> fmt, T&&... args) -> OutputIt {
|
||||
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
|
||||
T&&... args) -> OutputIt {
|
||||
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ namespace detail {
|
||||
#endif
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
auto first(const T& value, const Tail&...) -> const T& {
|
||||
constexpr auto first(const T& value, const Tail&...) -> const T& {
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -436,8 +436,8 @@ FMT_BEGIN_EXPORT
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
FMT_INLINE FMT_CONSTEXPR_STRING std::basic_string<Char> format(
|
||||
const CompiledFormat& cf, const Args&... args) {
|
||||
auto s = std::basic_string<Char>();
|
||||
cf.format(std::back_inserter(s), args...);
|
||||
return s;
|
||||
@ -452,8 +452,8 @@ constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
FMT_INLINE FMT_CONSTEXPR_STRING std::basic_string<typename S::char_type> format(
|
||||
const S&, Args&&... args) {
|
||||
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
||||
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#if FMT_USE_LOCALE
|
||||
#if FMT_USE_LOCALE && !defined(FMT_MODULE)
|
||||
# include <locale>
|
||||
#endif
|
||||
|
||||
@ -212,7 +212,7 @@ inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {
|
||||
return (e * 631305 - 261663) >> 21;
|
||||
}
|
||||
|
||||
FMT_INLINE_VARIABLE constexpr struct {
|
||||
FMT_INLINE_VARIABLE constexpr struct div_small_pow10_infos_struct {
|
||||
uint32_t divisor;
|
||||
int shift_amount;
|
||||
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
|
||||
@ -275,7 +275,7 @@ template <> struct cache_accessor<float> {
|
||||
static auto get_cached_power(int k) noexcept -> uint64_t {
|
||||
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
|
||||
"k is out of range");
|
||||
static constexpr const uint64_t pow10_significands[] = {
|
||||
static constexpr uint64_t pow10_significands[] = {
|
||||
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
|
||||
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
|
||||
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
|
||||
@ -370,7 +370,7 @@ template <> struct cache_accessor<double> {
|
||||
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
|
||||
"k is out of range");
|
||||
|
||||
static constexpr const uint128_fallback pow10_significands[] = {
|
||||
static constexpr uint128_fallback pow10_significands[] = {
|
||||
#if FMT_USE_FULL_CACHE_DRAGONBOX
|
||||
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
|
||||
{0x9faacf3df73609b1, 0x77b191618c54e9ad},
|
||||
@ -1037,7 +1037,7 @@ template <> struct cache_accessor<double> {
|
||||
#if FMT_USE_FULL_CACHE_DRAGONBOX
|
||||
return pow10_significands[k - float_info<double>::min_k];
|
||||
#else
|
||||
static constexpr const uint64_t powers_of_5_64[] = {
|
||||
static constexpr uint64_t powers_of_5_64[] = {
|
||||
0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
|
||||
0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
|
||||
0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
|
||||
@ -1097,7 +1097,7 @@ template <> struct cache_accessor<double> {
|
||||
return {r.high(), r.low() == 0};
|
||||
}
|
||||
|
||||
static auto compute_delta(cache_entry_type const& cache, int beta) noexcept
|
||||
static auto compute_delta(const cache_entry_type& cache, int beta) noexcept
|
||||
-> uint32_t {
|
||||
return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
|
||||
}
|
||||
@ -1526,9 +1526,8 @@ template <typename F> class glibc_file : public file_base<F> {
|
||||
}
|
||||
|
||||
void init_buffer() {
|
||||
if (this->file_->_IO_write_ptr) return;
|
||||
if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return;
|
||||
// Force buffer initialization by placing and removing a char in a buffer.
|
||||
assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end);
|
||||
putc_unlocked(0, this->file_);
|
||||
--this->file_->_IO_write_ptr;
|
||||
}
|
||||
|
@ -44,6 +44,7 @@
|
||||
# include <cmath> // std::signbit
|
||||
# include <cstddef> // std::byte
|
||||
# include <cstdint> // uint32_t
|
||||
# include <cstdlib> // std::malloc, std::free
|
||||
# include <cstring> // std::memcpy
|
||||
# include <limits> // std::numeric_limits
|
||||
# include <new> // std::bad_alloc
|
||||
@ -117,6 +118,34 @@
|
||||
# define FMT_NOINLINE
|
||||
#endif
|
||||
|
||||
// Detect constexpr std::string.
|
||||
#if !FMT_USE_CONSTEVAL
|
||||
# define FMT_USE_CONSTEXPR_STRING 0
|
||||
#elif defined(__cpp_lib_constexpr_string) && \
|
||||
__cpp_lib_constexpr_string >= 201907L
|
||||
# if FMT_CLANG_VERSION && FMT_GLIBCXX_RELEASE
|
||||
// clang + libstdc++ are able to work only starting with gcc13.3
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113294
|
||||
# if FMT_GLIBCXX_RELEASE < 13
|
||||
# define FMT_USE_CONSTEXPR_STRING 0
|
||||
# elif FMT_GLIBCXX_RELEASE == 13 && __GLIBCXX__ < 20240521
|
||||
# define FMT_USE_CONSTEXPR_STRING 0
|
||||
# else
|
||||
# define FMT_USE_CONSTEXPR_STRING 1
|
||||
# endif
|
||||
# else
|
||||
# define FMT_USE_CONSTEXPR_STRING 1
|
||||
# endif
|
||||
#else
|
||||
# define FMT_USE_CONSTEXPR_STRING 0
|
||||
#endif
|
||||
#if FMT_USE_CONSTEXPR_STRING
|
||||
# define FMT_CONSTEXPR_STRING constexpr
|
||||
#else
|
||||
# define FMT_CONSTEXPR_STRING
|
||||
#endif
|
||||
|
||||
// GCC 4.9 doesn't support qualified names in specializations.
|
||||
namespace std {
|
||||
template <typename T> struct iterator_traits<fmt::basic_appender<T>> {
|
||||
using iterator_category = output_iterator_tag;
|
||||
@ -525,6 +554,8 @@ FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value)
|
||||
template <typename T, typename Size>
|
||||
FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* {
|
||||
if (is_constant_evaluated()) return fill_n<T*, Size, T>(out, count, value);
|
||||
static_assert(sizeof(T) == 1,
|
||||
"sizeof(T) must be 1 to use char for initialization");
|
||||
std::memset(out, value, to_unsigned(count));
|
||||
return out + count;
|
||||
}
|
||||
@ -554,10 +585,10 @@ FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end,
|
||||
*/
|
||||
FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e)
|
||||
-> const char* {
|
||||
constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
|
||||
constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
|
||||
constexpr const int shiftc[] = {0, 18, 12, 6, 0};
|
||||
constexpr const int shifte[] = {0, 6, 4, 2, 0};
|
||||
constexpr int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
|
||||
constexpr uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
|
||||
constexpr int shiftc[] = {0, 18, 12, 6, 0};
|
||||
constexpr int shifte[] = {0, 6, 4, 2, 0};
|
||||
|
||||
int len = "\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>(*s) >> 3];
|
||||
@ -668,26 +699,6 @@ FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t {
|
||||
return num_code_points;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline auto code_point_index(basic_string_view<Char> s, size_t n) -> size_t {
|
||||
return min_of(n, s.size());
|
||||
}
|
||||
|
||||
// Calculates the index of the nth code point in a UTF-8 string.
|
||||
inline auto code_point_index(string_view s, size_t n) -> size_t {
|
||||
size_t result = s.size();
|
||||
const char* begin = s.begin();
|
||||
for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) {
|
||||
if (n != 0) {
|
||||
--n;
|
||||
return true;
|
||||
}
|
||||
result = to_unsigned(sv.begin() - begin);
|
||||
return false;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T> struct is_integral : std::is_integral<T> {};
|
||||
template <> struct is_integral<int128_opt> : std::true_type {};
|
||||
template <> struct is_integral<uint128_t> : std::true_type {};
|
||||
@ -705,7 +716,7 @@ using is_integer =
|
||||
|
||||
#if defined(FMT_USE_FLOAT128)
|
||||
// Use the provided definition.
|
||||
#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE(<quadmath.h>)
|
||||
#elif FMT_CLANG_VERSION >= 309 && FMT_HAS_INCLUDE(<quadmath.h>)
|
||||
# define FMT_USE_FLOAT128 1
|
||||
#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \
|
||||
!defined(__STRICT_ANSI__)
|
||||
@ -721,15 +732,17 @@ struct float128 {};
|
||||
|
||||
template <typename T> using is_float128 = std::is_same<T, float128>;
|
||||
|
||||
template <typename T>
|
||||
using is_floating_point =
|
||||
bool_constant<std::is_floating_point<T>::value || is_float128<T>::value>;
|
||||
template <typename T> struct is_floating_point : std::is_floating_point<T> {};
|
||||
template <> struct is_floating_point<float128> : std::true_type {};
|
||||
|
||||
template <typename T, bool = std::is_floating_point<T>::value>
|
||||
template <typename T, bool = is_floating_point<T>::value>
|
||||
struct is_fast_float : bool_constant<std::numeric_limits<T>::is_iec559 &&
|
||||
sizeof(T) <= sizeof(double)> {};
|
||||
template <typename T> struct is_fast_float<T, false> : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
using fast_float_t = conditional_t<sizeof(T) == sizeof(double), double, float>;
|
||||
|
||||
template <typename T>
|
||||
using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;
|
||||
|
||||
@ -738,18 +751,19 @@ using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;
|
||||
#endif
|
||||
|
||||
// An allocator that uses malloc/free to allow removing dependency on the C++
|
||||
// standard libary runtime.
|
||||
template <typename T> struct allocator {
|
||||
// standard libary runtime. std::decay is used for back_inserter to be found by
|
||||
// ADL when applied to memory_buffer.
|
||||
template <typename T> struct allocator : private std::decay<void> {
|
||||
using value_type = T;
|
||||
|
||||
T* allocate(size_t n) {
|
||||
FMT_ASSERT(n <= max_value<size_t>() / sizeof(T), "");
|
||||
T* p = static_cast<T*>(malloc(n * sizeof(T)));
|
||||
T* p = static_cast<T*>(std::malloc(n * sizeof(T)));
|
||||
if (!p) FMT_THROW(std::bad_alloc());
|
||||
return p;
|
||||
}
|
||||
|
||||
void deallocate(T* p, size_t) { free(p); }
|
||||
void deallocate(T* p, size_t) { std::free(p); }
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
@ -1044,7 +1058,7 @@ inline auto do_count_digits(uint64_t n) -> int {
|
||||
10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
|
||||
15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
|
||||
auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63];
|
||||
static constexpr const uint64_t zero_or_powers_of_10[] = {
|
||||
static constexpr uint64_t zero_or_powers_of_10[] = {
|
||||
0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
|
||||
10000000000000000000ULL};
|
||||
return t - (n < zero_or_powers_of_10[t]);
|
||||
@ -1225,7 +1239,7 @@ FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value,
|
||||
out += size;
|
||||
do {
|
||||
const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef";
|
||||
unsigned digit = static_cast<unsigned>(value & ((1 << base_bits) - 1));
|
||||
unsigned digit = static_cast<unsigned>(value & ((1u << base_bits) - 1));
|
||||
*--out = static_cast<Char>(base_bits < 4 ? static_cast<char>('0' + digit)
|
||||
: digits[digit]);
|
||||
} while ((value >>= base_bits) != 0);
|
||||
@ -1487,6 +1501,13 @@ template <typename Float> constexpr auto exponent_bias() -> int {
|
||||
: std::numeric_limits<Float>::max_exponent - 1;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline auto compute_exp_size(int exp) -> int {
|
||||
auto prefix_size = 2; // sign + 'e'
|
||||
auto abs_exp = exp >= 0 ? exp : -exp;
|
||||
if (exp < 100) return prefix_size + 2;
|
||||
return prefix_size + (abs_exp >= 1000 ? 4 : 3);
|
||||
}
|
||||
|
||||
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
|
||||
template <typename Char, typename OutputIt>
|
||||
FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt {
|
||||
@ -1519,7 +1540,7 @@ template <typename F> struct basic_fp {
|
||||
F f;
|
||||
int e;
|
||||
|
||||
static constexpr const int num_significand_bits =
|
||||
static constexpr int num_significand_bits =
|
||||
static_cast<int>(sizeof(F) * num_bits<unsigned char>());
|
||||
|
||||
constexpr basic_fp() : f(0), e(0) {}
|
||||
@ -1613,7 +1634,7 @@ constexpr auto convert_float(T value) -> convert_float_result<T> {
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n,
|
||||
FMT_CONSTEXPR FMT_NOINLINE auto fill(OutputIt it, size_t n,
|
||||
const basic_specs& specs) -> OutputIt {
|
||||
auto fill_size = specs.fill_size();
|
||||
if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit<Char>());
|
||||
@ -1964,8 +1985,7 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign s)
|
||||
prefix = 0x01000000 | '-';
|
||||
abs_value = 0 - abs_value;
|
||||
} else {
|
||||
constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
|
||||
0x1000000u | ' '};
|
||||
constexpr unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '};
|
||||
prefix = prefixes[static_cast<int>(s)];
|
||||
}
|
||||
return {abs_value, prefix};
|
||||
@ -2018,7 +2038,7 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
|
||||
const format_specs& specs) -> OutputIt {
|
||||
static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::value, "");
|
||||
|
||||
constexpr int buffer_size = num_bits<T>();
|
||||
constexpr size_t buffer_size = num_bits<T>();
|
||||
char buffer[buffer_size];
|
||||
if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0');
|
||||
const char* begin = nullptr;
|
||||
@ -2110,13 +2130,35 @@ FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
|
||||
return write_int<Char>(out, make_write_int_arg(value, specs.sign()), specs);
|
||||
}
|
||||
|
||||
inline auto convert_precision_to_size(string_view s, size_t precision)
|
||||
-> size_t {
|
||||
size_t display_width = 0;
|
||||
size_t result = s.size();
|
||||
for_each_codepoint(s, [&](uint32_t, string_view sv) {
|
||||
display_width += compute_width(sv);
|
||||
// Stop when display width exceeds precision.
|
||||
if (display_width > precision) {
|
||||
result = to_unsigned(sv.begin() - s.begin());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto convert_precision_to_size(basic_string_view<Char>, size_t precision)
|
||||
-> size_t {
|
||||
return precision;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
|
||||
const format_specs& specs) -> OutputIt {
|
||||
auto data = s.data();
|
||||
auto size = s.size();
|
||||
if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
|
||||
size = code_point_index(s, to_unsigned(specs.precision));
|
||||
size = convert_precision_to_size(s, to_unsigned(specs.precision));
|
||||
|
||||
bool is_debug = specs.type() == presentation_type::debug;
|
||||
if (is_debug) {
|
||||
@ -2274,7 +2316,7 @@ inline auto write_significand(Char* out, UInt significand, int significand_size,
|
||||
int floating_size = significand_size - integral_size;
|
||||
for (int i = floating_size / 2; i > 0; --i) {
|
||||
out -= 2;
|
||||
write2digits(out, static_cast<std::size_t>(significand % 100));
|
||||
write2digits(out, static_cast<size_t>(significand % 100));
|
||||
significand /= 100;
|
||||
}
|
||||
if (floating_size % 2 != 0) {
|
||||
@ -2328,110 +2370,18 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
|
||||
buffer.end(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename DecimalFP,
|
||||
typename Grouping = digit_grouping<Char>>
|
||||
FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
|
||||
const format_specs& specs, sign s,
|
||||
int exp_upper, locale_ref loc) -> OutputIt {
|
||||
auto significand = f.significand;
|
||||
int significand_size = get_significand_size(f);
|
||||
const Char zero = static_cast<Char>('0');
|
||||
size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0);
|
||||
using iterator = reserve_iterator<OutputIt>;
|
||||
// Numbers with exponents greater or equal to the returned value will use
|
||||
// the exponential notation.
|
||||
template <typename T> FMT_CONSTEVAL auto exp_upper() -> int {
|
||||
return std::numeric_limits<T>::digits10 != 0
|
||||
? min_of(16, std::numeric_limits<T>::digits10 + 1)
|
||||
: 16;
|
||||
}
|
||||
|
||||
Char decimal_point = specs.localized() ? detail::decimal_point<Char>(loc)
|
||||
: static_cast<Char>('.');
|
||||
|
||||
int output_exp = f.exponent + significand_size - 1;
|
||||
auto use_exp_format = [=]() {
|
||||
if (specs.type() == presentation_type::exp) return true;
|
||||
if (specs.type() == presentation_type::fixed) return false;
|
||||
// Use the fixed notation if the exponent is in [exp_lower, exp_upper),
|
||||
// e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation.
|
||||
const int exp_lower = -4;
|
||||
return output_exp < exp_lower ||
|
||||
output_exp >= (specs.precision > 0 ? specs.precision : exp_upper);
|
||||
};
|
||||
if (use_exp_format()) {
|
||||
int num_zeros = 0;
|
||||
if (specs.alt()) {
|
||||
num_zeros = specs.precision - significand_size;
|
||||
if (num_zeros < 0) num_zeros = 0;
|
||||
size += to_unsigned(num_zeros);
|
||||
} else if (significand_size == 1) {
|
||||
decimal_point = Char();
|
||||
}
|
||||
auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp;
|
||||
int exp_digits = 2;
|
||||
if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3;
|
||||
|
||||
size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits);
|
||||
char exp_char = specs.upper() ? 'E' : 'e';
|
||||
auto write = [=](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// Insert a decimal point after the first digit and add an exponent.
|
||||
it = write_significand(it, significand, significand_size, 1,
|
||||
decimal_point);
|
||||
if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero);
|
||||
*it++ = static_cast<Char>(exp_char);
|
||||
return write_exponent<Char>(output_exp, it);
|
||||
};
|
||||
return specs.width > 0
|
||||
? write_padded<Char, align::right>(out, specs, size, write)
|
||||
: base_iterator(out, write(reserve(out, size)));
|
||||
}
|
||||
|
||||
int exp = f.exponent + significand_size;
|
||||
if (f.exponent >= 0) {
|
||||
// 1234e5 -> 123400000[.0+]
|
||||
size += to_unsigned(f.exponent);
|
||||
int num_zeros = specs.precision - exp;
|
||||
abort_fuzzing_if(num_zeros > 5000);
|
||||
if (specs.alt()) {
|
||||
++size;
|
||||
if (num_zeros <= 0 && specs.type() != presentation_type::fixed)
|
||||
num_zeros = 0;
|
||||
if (num_zeros > 0) size += to_unsigned(num_zeros);
|
||||
}
|
||||
auto grouping = Grouping(loc, specs.localized());
|
||||
size += to_unsigned(grouping.count_separators(exp));
|
||||
return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
it = write_significand<Char>(it, significand, significand_size,
|
||||
f.exponent, grouping);
|
||||
if (!specs.alt()) return it;
|
||||
*it++ = decimal_point;
|
||||
return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
|
||||
});
|
||||
} else if (exp > 0) {
|
||||
// 1234e-2 -> 12.34[0+]
|
||||
int num_zeros = specs.alt() ? specs.precision - significand_size : 0;
|
||||
size += 1 + static_cast<unsigned>(max_of(num_zeros, 0));
|
||||
auto grouping = Grouping(loc, specs.localized());
|
||||
size += to_unsigned(grouping.count_separators(exp));
|
||||
return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
it = write_significand(it, significand, significand_size, exp,
|
||||
decimal_point, grouping);
|
||||
return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
|
||||
});
|
||||
}
|
||||
// 1234e-6 -> 0.001234
|
||||
int num_zeros = -exp;
|
||||
if (significand_size == 0 && specs.precision >= 0 &&
|
||||
specs.precision < num_zeros) {
|
||||
num_zeros = specs.precision;
|
||||
}
|
||||
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
|
||||
size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros);
|
||||
return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
*it++ = zero;
|
||||
if (!pointy) return it;
|
||||
*it++ = decimal_point;
|
||||
it = detail::fill_n(it, num_zeros, zero);
|
||||
return write_significand<Char>(it, significand, significand_size);
|
||||
});
|
||||
// Use the fixed notation if the exponent is in [-4, exp_upper),
|
||||
// e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation.
|
||||
constexpr auto use_fixed(int exp, int exp_upper) -> bool {
|
||||
return exp >= -4 && exp < exp_upper;
|
||||
}
|
||||
|
||||
template <typename Char> class fallback_digit_grouping {
|
||||
@ -2448,16 +2398,122 @@ template <typename Char> class fallback_digit_grouping {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Grouping, typename OutputIt,
|
||||
typename DecimalFP>
|
||||
FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
|
||||
int significand_size, Char decimal_point,
|
||||
const format_specs& specs, sign s,
|
||||
locale_ref loc = {}) -> OutputIt {
|
||||
using iterator = reserve_iterator<OutputIt>;
|
||||
|
||||
int exp = f.exponent + significand_size;
|
||||
long long size = significand_size + (s != sign::none ? 1 : 0);
|
||||
if (f.exponent >= 0) {
|
||||
// 1234e5 -> 123400000[.0+]
|
||||
size += f.exponent;
|
||||
int num_zeros = specs.precision - exp;
|
||||
abort_fuzzing_if(num_zeros > 5000);
|
||||
if (specs.alt()) {
|
||||
++size;
|
||||
if (num_zeros <= 0 && specs.type() != presentation_type::fixed)
|
||||
num_zeros = 0;
|
||||
if (num_zeros > 0) size += num_zeros;
|
||||
}
|
||||
auto grouping = Grouping(loc, specs.localized());
|
||||
size += grouping.count_separators(exp);
|
||||
return write_padded<Char, align::right>(
|
||||
out, specs, to_unsigned(size), [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
it = write_significand<Char>(it, f.significand, significand_size,
|
||||
f.exponent, grouping);
|
||||
if (!specs.alt()) return it;
|
||||
*it++ = decimal_point;
|
||||
return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it;
|
||||
});
|
||||
}
|
||||
if (exp > 0) {
|
||||
// 1234e-2 -> 12.34[0+]
|
||||
int num_zeros = specs.alt() ? specs.precision - significand_size : 0;
|
||||
size += 1 + max_of(num_zeros, 0);
|
||||
auto grouping = Grouping(loc, specs.localized());
|
||||
size += grouping.count_separators(exp);
|
||||
return write_padded<Char, align::right>(
|
||||
out, specs, to_unsigned(size), [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
it = write_significand(it, f.significand, significand_size, exp,
|
||||
decimal_point, grouping);
|
||||
return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it;
|
||||
});
|
||||
}
|
||||
// 1234e-6 -> 0.001234
|
||||
int num_zeros = -exp;
|
||||
if (significand_size == 0 && specs.precision >= 0 &&
|
||||
specs.precision < num_zeros) {
|
||||
num_zeros = specs.precision;
|
||||
}
|
||||
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
|
||||
size += 1 + (pointy ? 1 : 0) + num_zeros;
|
||||
return write_padded<Char, align::right>(
|
||||
out, specs, to_unsigned(size), [&](iterator it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
*it++ = Char('0');
|
||||
if (!pointy) return it;
|
||||
*it++ = decimal_point;
|
||||
it = detail::fill_n(it, num_zeros, Char('0'));
|
||||
return write_significand<Char>(it, f.significand, significand_size);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Char, typename Grouping, typename OutputIt,
|
||||
typename DecimalFP>
|
||||
FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
|
||||
const format_specs& specs, sign s,
|
||||
int exp_upper, locale_ref loc) -> OutputIt {
|
||||
Char point = specs.localized() ? detail::decimal_point<Char>(loc) : Char('.');
|
||||
int significand_size = get_significand_size(f);
|
||||
int exp = f.exponent + significand_size - 1;
|
||||
if (specs.type() == presentation_type::fixed ||
|
||||
(specs.type() != presentation_type::exp &&
|
||||
use_fixed(exp, specs.precision > 0 ? specs.precision : exp_upper))) {
|
||||
return write_fixed<Char, Grouping>(out, f, significand_size, point, specs,
|
||||
s, loc);
|
||||
}
|
||||
|
||||
// Write value in the exponential format.
|
||||
int num_zeros = 0;
|
||||
long long size = significand_size + (s != sign::none ? 1 : 0);
|
||||
if (specs.alt()) {
|
||||
num_zeros = max_of(specs.precision - significand_size, 0);
|
||||
size += num_zeros;
|
||||
} else if (significand_size == 1) {
|
||||
point = Char();
|
||||
}
|
||||
size += (point ? 1 : 0) + compute_exp_size(exp);
|
||||
char exp_char = specs.upper() ? 'E' : 'e';
|
||||
auto write = [=](reserve_iterator<OutputIt> it) {
|
||||
if (s != sign::none) *it++ = detail::getsign<Char>(s);
|
||||
// Insert a decimal point after the first digit and add an exponent.
|
||||
it = write_significand(it, f.significand, significand_size, 1, point);
|
||||
if (num_zeros > 0) it = detail::fill_n(it, num_zeros, Char('0'));
|
||||
*it++ = Char(exp_char);
|
||||
return write_exponent<Char>(exp, it);
|
||||
};
|
||||
auto usize = to_unsigned(size);
|
||||
return specs.width > 0
|
||||
? write_padded<Char, align::right>(out, specs, usize, write)
|
||||
: base_iterator(out, write(reserve(out, usize)));
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename DecimalFP>
|
||||
FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f,
|
||||
const format_specs& specs, sign s,
|
||||
int exp_upper, locale_ref loc) -> OutputIt {
|
||||
if (is_constant_evaluated()) {
|
||||
return do_write_float<Char, OutputIt, DecimalFP,
|
||||
fallback_digit_grouping<Char>>(out, f, specs, s,
|
||||
exp_upper, loc);
|
||||
return do_write_float<Char, fallback_digit_grouping<Char>>(out, f, specs, s,
|
||||
exp_upper, loc);
|
||||
} else {
|
||||
return do_write_float<Char>(out, f, specs, s, exp_upper, loc);
|
||||
return do_write_float<Char, digit_grouping<Char>>(out, f, specs, s,
|
||||
exp_upper, loc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2472,8 +2528,8 @@ template <typename T>
|
||||
struct has_isfinite<T, enable_if_t<sizeof(std::isfinite(T())) != 0>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value&&
|
||||
has_isfinite<T>::value)>
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(is_floating_point<T>::value&& has_isfinite<T>::value)>
|
||||
FMT_CONSTEXPR20 auto isfinite(T value) -> bool {
|
||||
constexpr T inf = T(std::numeric_limits<double>::infinity());
|
||||
if (is_constant_evaluated())
|
||||
@ -2728,7 +2784,7 @@ class bigint {
|
||||
bigits_.resize(to_unsigned(num_bigits + exp_difference));
|
||||
for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
|
||||
bigits_[j] = bigits_[i];
|
||||
memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit));
|
||||
fill_n(bigits_.data(), to_unsigned(exp_difference), 0U);
|
||||
exp_ -= exp_difference;
|
||||
}
|
||||
|
||||
@ -3289,17 +3345,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision,
|
||||
return exp;
|
||||
}
|
||||
|
||||
// Numbers with exponents greater or equal to the returned value will use
|
||||
// the exponential notation.
|
||||
template <typename T> constexpr auto exp_upper() -> int {
|
||||
return std::numeric_limits<T>::digits10 != 0
|
||||
? min_of(16, std::numeric_limits<T>::digits10 + 1)
|
||||
: 16;
|
||||
}
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
FMT_ENABLE_IF(is_floating_point<T>::value)>
|
||||
FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs,
|
||||
locale_ref loc = {}) -> OutputIt {
|
||||
if (specs.localized() && write_loc(out, value, specs, loc)) return out;
|
||||
|
||||
template <typename Char, typename OutputIt, typename T>
|
||||
FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
|
||||
locale_ref loc) -> OutputIt {
|
||||
// Use signbit because value < 0 is false for NaN.
|
||||
sign s = detail::signbit(value) ? sign::minus : specs.sign();
|
||||
|
||||
@ -3312,15 +3363,14 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
|
||||
if (specs.width != 0) --specs.width;
|
||||
}
|
||||
|
||||
constexpr int exp_upper = detail::exp_upper<T>();
|
||||
const int exp_upper = detail::exp_upper<T>();
|
||||
int precision = specs.precision;
|
||||
if (precision < 0) {
|
||||
if (specs.type() != presentation_type::none) {
|
||||
precision = 6;
|
||||
} else if (is_fast_float<T>::value && !is_constant_evaluated()) {
|
||||
// Use Dragonbox for the shortest format.
|
||||
using floaty = conditional_t<sizeof(T) >= sizeof(double), double, float>;
|
||||
auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
|
||||
auto dec = dragonbox::to_decimal(static_cast<fast_float_t<T>>(value));
|
||||
return write_float<Char>(out, dec, specs, s, exp_upper, loc);
|
||||
}
|
||||
}
|
||||
@ -3352,38 +3402,44 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
|
||||
return write_float<Char>(out, f, specs, s, exp_upper, loc);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
FMT_ENABLE_IF(is_floating_point<T>::value)>
|
||||
FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs,
|
||||
locale_ref loc = {}) -> OutputIt {
|
||||
return specs.localized() && write_loc(out, value, specs, loc)
|
||||
? out
|
||||
: write_float<Char>(out, value, specs, loc);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
FMT_ENABLE_IF(is_fast_float<T>::value)>
|
||||
FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt {
|
||||
if (is_constant_evaluated()) return write<Char>(out, value, format_specs());
|
||||
|
||||
auto s = detail::signbit(value) ? sign::minus : sign::none;
|
||||
auto mask = exponent_mask<fast_float_t<T>>();
|
||||
if ((bit_cast<decltype(mask)>(value) & mask) == mask)
|
||||
return write_nonfinite<Char>(out, std::isnan(value), {}, s);
|
||||
|
||||
constexpr auto specs = format_specs();
|
||||
using floaty = conditional_t<sizeof(T) >= sizeof(double), double, float>;
|
||||
using floaty_uint = typename dragonbox::float_info<floaty>::carrier_uint;
|
||||
floaty_uint mask = exponent_mask<floaty>();
|
||||
if ((bit_cast<floaty_uint>(value) & mask) == mask)
|
||||
return write_nonfinite<Char>(out, std::isnan(value), specs, s);
|
||||
auto dec = dragonbox::to_decimal(static_cast<fast_float_t<T>>(value));
|
||||
int significand_size = count_digits(dec.significand);
|
||||
int exp = dec.exponent + significand_size - 1;
|
||||
if (use_fixed(exp, detail::exp_upper<T>())) {
|
||||
return write_fixed<Char, fallback_digit_grouping<Char>>(
|
||||
out, dec, significand_size, Char('.'), {}, s);
|
||||
}
|
||||
|
||||
auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
|
||||
return write_float<Char>(out, dec, specs, s, exp_upper<T>(), {});
|
||||
// Write value in the exponential format.
|
||||
auto has_decimal_point = significand_size != 1;
|
||||
size_t size =
|
||||
to_unsigned((s != sign::none ? 1 : 0) + significand_size +
|
||||
(has_decimal_point ? 1 : 0) + compute_exp_size(exp));
|
||||
auto it = reserve(out, size);
|
||||
if (s != sign::none) *it++ = Char('-');
|
||||
// Insert a decimal point after the first digit and add an exponent.
|
||||
it = write_significand(it, dec.significand, significand_size, 1,
|
||||
has_decimal_point ? '.' : Char());
|
||||
*it++ = Char('e');
|
||||
it = write_exponent<Char>(exp, it);
|
||||
return base_iterator(out, it);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
FMT_ENABLE_IF(is_floating_point<T>::value &&
|
||||
!is_fast_float<T>::value)>
|
||||
inline auto write(OutputIt out, T value) -> OutputIt {
|
||||
return write<Char>(out, value, format_specs());
|
||||
return write<Char>(out, value, {});
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
@ -3825,7 +3881,7 @@ struct formatter<T, Char, void_t<detail::format_as_result<T>>>
|
||||
* auto s = fmt::format("{}", fmt::ptr(p));
|
||||
*/
|
||||
template <typename T> auto ptr(T p) -> const void* {
|
||||
static_assert(std::is_pointer<T>::value, "");
|
||||
static_assert(std::is_pointer<T>::value, "fmt::ptr used with non-pointer");
|
||||
return detail::bit_cast<const void*>(p);
|
||||
}
|
||||
|
||||
@ -3850,13 +3906,14 @@ constexpr auto format_as(Enum e) noexcept -> underlying_t<Enum> {
|
||||
} // namespace enums
|
||||
|
||||
#ifdef __cpp_lib_byte
|
||||
template <> struct formatter<std::byte> : formatter<unsigned> {
|
||||
template <typename Char>
|
||||
struct formatter<std::byte, Char> : formatter<unsigned, Char> {
|
||||
static auto format_as(std::byte b) -> unsigned char {
|
||||
return static_cast<unsigned char>(b);
|
||||
}
|
||||
template <typename Context>
|
||||
auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) {
|
||||
return formatter<unsigned>::format(format_as(b), ctx);
|
||||
return formatter<unsigned, Char>::format(format_as(b), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@ -4208,7 +4265,7 @@ FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args)
|
||||
* std::string answer = fmt::to_string(42);
|
||||
*/
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
FMT_NODISCARD auto to_string(T value) -> std::string {
|
||||
FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(T value) -> std::string {
|
||||
// The buffer should be large enough to store the number including the sign
|
||||
// or "false" for bool.
|
||||
char buffer[max_of(detail::digits10<T>() + 2, 5)];
|
||||
@ -4216,13 +4273,15 @@ FMT_NODISCARD auto to_string(T value) -> std::string {
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(detail::use_format_as<T>::value)>
|
||||
FMT_NODISCARD auto to_string(const T& value) -> std::string {
|
||||
FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value)
|
||||
-> std::string {
|
||||
return to_string(format_as(value));
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value &&
|
||||
!detail::use_format_as<T>::value)>
|
||||
FMT_NODISCARD auto to_string(const T& value) -> std::string {
|
||||
FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value)
|
||||
-> std::string {
|
||||
auto buffer = memory_buffer();
|
||||
detail::write<char>(appender(buffer), value);
|
||||
return {buffer.data(), buffer.size()};
|
||||
|
@ -33,8 +33,8 @@
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// Generate a unique explicit instantion in every translation unit using a tag
|
||||
// type in an anonymous namespace.
|
||||
// Generate a unique explicit instantiation in every translation unit using a
|
||||
// tag type in an anonymous namespace.
|
||||
namespace {
|
||||
struct file_access_tag {};
|
||||
} // namespace
|
||||
@ -158,7 +158,8 @@ void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
|
||||
FMT_EXPORT template <typename... T>
|
||||
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
fmt::print(os, FMT_STRING("{}\n"),
|
||||
fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
@ -11,7 +11,6 @@
|
||||
#ifndef FMT_MODULE
|
||||
# include <initializer_list>
|
||||
# include <iterator>
|
||||
# include <string>
|
||||
# include <tuple>
|
||||
# include <type_traits>
|
||||
# include <utility>
|
||||
@ -31,7 +30,7 @@ template <typename T> class is_map {
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
static constexpr bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
@ -40,17 +39,16 @@ template <typename T> class is_set {
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
static constexpr bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
|
||||
};
|
||||
|
||||
// C array overload
|
||||
template <typename T, std::size_t N>
|
||||
template <typename T, size_t N>
|
||||
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||
return arr;
|
||||
}
|
||||
template <typename T, std::size_t N>
|
||||
auto range_end(const T (&arr)[N]) -> const T* {
|
||||
template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
|
||||
return arr + N;
|
||||
}
|
||||
|
||||
@ -120,7 +118,7 @@ template <typename T> class is_tuple_like_ {
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
static constexpr bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
@ -154,7 +152,7 @@ using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
|
||||
template <typename T, typename C, bool = is_tuple_like_<T>::value>
|
||||
class is_tuple_formattable_ {
|
||||
public:
|
||||
static constexpr const bool value = false;
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
||||
template <size_t... Is>
|
||||
@ -170,7 +168,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
||||
C>::value)...>{}));
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
static constexpr bool value =
|
||||
decltype(check(tuple_index_sequence<T>{}))::value;
|
||||
};
|
||||
|
||||
@ -208,7 +206,7 @@ template <typename Char, typename... T>
|
||||
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
|
||||
|
||||
using std::get;
|
||||
template <typename Tuple, typename Char, std::size_t... Is>
|
||||
template <typename Tuple, typename Char, size_t... Is>
|
||||
auto get_formatters(index_sequence<Is...>)
|
||||
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
|
||||
} // namespace tuple
|
||||
@ -219,7 +217,7 @@ template <typename R> struct range_reference_type_impl {
|
||||
using type = decltype(*detail::range_begin(std::declval<R&>()));
|
||||
};
|
||||
|
||||
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
|
||||
template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
|
||||
using type = T&;
|
||||
};
|
||||
|
||||
@ -281,14 +279,15 @@ template <typename FormatContext> struct format_tuple_element {
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T> struct is_tuple_like {
|
||||
static constexpr const bool value =
|
||||
static constexpr bool value =
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename C> struct is_tuple_formattable {
|
||||
static constexpr const bool value =
|
||||
detail::is_tuple_formattable_<T, C>::value;
|
||||
static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
|
||||
};
|
||||
|
||||
template <typename Tuple, typename Char>
|
||||
@ -343,8 +342,9 @@ struct formatter<Tuple, Char,
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static constexpr const bool value =
|
||||
static constexpr bool value =
|
||||
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
|
||||
};
|
||||
|
||||
@ -368,6 +368,7 @@ template <typename P1, typename... Pn>
|
||||
struct conjunction<P1, Pn...>
|
||||
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
struct range_formatter;
|
||||
|
||||
@ -670,7 +671,8 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
|
||||
FMT_EXPORT
|
||||
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
|
||||
const Tuple& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
@ -685,15 +687,15 @@ template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
|
||||
# define FMT_TUPLE_JOIN_SPECIFIERS 0
|
||||
#endif
|
||||
|
||||
template <typename Char, typename Tuple>
|
||||
struct formatter<tuple_join_view<Char, Tuple>, Char,
|
||||
template <typename Tuple, typename Char>
|
||||
struct formatter<tuple_join_view<Tuple, Char>, Char,
|
||||
enable_if_t<is_tuple_like<Tuple>::value>> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return do_parse(ctx, std::tuple_size<Tuple>());
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const tuple_join_view<Char, Tuple>& value,
|
||||
auto format(const tuple_join_view<Tuple, Char>& value,
|
||||
FormatContext& ctx) const -> typename FormatContext::iterator {
|
||||
return do_format(value, ctx, std::tuple_size<Tuple>());
|
||||
}
|
||||
@ -725,14 +727,14 @@ struct formatter<tuple_join_view<Char, Tuple>, Char,
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx,
|
||||
auto do_format(const tuple_join_view<Tuple, Char>&, FormatContext& ctx,
|
||||
std::integral_constant<size_t, 0>) const ->
|
||||
typename FormatContext::iterator {
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, size_t N>
|
||||
auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx,
|
||||
auto do_format(const tuple_join_view<Tuple, Char>& value, FormatContext& ctx,
|
||||
std::integral_constant<size_t, N>) const ->
|
||||
typename FormatContext::iterator {
|
||||
using std::get;
|
||||
@ -754,7 +756,7 @@ template <typename T> class is_container_adaptor_like {
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
static constexpr bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
@ -774,13 +776,13 @@ struct formatter<
|
||||
: formatter<detail::all<typename T::container_type>, Char> {
|
||||
using all = detail::all<typename T::container_type>;
|
||||
template <typename FormatContext>
|
||||
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
struct getter : T {
|
||||
static auto get(const T& t) -> all {
|
||||
return {t.*(&getter::c)}; // Access c through the derived class.
|
||||
static auto get(const T& v) -> all {
|
||||
return {v.*(&getter::c)}; // Access c through the derived class.
|
||||
}
|
||||
};
|
||||
return formatter<all>::format(getter::get(t), ctx);
|
||||
return formatter<all>::format(getter::get(value), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
@ -825,7 +827,7 @@ auto join(Range&& r, string_view sep)
|
||||
*/
|
||||
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
|
||||
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
|
||||
-> tuple_join_view<char, Tuple> {
|
||||
-> tuple_join_view<Tuple, char> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
|
@ -15,15 +15,13 @@
|
||||
# include <atomic>
|
||||
# include <bitset>
|
||||
# include <complex>
|
||||
# include <cstdlib>
|
||||
# include <exception>
|
||||
# include <functional>
|
||||
# include <functional> // std::reference_wrapper
|
||||
# include <memory>
|
||||
# include <thread>
|
||||
# include <type_traits>
|
||||
# include <typeinfo>
|
||||
# include <utility>
|
||||
# include <vector>
|
||||
# include <typeinfo> // std::type_info
|
||||
# include <utility> // std::make_index_sequence
|
||||
|
||||
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
|
||||
# if FMT_CPLUSPLUS >= 201703L
|
||||
@ -79,11 +77,11 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
template <typename Char, typename PathChar>
|
||||
auto get_path_string(const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
@ -111,9 +109,169 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
|
||||
template <typename Char, typename OutputIt, typename T>
|
||||
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
|
||||
if constexpr (has_to_string_view<T>::value)
|
||||
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
||||
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_VARIANT
|
||||
|
||||
template <typename> struct is_variant_like_ : std::false_type {};
|
||||
template <typename... Types>
|
||||
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
||||
|
||||
template <typename Variant, typename Char> class is_variant_formattable {
|
||||
template <size_t... Is>
|
||||
static std::conjunction<
|
||||
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>
|
||||
check(std::index_sequence<Is...>);
|
||||
|
||||
public:
|
||||
static constexpr bool value = decltype(check(
|
||||
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
|
||||
};
|
||||
|
||||
#endif // FMT_CPP_LIB_VARIANT
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
size_t size = 0;
|
||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
if (demangled_name_ptr) {
|
||||
demangled_name_view = demangled_name_ptr.get();
|
||||
|
||||
// Normalization of stdlib inline namespace names.
|
||||
// libc++ inline namespaces.
|
||||
// std::__1::* -> std::*
|
||||
// std::__1::__fs::* -> std::*
|
||||
// libstdc++ inline namespaces.
|
||||
// std::__cxx11::* -> std::*
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||
if (demangled_name_view.starts_with("std::")) {
|
||||
char* begin = demangled_name_ptr.get();
|
||||
char* to = begin + 5; // std::
|
||||
for (char *from = to, *end = begin + demangled_name_view.size();
|
||||
from < end;) {
|
||||
// This is safe, because demangled_name is NUL-terminated.
|
||||
if (from[0] == '_' && from[1] == '_') {
|
||||
char* next = from + 1;
|
||||
while (next < end && *next != ':') next++;
|
||||
if (next[0] == ':' && next[1] == ':') {
|
||||
from = next + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*to++ = *from++;
|
||||
}
|
||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||
}
|
||||
} else {
|
||||
demangled_name_view = string_view(ti.name());
|
||||
}
|
||||
return detail::write_bytes<Char>(out, demangled_name_view);
|
||||
# elif FMT_MSC_VERSION
|
||||
const string_view demangled_name(ti.name());
|
||||
for (size_t i = 0; i < demangled_name.size(); ++i) {
|
||||
auto sub = demangled_name;
|
||||
sub.remove_prefix(i);
|
||||
if (sub.starts_with("enum ")) {
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("class ") || sub.starts_with("union ")) {
|
||||
i += 5;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("struct ")) {
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
if (*sub.begin() != ' ') *out++ = *sub.begin();
|
||||
}
|
||||
return out;
|
||||
# else
|
||||
return detail::write_bytes<Char>(out, string_view(ti.name()));
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif // FMT_USE_RTTI
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_flip : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T> struct is_bit_reference_like {
|
||||
static constexpr bool value = std::is_convertible<T, bool>::value &&
|
||||
std::is_nothrow_assignable<T, bool>::value &&
|
||||
has_flip<T>::value;
|
||||
};
|
||||
|
||||
// Workaround for libc++ incompatibility with C++ standard.
|
||||
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||
#ifdef _LIBCPP_VERSION
|
||||
template <typename C>
|
||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_format_as : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_format_as_member : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_format_as_member<
|
||||
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Deleter>
|
||||
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
class path : public std::filesystem::path {
|
||||
public:
|
||||
auto display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{}"), base);
|
||||
}
|
||||
auto system_string() const -> std::string { return string(); }
|
||||
|
||||
auto generic_display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{:g}"), base);
|
||||
}
|
||||
auto generic_system_string() const -> std::string { return generic_string(); }
|
||||
};
|
||||
|
||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
@ -163,40 +321,20 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
class path : public std::filesystem::path {
|
||||
public:
|
||||
auto display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{}"), base);
|
||||
}
|
||||
auto system_string() const -> std::string { return string(); }
|
||||
|
||||
auto generic_display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{:g}"), base);
|
||||
}
|
||||
auto generic_system_string() const -> std::string { return generic_string(); }
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <std::size_t N, typename Char>
|
||||
template <size_t N, typename Char>
|
||||
struct formatter<std::bitset<N>, Char>
|
||||
: nested_formatter<basic_string_view<Char>, Char> {
|
||||
private:
|
||||
// Functor because C++11 doesn't support generic lambdas.
|
||||
// This is a functor because C++11 doesn't support generic lambdas.
|
||||
struct writer {
|
||||
const std::bitset<N>& bs;
|
||||
|
||||
template <typename OutputIt>
|
||||
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
|
||||
for (auto pos = N; pos > 0; --pos) {
|
||||
for (auto pos = N; pos > 0; --pos)
|
||||
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
@ -209,14 +347,10 @@ struct formatter<std::bitset<N>, Char>
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#ifdef __cpp_lib_optional
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::optional<T>, Char,
|
||||
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||
@ -255,31 +389,9 @@ struct formatter<std::optional<T>, Char,
|
||||
return detail::write(out, ')');
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_optional
|
||||
|
||||
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Char, typename OutputIt, typename T>
|
||||
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
|
||||
if constexpr (has_to_string_view<T>::value)
|
||||
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
||||
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#ifdef __cpp_lib_expected
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename E, typename Char>
|
||||
struct formatter<std::expected<T, E>, Char,
|
||||
std::enable_if_t<(std::is_void<T>::value ||
|
||||
@ -306,12 +418,9 @@ struct formatter<std::expected<T, E>, Char,
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_expected
|
||||
|
||||
#ifdef __cpp_lib_source_location
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <> struct formatter<std::source_location> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
|
||||
|
||||
@ -329,45 +438,14 @@ template <> struct formatter<std::source_location> {
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_VARIANT
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
using variant_index_sequence =
|
||||
std::make_index_sequence<std::variant_size<T>::value>;
|
||||
|
||||
template <typename> struct is_variant_like_ : std::false_type {};
|
||||
template <typename... Types>
|
||||
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
||||
|
||||
// formattable element check.
|
||||
template <typename T, typename C> class is_variant_formattable_ {
|
||||
template <std::size_t... Is>
|
||||
static std::conjunction<
|
||||
is_formattable<std::variant_alternative_t<Is, T>, C>...>
|
||||
check(std::index_sequence<Is...>);
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
decltype(check(variant_index_sequence<T>{}))::value;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_variant_like {
|
||||
static constexpr const bool value = detail::is_variant_like_<T>::value;
|
||||
static constexpr bool value = detail::is_variant_like_<T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename C> struct is_variant_formattable {
|
||||
static constexpr const bool value =
|
||||
detail::is_variant_formattable_<T, C>::value;
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::monostate, Char> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return ctx.begin();
|
||||
@ -380,12 +458,11 @@ template <typename Char> struct formatter<std::monostate, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Variant, typename Char>
|
||||
struct formatter<
|
||||
Variant, Char,
|
||||
std::enable_if_t<std::conjunction_v<
|
||||
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
|
||||
struct formatter<Variant, Char,
|
||||
std::enable_if_t<std::conjunction_v<
|
||||
is_variant_like<Variant>,
|
||||
detail::is_variant_formattable<Variant, Char>>>> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return ctx.begin();
|
||||
}
|
||||
@ -410,15 +487,14 @@ struct formatter<
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_CPP_LIB_VARIANT
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_EXPORT
|
||||
template <> struct formatter<std::error_code> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
detail::arg_ref<char> width_ref_;
|
||||
bool debug_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||
@ -426,11 +502,19 @@ template <> struct formatter<std::error_code> {
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
char c = *it;
|
||||
if ((c >= '0' && c <= '9') || c == '{')
|
||||
if (it != end && ((c >= '0' && c <= '9') || c == '{'))
|
||||
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
|
||||
|
||||
if (it != end && *it == '?') {
|
||||
debug_ = true;
|
||||
++it;
|
||||
}
|
||||
if (it != end && *it == 's') {
|
||||
specs_.set_type(presentation_type::string);
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
@ -440,87 +524,25 @@ template <> struct formatter<std::error_code> {
|
||||
auto specs = specs_;
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
memory_buffer buf;
|
||||
buf.append(string_view(ec.category().name()));
|
||||
buf.push_back(':');
|
||||
detail::write<char>(appender(buf), ec.value());
|
||||
return detail::write<char>(ctx.out(), string_view(buf.data(), buf.size()),
|
||||
specs);
|
||||
auto buf = memory_buffer();
|
||||
if (specs_.type() == presentation_type::string) {
|
||||
buf.append(ec.message());
|
||||
} else {
|
||||
buf.append(string_view(ec.category().name()));
|
||||
buf.push_back(':');
|
||||
detail::write<char>(appender(buf), ec.value());
|
||||
}
|
||||
auto quoted = memory_buffer();
|
||||
auto str = string_view(buf.data(), buf.size());
|
||||
if (debug_) {
|
||||
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
|
||||
str = string_view(quoted.data(), quoted.size());
|
||||
}
|
||||
return detail::write<char>(ctx.out(), str, specs);
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
namespace detail {
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
std::size_t size = 0;
|
||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
if (demangled_name_ptr) {
|
||||
demangled_name_view = demangled_name_ptr.get();
|
||||
|
||||
// Normalization of stdlib inline namespace names.
|
||||
// libc++ inline namespaces.
|
||||
// std::__1::* -> std::*
|
||||
// std::__1::__fs::* -> std::*
|
||||
// libstdc++ inline namespaces.
|
||||
// std::__cxx11::* -> std::*
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||
if (demangled_name_view.starts_with("std::")) {
|
||||
char* begin = demangled_name_ptr.get();
|
||||
char* to = begin + 5; // std::
|
||||
for (char *from = to, *end = begin + demangled_name_view.size();
|
||||
from < end;) {
|
||||
// This is safe, because demangled_name is NUL-terminated.
|
||||
if (from[0] == '_' && from[1] == '_') {
|
||||
char* next = from + 1;
|
||||
while (next < end && *next != ':') next++;
|
||||
if (next[0] == ':' && next[1] == ':') {
|
||||
from = next + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*to++ = *from++;
|
||||
}
|
||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||
}
|
||||
} else {
|
||||
demangled_name_view = string_view(ti.name());
|
||||
}
|
||||
return detail::write_bytes<Char>(out, demangled_name_view);
|
||||
# elif FMT_MSC_VERSION
|
||||
const string_view demangled_name(ti.name());
|
||||
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
|
||||
auto sub = demangled_name;
|
||||
sub.remove_prefix(i);
|
||||
if (sub.starts_with("enum ")) {
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("class ") || sub.starts_with("union ")) {
|
||||
i += 5;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("struct ")) {
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
if (*sub.begin() != ' ') *out++ = *sub.begin();
|
||||
}
|
||||
return out;
|
||||
# else
|
||||
return detail::write_bytes<Char>(out, string_view(ti.name()));
|
||||
# endif
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
|
||||
> {
|
||||
@ -535,9 +557,8 @@ struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
|
||||
return detail::write_demangled_name<Char>(ctx.out(), ti);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
#endif // FMT_USE_RTTI
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char, // DEPRECATED! Mixing code unit types.
|
||||
@ -572,38 +593,9 @@ struct formatter<
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_flip : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T> struct is_bit_reference_like {
|
||||
static constexpr const bool value =
|
||||
std::is_convertible<T, bool>::value &&
|
||||
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
|
||||
};
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
|
||||
// Workaround for libc++ incompatibility with C++ standard.
|
||||
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||
template <typename C>
|
||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||
static constexpr const bool value = true;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// We can't use std::vector<bool, Allocator>::reference and
|
||||
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
||||
// in partial specialization.
|
||||
FMT_EXPORT
|
||||
template <typename BitRef, typename Char>
|
||||
struct formatter<BitRef, Char,
|
||||
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
||||
@ -615,15 +607,6 @@ struct formatter<BitRef, Char,
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Deleter>
|
||||
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::atomic<T>, Char,
|
||||
enable_if_t<is_formattable<T, Char>::value>>
|
||||
@ -636,7 +619,6 @@ struct formatter<std::atomic<T>, Char,
|
||||
};
|
||||
|
||||
#ifdef __cpp_lib_atomic_flag_test
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
@ -647,7 +629,6 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
||||
};
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
||||
private:
|
||||
detail::dynamic_format_specs<Char> specs_;
|
||||
@ -710,10 +691,13 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::reference_wrapper<T>, Char,
|
||||
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
|
||||
// Guard against format_as because reference_wrapper is
|
||||
// implicitly convertible to T&.
|
||||
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
|
||||
!detail::has_format_as<T>::value &&
|
||||
!detail::has_format_as_member<T>::value>>
|
||||
: formatter<remove_cvref_t<T>, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
|
||||
@ -723,4 +707,5 @@ struct formatter<std::reference_wrapper<T>, Char,
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_STD_H_
|
||||
|
@ -112,10 +112,6 @@ inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
|
||||
return {{s}};
|
||||
}
|
||||
|
||||
template <> struct is_char<wchar_t> : std::true_type {};
|
||||
template <> struct is_char<char16_t> : std::true_type {};
|
||||
template <> struct is_char<char32_t> : std::true_type {};
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
|
||||
#endif
|
||||
@ -155,7 +151,7 @@ auto join(std::initializer_list<T> list, wstring_view sep)
|
||||
|
||||
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
|
||||
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
|
||||
-> tuple_join_view<wchar_t, Tuple> {
|
||||
-> tuple_join_view<Tuple, wchar_t> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
@ -322,7 +318,7 @@ template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
|
||||
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
|
||||
inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
|
||||
-> std::wstring {
|
||||
auto buf = wmemory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
@ -330,19 +326,19 @@ inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
|
||||
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
|
||||
-> std::wstring {
|
||||
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
|
||||
wformat_string<T...> fmt, const T&... args) {
|
||||
FMT_DEPRECATED void print(std::FILE* f, text_style ts, wformat_string<T...> fmt,
|
||||
const T&... args) {
|
||||
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
|
||||
FMT_DEPRECATED void print(text_style ts, wformat_string<T...> fmt,
|
||||
const T&... args) {
|
||||
return print(stdout, ts, fmt, args...);
|
||||
}
|
||||
|
@ -66,14 +66,14 @@ using rwresult = int;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
inline unsigned convert_rwcount(std::size_t count) {
|
||||
inline unsigned convert_rwcount(size_t count) {
|
||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||
}
|
||||
#elif FMT_USE_FCNTL
|
||||
// Return type of read and write functions.
|
||||
using rwresult = ssize_t;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
inline size_t convert_rwcount(size_t count) { return count; }
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
@ -266,7 +266,7 @@ long long file::size() const {
|
||||
# endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
size_t file::read(void* buffer, size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
@ -274,7 +274,7 @@ std::size_t file::read(void* buffer, std::size_t count) {
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
size_t file::write(const void* buffer, size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0)
|
||||
|
@ -1 +1 @@
|
||||
7.1.2
|
||||
8.1.1
|
||||
|
@ -1,3 +1,5 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_library")
|
||||
|
||||
cc_library(
|
||||
name = "fmt",
|
||||
srcs = [
|
||||
|
@ -3,4 +3,5 @@ module(
|
||||
compatibility_level = 10,
|
||||
)
|
||||
|
||||
bazel_dep(name = "platforms", version = "0.0.10")
|
||||
bazel_dep(name = "platforms", version = "0.0.11")
|
||||
bazel_dep(name = "rules_cc", version = "0.1.1")
|
||||
|
@ -13,7 +13,7 @@ The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registr
|
||||
For instance, to use {fmt} add to your `MODULE.bazel` file:
|
||||
|
||||
```
|
||||
bazel_dep(name = "fmt", version = "10.2.1")
|
||||
bazel_dep(name = "fmt", version = "11.1.4")
|
||||
```
|
||||
|
||||
### Live at head
|
||||
|
@ -63,7 +63,9 @@ if len(args) > 0:
|
||||
'--branch', 'master'], cwd=site_dir, env=env)
|
||||
if ret != 0 or version == 'dev':
|
||||
sys.exit(ret)
|
||||
redirect_page_path = os.path.join(site_dir, version, 'api.html')
|
||||
current_doc_path = os.path.join(site_dir, version)
|
||||
os.makedirs(current_doc_path, exist_ok=True)
|
||||
redirect_page_path = os.path.join(current_doc_path, 'api.html')
|
||||
with open(redirect_page_path, "w") as file:
|
||||
file.write(redirect_page)
|
||||
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
|
||||
|
@ -5,14 +5,16 @@
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
// Turn assertion failures into exceptions for testing.
|
||||
// clang-format off
|
||||
#include "test-assert.h"
|
||||
// clang-format on
|
||||
|
||||
#include "fmt/base.h"
|
||||
|
||||
#include <climits> // INT_MAX
|
||||
#include <cstring> // std::strlen
|
||||
#include <limits.h> // INT_MAX
|
||||
#include <string.h> // strlen
|
||||
|
||||
#include <functional> // std::equal_to
|
||||
#include <iterator> // std::back_insert_iterator, std::distance
|
||||
#include <limits> // std::numeric_limits
|
||||
@ -21,39 +23,36 @@
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
using fmt::string_view;
|
||||
using fmt::detail::buffer;
|
||||
#ifdef FMT_FORMAT_H_
|
||||
# error base-test includes format.h
|
||||
#endif
|
||||
|
||||
using testing::_;
|
||||
using testing::Invoke;
|
||||
using testing::Return;
|
||||
|
||||
#ifdef FMT_FORMAT_H_
|
||||
# error core-test includes format.h
|
||||
#endif
|
||||
|
||||
fmt::appender copy(fmt::string_view s, fmt::appender out) {
|
||||
auto copy(fmt::string_view s, fmt::appender out) -> fmt::appender {
|
||||
for (char c : s) *out++ = c;
|
||||
return out;
|
||||
}
|
||||
|
||||
TEST(string_view_test, value_type) {
|
||||
static_assert(std::is_same<string_view::value_type, char>::value, "");
|
||||
static_assert(std::is_same<fmt::string_view::value_type, char>::value, "");
|
||||
}
|
||||
|
||||
TEST(string_view_test, ctor) {
|
||||
EXPECT_STREQ("abc", fmt::string_view("abc").data());
|
||||
EXPECT_EQ(3u, fmt::string_view("abc").size());
|
||||
EXPECT_STREQ(fmt::string_view("abc").data(), "abc");
|
||||
EXPECT_EQ(fmt::string_view("abc").size(), 3u);
|
||||
|
||||
EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data());
|
||||
EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size());
|
||||
EXPECT_STREQ(fmt::string_view(std::string("defg")).data(), "defg");
|
||||
EXPECT_EQ(fmt::string_view(std::string("defg")).size(), 4u);
|
||||
}
|
||||
|
||||
TEST(string_view_test, length) {
|
||||
// Test that string_view::size() returns string length, not buffer size.
|
||||
char str[100] = "some string";
|
||||
EXPECT_EQ(std::strlen(str), string_view(str).size());
|
||||
EXPECT_LT(std::strlen(str), sizeof(str));
|
||||
EXPECT_EQ(fmt::string_view(str).size(), strlen(str));
|
||||
EXPECT_LT(strlen(str), sizeof(str));
|
||||
}
|
||||
|
||||
// Check string_view's comparison operator.
|
||||
@ -62,13 +61,16 @@ template <template <typename> class Op> void check_op() {
|
||||
size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
|
||||
for (size_t i = 0; i < num_inputs; ++i) {
|
||||
for (size_t j = 0; j < num_inputs; ++j) {
|
||||
string_view lhs(inputs[i]), rhs(inputs[j]);
|
||||
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs));
|
||||
fmt::string_view lhs(inputs[i]), rhs(inputs[j]);
|
||||
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0),
|
||||
Op<fmt::string_view>()(lhs, rhs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(string_view_test, compare) {
|
||||
using fmt::string_view;
|
||||
|
||||
EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0);
|
||||
EXPECT_GT(string_view("fop").compare(string_view("foo")), 0);
|
||||
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
|
||||
@ -92,6 +94,167 @@ TEST(string_view_test, compare) {
|
||||
check_op<std::greater_equal>();
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEVAL
|
||||
TEST(string_view_test, from_constexpr_fixed_string) {
|
||||
constexpr int size = 4;
|
||||
|
||||
struct fixed_string {
|
||||
char data[size] = {};
|
||||
|
||||
constexpr fixed_string(const char (&m)[size]) {
|
||||
for (size_t i = 0; i != size; ++i) data[i] = m[i];
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto fs = fixed_string("foo");
|
||||
static constexpr auto sv = fmt::string_view(fs.data);
|
||||
EXPECT_EQ(sv, "foo");
|
||||
}
|
||||
#endif // FMT_USE_CONSTEVAL
|
||||
|
||||
TEST(buffer_test, noncopyable) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<fmt::detail::buffer<char>>::value);
|
||||
EXPECT_FALSE(std::is_copy_assignable<fmt::detail::buffer<char>>::value);
|
||||
}
|
||||
|
||||
TEST(buffer_test, nonmoveable) {
|
||||
EXPECT_FALSE(std::is_move_constructible<fmt::detail::buffer<char>>::value);
|
||||
EXPECT_FALSE(std::is_move_assignable<fmt::detail::buffer<char>>::value);
|
||||
}
|
||||
|
||||
TEST(buffer_test, indestructible) {
|
||||
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
|
||||
"buffer's destructor is protected");
|
||||
}
|
||||
|
||||
template <typename T> struct mock_buffer final : fmt::detail::buffer<T> {
|
||||
MOCK_METHOD(size_t, do_grow, (size_t));
|
||||
|
||||
static void grow(fmt::detail::buffer<T>& buf, size_t capacity) {
|
||||
auto& self = static_cast<mock_buffer&>(buf);
|
||||
self.set(buf.data(), self.do_grow(capacity));
|
||||
}
|
||||
|
||||
mock_buffer(T* data = nullptr, size_t buf_capacity = 0)
|
||||
: fmt::detail::buffer<T>(grow) {
|
||||
this->set(data, buf_capacity);
|
||||
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
|
||||
return capacity;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
TEST(buffer_test, ctor) {
|
||||
{
|
||||
mock_buffer<int> buffer;
|
||||
EXPECT_EQ(buffer.data(), nullptr);
|
||||
EXPECT_EQ(buffer.size(), 0u);
|
||||
EXPECT_EQ(buffer.capacity(), 0u);
|
||||
}
|
||||
{
|
||||
int data;
|
||||
mock_buffer<int> buffer(&data);
|
||||
EXPECT_EQ(&buffer[0], &data);
|
||||
EXPECT_EQ(buffer.size(), 0u);
|
||||
EXPECT_EQ(buffer.capacity(), 0u);
|
||||
}
|
||||
{
|
||||
int data;
|
||||
size_t capacity = std::numeric_limits<size_t>::max();
|
||||
mock_buffer<int> buffer(&data, capacity);
|
||||
EXPECT_EQ(&buffer[0], &data);
|
||||
EXPECT_EQ(buffer.size(), 0u);
|
||||
EXPECT_EQ(buffer.capacity(), capacity);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(buffer_test, access) {
|
||||
char data[10];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
buffer[0] = 11;
|
||||
EXPECT_EQ(buffer[0], 11);
|
||||
buffer[3] = 42;
|
||||
EXPECT_EQ(*(&buffer[0] + 3), 42);
|
||||
const fmt::detail::buffer<char>& const_buffer = buffer;
|
||||
EXPECT_EQ(const_buffer[3], 42);
|
||||
}
|
||||
|
||||
TEST(buffer_test, try_resize) {
|
||||
char data[123];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
buffer[10] = 42;
|
||||
EXPECT_EQ(buffer[10], 42);
|
||||
buffer.try_resize(20);
|
||||
EXPECT_EQ(buffer.size(), 20u);
|
||||
EXPECT_EQ(buffer.capacity(), 123u);
|
||||
EXPECT_EQ(buffer[10], 42);
|
||||
buffer.try_resize(5);
|
||||
EXPECT_EQ(buffer.size(), 5u);
|
||||
EXPECT_EQ(buffer.capacity(), 123u);
|
||||
EXPECT_EQ(buffer[10], 42);
|
||||
// Check if try_resize calls grow.
|
||||
EXPECT_CALL(buffer, do_grow(124));
|
||||
buffer.try_resize(124);
|
||||
EXPECT_CALL(buffer, do_grow(200));
|
||||
buffer.try_resize(200);
|
||||
}
|
||||
|
||||
TEST(buffer_test, try_resize_partial) {
|
||||
char data[10];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15));
|
||||
buffer.try_resize(20);
|
||||
EXPECT_EQ(buffer.capacity(), 15);
|
||||
EXPECT_EQ(buffer.size(), 15);
|
||||
}
|
||||
|
||||
TEST(buffer_test, clear) {
|
||||
mock_buffer<char> buffer;
|
||||
EXPECT_CALL(buffer, do_grow(20));
|
||||
buffer.try_resize(20);
|
||||
buffer.try_resize(0);
|
||||
EXPECT_EQ(buffer.size(), 0u);
|
||||
EXPECT_EQ(buffer.capacity(), 20u);
|
||||
}
|
||||
|
||||
TEST(buffer_test, append) {
|
||||
char data[15];
|
||||
mock_buffer<char> buffer(data, 10);
|
||||
auto test = "test";
|
||||
buffer.append(test, test + 5);
|
||||
EXPECT_STREQ(&buffer[0], test);
|
||||
EXPECT_EQ(buffer.size(), 5u);
|
||||
buffer.try_resize(10);
|
||||
EXPECT_CALL(buffer, do_grow(12));
|
||||
buffer.append(test, test + 2);
|
||||
EXPECT_EQ(buffer[10], 't');
|
||||
EXPECT_EQ(buffer[11], 'e');
|
||||
EXPECT_EQ(buffer.size(), 12u);
|
||||
}
|
||||
|
||||
TEST(buffer_test, append_partial) {
|
||||
char data[10];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
testing::InSequence seq;
|
||||
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10));
|
||||
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) {
|
||||
EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789");
|
||||
buffer.clear();
|
||||
return 10;
|
||||
}));
|
||||
auto test = "0123456789abcde";
|
||||
buffer.append(test, test + 15);
|
||||
}
|
||||
|
||||
TEST(buffer_test, append_allocates_enough_storage) {
|
||||
char data[19];
|
||||
mock_buffer<char> buffer(data, 10);
|
||||
auto test = "abcdefgh";
|
||||
buffer.try_resize(10);
|
||||
EXPECT_CALL(buffer, do_grow(19));
|
||||
buffer.append(test, test + 9);
|
||||
}
|
||||
|
||||
TEST(base_test, is_locking) {
|
||||
EXPECT_FALSE(fmt::detail::is_locking<const char(&)[3]>());
|
||||
}
|
||||
@ -116,154 +279,15 @@ TEST(base_test, is_back_insert_iterator) {
|
||||
std::front_insert_iterator<std::string>>::value);
|
||||
}
|
||||
|
||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470
|
||||
TEST(buffer_test, noncopyable) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<buffer<char>>::value);
|
||||
# if !FMT_MSC_VERSION
|
||||
// std::is_copy_assignable is broken in MSVC2013.
|
||||
EXPECT_FALSE(std::is_copy_assignable<buffer<char>>::value);
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(buffer_test, nonmoveable) {
|
||||
EXPECT_FALSE(std::is_move_constructible<buffer<char>>::value);
|
||||
# if !FMT_MSC_VERSION
|
||||
// std::is_move_assignable is broken in MSVC2013.
|
||||
EXPECT_FALSE(std::is_move_assignable<buffer<char>>::value);
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(buffer_test, indestructible) {
|
||||
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
|
||||
"buffer's destructor is protected");
|
||||
}
|
||||
|
||||
template <typename T> struct mock_buffer final : buffer<T> {
|
||||
MOCK_METHOD(size_t, do_grow, (size_t));
|
||||
|
||||
static void grow(buffer<T>& buf, size_t capacity) {
|
||||
auto& self = static_cast<mock_buffer&>(buf);
|
||||
self.set(buf.data(), self.do_grow(capacity));
|
||||
}
|
||||
|
||||
mock_buffer(T* data = nullptr, size_t buf_capacity = 0) : buffer<T>(grow) {
|
||||
this->set(data, buf_capacity);
|
||||
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
|
||||
return capacity;
|
||||
}));
|
||||
}
|
||||
struct minimal_container {
|
||||
using value_type = char;
|
||||
void push_back(char) {}
|
||||
};
|
||||
|
||||
TEST(buffer_test, ctor) {
|
||||
{
|
||||
mock_buffer<int> buffer;
|
||||
EXPECT_EQ(nullptr, buffer.data());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
|
||||
}
|
||||
{
|
||||
int dummy;
|
||||
mock_buffer<int> buffer(&dummy);
|
||||
EXPECT_EQ(&dummy, &buffer[0]);
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
|
||||
}
|
||||
{
|
||||
int dummy;
|
||||
size_t capacity = std::numeric_limits<size_t>::max();
|
||||
mock_buffer<int> buffer(&dummy, capacity);
|
||||
EXPECT_EQ(&dummy, &buffer[0]);
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(capacity, buffer.capacity());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(buffer_test, access) {
|
||||
char data[10];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
buffer[0] = 11;
|
||||
EXPECT_EQ(11, buffer[0]);
|
||||
buffer[3] = 42;
|
||||
EXPECT_EQ(42, *(&buffer[0] + 3));
|
||||
const fmt::detail::buffer<char>& const_buffer = buffer;
|
||||
EXPECT_EQ(42, const_buffer[3]);
|
||||
}
|
||||
|
||||
TEST(buffer_test, try_resize) {
|
||||
char data[123];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
buffer[10] = 42;
|
||||
EXPECT_EQ(42, buffer[10]);
|
||||
buffer.try_resize(20);
|
||||
EXPECT_EQ(20u, buffer.size());
|
||||
EXPECT_EQ(123u, buffer.capacity());
|
||||
EXPECT_EQ(42, buffer[10]);
|
||||
buffer.try_resize(5);
|
||||
EXPECT_EQ(5u, buffer.size());
|
||||
EXPECT_EQ(123u, buffer.capacity());
|
||||
EXPECT_EQ(42, buffer[10]);
|
||||
// Check if try_resize calls grow.
|
||||
EXPECT_CALL(buffer, do_grow(124));
|
||||
buffer.try_resize(124);
|
||||
EXPECT_CALL(buffer, do_grow(200));
|
||||
buffer.try_resize(200);
|
||||
}
|
||||
|
||||
TEST(buffer_test, try_resize_partial) {
|
||||
char data[10];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15));
|
||||
buffer.try_resize(20);
|
||||
EXPECT_EQ(buffer.capacity(), 15);
|
||||
EXPECT_EQ(buffer.size(), 15);
|
||||
}
|
||||
|
||||
TEST(buffer_test, clear) {
|
||||
mock_buffer<char> buffer;
|
||||
EXPECT_CALL(buffer, do_grow(20));
|
||||
buffer.try_resize(20);
|
||||
buffer.try_resize(0);
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(20u, buffer.capacity());
|
||||
}
|
||||
|
||||
TEST(buffer_test, append) {
|
||||
char data[15];
|
||||
mock_buffer<char> buffer(data, 10);
|
||||
auto test = "test";
|
||||
buffer.append(test, test + 5);
|
||||
EXPECT_STREQ(test, &buffer[0]);
|
||||
EXPECT_EQ(5u, buffer.size());
|
||||
buffer.try_resize(10);
|
||||
EXPECT_CALL(buffer, do_grow(12));
|
||||
buffer.append(test, test + 2);
|
||||
EXPECT_EQ('t', buffer[10]);
|
||||
EXPECT_EQ('e', buffer[11]);
|
||||
EXPECT_EQ(12u, buffer.size());
|
||||
}
|
||||
|
||||
TEST(buffer_test, append_partial) {
|
||||
char data[10];
|
||||
mock_buffer<char> buffer(data, sizeof(data));
|
||||
testing::InSequence seq;
|
||||
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10));
|
||||
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) {
|
||||
EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789");
|
||||
buffer.clear();
|
||||
return 10;
|
||||
}));
|
||||
auto test = "0123456789abcde";
|
||||
buffer.append(test, test + 15);
|
||||
}
|
||||
|
||||
TEST(buffer_test, append_allocates_enough_storage) {
|
||||
char data[19];
|
||||
mock_buffer<char> buffer(data, 10);
|
||||
auto test = "abcdefgh";
|
||||
buffer.try_resize(10);
|
||||
EXPECT_CALL(buffer, do_grow(19));
|
||||
buffer.append(test, test + 9);
|
||||
TEST(base_test, copy) {
|
||||
minimal_container c;
|
||||
static constexpr char str[] = "a";
|
||||
fmt::detail::copy<char>(str, str + 1, std::back_inserter(c));
|
||||
}
|
||||
|
||||
TEST(base_test, get_buffer) {
|
||||
@ -290,11 +314,6 @@ template <typename Char> struct formatter<test_struct, Char> {
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(arg_test, format_args) {
|
||||
auto args = fmt::format_args();
|
||||
EXPECT_FALSE(args.get(1));
|
||||
}
|
||||
|
||||
// Use a unique result type to make sure that there are no undesirable
|
||||
// conversions.
|
||||
struct test_result {};
|
||||
@ -360,33 +379,9 @@ VISIT_TYPE(unsigned long, unsigned long long);
|
||||
CHECK_ARG(expected, value) \
|
||||
}
|
||||
|
||||
template <typename T> class numeric_arg_test : public testing::Test {};
|
||||
|
||||
#if FMT_BUILTIN_TYPES
|
||||
using test_types =
|
||||
testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
|
||||
unsigned, long, unsigned long, long long, unsigned long long,
|
||||
float, double, long double>;
|
||||
#else
|
||||
using test_types = testing::Types<int>;
|
||||
#endif
|
||||
TYPED_TEST_SUITE(numeric_arg_test, test_types);
|
||||
|
||||
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||
auto test_value() -> T {
|
||||
return static_cast<T>(42);
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
|
||||
auto test_value() -> T {
|
||||
return static_cast<T>(4.2);
|
||||
}
|
||||
|
||||
TYPED_TEST(numeric_arg_test, make_and_visit) {
|
||||
CHECK_ARG_SIMPLE(test_value<TypeParam>());
|
||||
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::min());
|
||||
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
|
||||
TEST(arg_test, format_args) {
|
||||
auto args = fmt::format_args();
|
||||
EXPECT_FALSE(args.get(1));
|
||||
}
|
||||
|
||||
TEST(arg_test, char_arg) { CHECK_ARG('a', 'a'); }
|
||||
@ -428,7 +423,7 @@ struct check_custom {
|
||||
auto parse_ctx = fmt::format_parse_context("");
|
||||
auto ctx = fmt::format_context(fmt::appender(buffer), fmt::format_args());
|
||||
h.format(parse_ctx, ctx);
|
||||
EXPECT_EQ("test", std::string(buffer.data, buffer.size()));
|
||||
EXPECT_EQ(std::string(buffer.data, buffer.size()), "test");
|
||||
return test_result();
|
||||
}
|
||||
};
|
||||
@ -448,27 +443,57 @@ TEST(arg_test, visit_invalid_arg) {
|
||||
fmt::basic_format_arg<fmt::format_context>().visit(visitor);
|
||||
}
|
||||
|
||||
template <typename T> class numeric_arg_test : public testing::Test {};
|
||||
|
||||
#if FMT_BUILTIN_TYPES
|
||||
using test_types =
|
||||
testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
|
||||
unsigned, long, unsigned long, long long, unsigned long long,
|
||||
float, double, long double>;
|
||||
#else
|
||||
using test_types = testing::Types<int>;
|
||||
#endif
|
||||
TYPED_TEST_SUITE(numeric_arg_test, test_types);
|
||||
|
||||
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||
auto test_value() -> T {
|
||||
return static_cast<T>(42);
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
|
||||
auto test_value() -> T {
|
||||
return static_cast<T>(4.2);
|
||||
}
|
||||
|
||||
TYPED_TEST(numeric_arg_test, make_and_visit) {
|
||||
CHECK_ARG_SIMPLE(test_value<TypeParam>());
|
||||
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::min());
|
||||
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
|
||||
enum class arg_id_result { none, index, name };
|
||||
|
||||
struct test_arg_id_handler {
|
||||
arg_id_result res = arg_id_result::none;
|
||||
int index = 0;
|
||||
string_view name;
|
||||
fmt::string_view name;
|
||||
|
||||
constexpr void on_index(int i) {
|
||||
res = arg_id_result::index;
|
||||
index = i;
|
||||
}
|
||||
|
||||
constexpr void on_name(string_view n) {
|
||||
constexpr void on_name(fmt::string_view n) {
|
||||
res = arg_id_result::name;
|
||||
name = n;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
|
||||
constexpr auto parse_arg_id(const char (&s)[N]) -> test_arg_id_handler {
|
||||
auto h = test_arg_id_handler();
|
||||
fmt::detail::parse_arg_id(s, s + N, h);
|
||||
return h;
|
||||
@ -526,7 +551,7 @@ struct test_format_string_handler {
|
||||
bool error = false;
|
||||
};
|
||||
|
||||
template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
|
||||
template <size_t N> constexpr auto parse_string(const char (&s)[N]) -> bool {
|
||||
auto h = test_format_string_handler();
|
||||
fmt::detail::parse_format_string(fmt::string_view(s, N - 1), h);
|
||||
return !h.error;
|
||||
@ -540,6 +565,7 @@ TEST(base_test, constexpr_parse_format_string) {
|
||||
static_assert(parse_string("{foo}"), "");
|
||||
static_assert(parse_string("{:}"), "");
|
||||
}
|
||||
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
struct enabled_formatter {};
|
||||
@ -684,46 +710,46 @@ TEST(base_test, format_to) {
|
||||
TEST(base_test, format_to_array) {
|
||||
char buffer[4];
|
||||
auto result = fmt::format_to(buffer, "{}", 12345);
|
||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
||||
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||
EXPECT_TRUE(result.truncated);
|
||||
EXPECT_EQ(buffer + 4, result.out);
|
||||
EXPECT_EQ("1234", fmt::string_view(buffer, 4));
|
||||
EXPECT_EQ(result.out, buffer + 4);
|
||||
EXPECT_EQ(fmt::string_view(buffer, 4), "1234");
|
||||
|
||||
char* out = nullptr;
|
||||
EXPECT_THROW(out = result, std::runtime_error);
|
||||
(void)out;
|
||||
|
||||
result = fmt::format_to(buffer, "{:s}", "foobar");
|
||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
||||
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||
EXPECT_TRUE(result.truncated);
|
||||
EXPECT_EQ(buffer + 4, result.out);
|
||||
EXPECT_EQ("foob", fmt::string_view(buffer, 4));
|
||||
EXPECT_EQ(result.out, buffer + 4);
|
||||
EXPECT_EQ(fmt::string_view(buffer, 4), "foob");
|
||||
|
||||
buffer[0] = 'x';
|
||||
buffer[1] = 'x';
|
||||
buffer[2] = 'x';
|
||||
buffer[3] = 'x';
|
||||
result = fmt::format_to(buffer, "{}", 'A');
|
||||
EXPECT_EQ(1, std::distance(&buffer[0], result.out));
|
||||
EXPECT_EQ(std::distance(&buffer[0], result.out), 1);
|
||||
EXPECT_FALSE(result.truncated);
|
||||
EXPECT_EQ(buffer + 1, result.out);
|
||||
EXPECT_EQ("Axxx", fmt::string_view(buffer, 4));
|
||||
EXPECT_EQ(result.out, buffer + 1);
|
||||
EXPECT_EQ(fmt::string_view(buffer, 4), "Axxx");
|
||||
|
||||
result = fmt::format_to(buffer, "{}{} ", 'B', 'C');
|
||||
EXPECT_EQ(3, std::distance(&buffer[0], result.out));
|
||||
EXPECT_EQ(std::distance(&buffer[0], result.out), 3);
|
||||
EXPECT_FALSE(result.truncated);
|
||||
EXPECT_EQ(buffer + 3, result.out);
|
||||
EXPECT_EQ("BC x", fmt::string_view(buffer, 4));
|
||||
EXPECT_EQ(result.out, buffer + 3);
|
||||
EXPECT_EQ(fmt::string_view(buffer, 4), "BC x");
|
||||
|
||||
result = fmt::format_to(buffer, "{}", "ABCDE");
|
||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
||||
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||
EXPECT_TRUE(result.truncated);
|
||||
EXPECT_EQ("ABCD", fmt::string_view(buffer, 4));
|
||||
EXPECT_EQ(fmt::string_view(buffer, 4), "ABCD");
|
||||
|
||||
result = fmt::format_to(buffer, "{}", std::string(1000, '*').c_str());
|
||||
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
|
||||
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
|
||||
EXPECT_TRUE(result.truncated);
|
||||
EXPECT_EQ("****", fmt::string_view(buffer, 4));
|
||||
EXPECT_EQ(fmt::string_view(buffer, 4), "****");
|
||||
}
|
||||
|
||||
// Test that check is not found by ADL.
|
||||
@ -790,7 +816,7 @@ TEST(base_test, format_nonconst) {
|
||||
}
|
||||
|
||||
TEST(base_test, throw_in_buffer_dtor) {
|
||||
enum { buffer_size = 256 };
|
||||
constexpr int buffer_size = 256;
|
||||
|
||||
struct throwing_iterator {
|
||||
int& count;
|
||||
@ -812,7 +838,7 @@ TEST(base_test, throw_in_buffer_dtor) {
|
||||
}
|
||||
}
|
||||
|
||||
struct its_a_trap {
|
||||
struct convertible_to_any_type_with_member_x {
|
||||
template <typename T> operator T() const {
|
||||
auto v = T();
|
||||
v.x = 42;
|
||||
@ -821,12 +847,12 @@ struct its_a_trap {
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<its_a_trap> {
|
||||
template <> struct formatter<convertible_to_any_type_with_member_x> {
|
||||
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
auto format(its_a_trap, format_context& ctx) const
|
||||
auto format(convertible_to_any_type_with_member_x, format_context& ctx) const
|
||||
-> decltype(ctx.out()) const {
|
||||
auto out = ctx.out();
|
||||
*out++ = 'x';
|
||||
@ -835,9 +861,10 @@ template <> struct formatter<its_a_trap> {
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(base_test, trappy_conversion) {
|
||||
TEST(base_test, promiscuous_conversions) {
|
||||
auto s = std::string();
|
||||
fmt::format_to(std::back_inserter(s), "{}", its_a_trap());
|
||||
fmt::format_to(std::back_inserter(s), "{}",
|
||||
convertible_to_any_type_with_member_x());
|
||||
EXPECT_EQ(s, "x");
|
||||
}
|
||||
|
||||
@ -862,25 +889,23 @@ TEST(base_test, format_to_custom_container) {
|
||||
fmt::format_to(std::back_inserter(c), "");
|
||||
}
|
||||
|
||||
struct nondeterministic_format_string {
|
||||
mutable int i = 0;
|
||||
FMT_CONSTEXPR operator string_view() const {
|
||||
return string_view("{}", i++ != 0 ? 2 : 0);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(base_test, no_repeated_format_string_conversions) {
|
||||
#if !FMT_GCC_VERSION
|
||||
struct nondeterministic_format_string {
|
||||
mutable int i = 0;
|
||||
FMT_CONSTEXPR operator fmt::string_view() const {
|
||||
return {"{}", i++ != 0 ? 2u : 0u};
|
||||
}
|
||||
};
|
||||
|
||||
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200
|
||||
char buf[10];
|
||||
fmt::format_to(buf, nondeterministic_format_string());
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(base_test, format_context_accessors) {
|
||||
class copier {
|
||||
static fmt::format_context copy(fmt::appender app,
|
||||
const fmt::format_context& ctx) {
|
||||
return fmt::format_context(std::move(app), ctx.args(), ctx.locale());
|
||||
}
|
||||
auto copy = [](fmt::appender app, const fmt::format_context& ctx) {
|
||||
return fmt::format_context(app, ctx.args(), ctx.locale());
|
||||
};
|
||||
fmt::detail::ignore_unused(copy);
|
||||
}
|
||||
|
@ -15,11 +15,9 @@
|
||||
#include "util.h" // get_locale
|
||||
|
||||
using fmt::runtime;
|
||||
using fmt::sys_time;
|
||||
using testing::Contains;
|
||||
|
||||
template <typename Duration>
|
||||
using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;
|
||||
|
||||
#if defined(__MINGW32__) && !defined(_UCRT)
|
||||
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
|
||||
# define FMT_HAS_C99_STRFTIME 0
|
||||
@ -240,196 +238,145 @@ TEST(chrono_test, format_to_empty_container) {
|
||||
EXPECT_EQ(s, "42");
|
||||
}
|
||||
|
||||
TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
|
||||
|
||||
auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
|
||||
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
|
||||
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
|
||||
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
|
||||
lhs.tm_wday == rhs.tm_wday && lhs.tm_yday == rhs.tm_yday &&
|
||||
lhs.tm_isdst == rhs.tm_isdst;
|
||||
}
|
||||
|
||||
TEST(chrono_test, gmtime) {
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::gmtime(&t);
|
||||
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
|
||||
auto expected = *std::gmtime(&t);
|
||||
auto actual = fmt::gmtime(t);
|
||||
EXPECT_EQ(actual.tm_sec, expected.tm_sec);
|
||||
EXPECT_EQ(actual.tm_min, expected.tm_min);
|
||||
EXPECT_EQ(actual.tm_hour, expected.tm_hour);
|
||||
EXPECT_EQ(actual.tm_mday, expected.tm_mday);
|
||||
EXPECT_EQ(actual.tm_mon, expected.tm_mon);
|
||||
EXPECT_EQ(actual.tm_year, expected.tm_year);
|
||||
EXPECT_EQ(actual.tm_wday, expected.tm_wday);
|
||||
EXPECT_EQ(actual.tm_yday, expected.tm_yday);
|
||||
EXPECT_EQ(actual.tm_isdst, expected.tm_isdst);
|
||||
}
|
||||
|
||||
template <typename TimePoint>
|
||||
auto strftime_full_utc(TimePoint tp) -> std::string {
|
||||
auto t = std::chrono::system_clock::to_time_t(tp);
|
||||
auto tm = *std::gmtime(&t);
|
||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
||||
template <typename Time> void test_time(Time time) {
|
||||
EXPECT_EQ(fmt::format("{}", time), "1979-03-12 12:00:00");
|
||||
EXPECT_EQ(fmt::format("{:}", time), "1979-03-12 12:00:00");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%%}", time), "%");
|
||||
EXPECT_EQ(fmt::format("{:%n}", time), "\n");
|
||||
EXPECT_EQ(fmt::format("{:%t}", time), "\t");
|
||||
EXPECT_EQ(fmt::format("{:%Y}", time), "1979");
|
||||
EXPECT_EQ(fmt::format("{:%EY}", time), "1979");
|
||||
EXPECT_EQ(fmt::format("{:%y}", time), "79");
|
||||
EXPECT_EQ(fmt::format("{:%Oy}", time), "79");
|
||||
EXPECT_EQ(fmt::format("{:%Ey}", time), "79");
|
||||
EXPECT_EQ(fmt::format("{:%C}", time), "19");
|
||||
EXPECT_EQ(fmt::format("{:%EC}", time), "19");
|
||||
EXPECT_EQ(fmt::format("{:%G}", time), "1979");
|
||||
EXPECT_EQ(fmt::format("{:%g}", time), "79");
|
||||
EXPECT_EQ(fmt::format("{:%b}", time), "Mar");
|
||||
EXPECT_EQ(fmt::format("{:%h}", time), "Mar");
|
||||
EXPECT_EQ(fmt::format("{:%B}", time), "March");
|
||||
EXPECT_EQ(fmt::format("{:%m}", time), "03");
|
||||
EXPECT_EQ(fmt::format("{:%Om}", time), "03");
|
||||
EXPECT_EQ(fmt::format("{:%U}", time), "10");
|
||||
EXPECT_EQ(fmt::format("{:%OU}", time), "10");
|
||||
EXPECT_EQ(fmt::format("{:%W}", time), "11");
|
||||
EXPECT_EQ(fmt::format("{:%OW}", time), "11");
|
||||
EXPECT_EQ(fmt::format("{:%V}", time), "11");
|
||||
EXPECT_EQ(fmt::format("{:%OV}", time), "11");
|
||||
EXPECT_EQ(fmt::format("{:%j}", time), "071");
|
||||
EXPECT_EQ(fmt::format("{:%d}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%Od}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%e}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%Oe}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%a}", time), "Mon");
|
||||
EXPECT_EQ(fmt::format("{:%A}", time), "Monday");
|
||||
EXPECT_EQ(fmt::format("{:%w}", time), "1");
|
||||
EXPECT_EQ(fmt::format("{:%Ow}", time), "1");
|
||||
EXPECT_EQ(fmt::format("{:%u}", time), "1");
|
||||
EXPECT_EQ(fmt::format("{:%Ou}", time), "1");
|
||||
EXPECT_EQ(fmt::format("{:%H}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%OH}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%I}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%OI}", time), "12");
|
||||
EXPECT_EQ(fmt::format("{:%M}", time), "00");
|
||||
EXPECT_EQ(fmt::format("{:%OM}", time), "00");
|
||||
EXPECT_EQ(fmt::format("{:%S}", time), "00");
|
||||
EXPECT_EQ(fmt::format("{:%OS}", time), "00");
|
||||
EXPECT_EQ(fmt::format("{:%x}", time), "03/12/79");
|
||||
EXPECT_EQ(fmt::format("{:%Ex}", time), "03/12/79");
|
||||
EXPECT_EQ(fmt::format("{:%X}", time), "12:00:00");
|
||||
EXPECT_EQ(fmt::format("{:%EX}", time), "12:00:00");
|
||||
EXPECT_EQ(fmt::format("{:%D}", time), "03/12/79");
|
||||
EXPECT_EQ(fmt::format("{:%F}", time), "1979-03-12");
|
||||
EXPECT_EQ(fmt::format("{:%R}", time), "12:00");
|
||||
EXPECT_EQ(fmt::format("{:%T}", time), "12:00:00");
|
||||
EXPECT_EQ(fmt::format("{:%p}", time), "PM");
|
||||
EXPECT_EQ(fmt::format("{:%c}", time), "Mon Mar 12 12:00:00 1979");
|
||||
EXPECT_EQ(fmt::format("{:%Ec}", time), "Mon Mar 12 12:00:00 1979");
|
||||
EXPECT_EQ(fmt::format("{:%r}", time), "12:00:00 PM");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", time), "1979-03-12 12:00:00");
|
||||
}
|
||||
|
||||
TEST(chrono_test, system_clock_time_point) {
|
||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now());
|
||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1));
|
||||
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1));
|
||||
TEST(chrono_test, sys_time) {
|
||||
auto time =
|
||||
fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
|
||||
test_time(time);
|
||||
EXPECT_EQ(fmt::format("{:%z}", time), "+0000");
|
||||
EXPECT_EQ(fmt::format("{:%Ez}", time), "+00:00");
|
||||
EXPECT_EQ(fmt::format("{:%Oz}", time), "+00:00");
|
||||
EXPECT_EQ(fmt::format("{:%Z}", time), "UTC");
|
||||
}
|
||||
|
||||
auto t2 = sys_time<std::chrono::seconds>(std::chrono::seconds(42));
|
||||
EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||
TEST(chrono_test, local_time) {
|
||||
auto time =
|
||||
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(290088000));
|
||||
test_time(time);
|
||||
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
|
||||
fmt::format_error, "no timezone");
|
||||
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
|
||||
fmt::format_error, "no timezone");
|
||||
}
|
||||
|
||||
std::vector<std::string> spec_list = {
|
||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
||||
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
|
||||
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
|
||||
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
|
||||
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
|
||||
"%EX", "%D", "%F", "%R", "%T", "%p"};
|
||||
#ifndef _WIN32
|
||||
// Disabled on Windows because these formats are not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||
#elif !FMT_HAS_C99_STRFTIME
|
||||
// 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"};
|
||||
#endif
|
||||
spec_list.push_back("%Y-%m-%d %H:%M:%S");
|
||||
template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)>
|
||||
bool set_tm_gmtoff(T& time, long offset) {
|
||||
time.tm_gmtoff = offset;
|
||||
return true;
|
||||
}
|
||||
template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)>
|
||||
bool set_tm_gmtoff(T&, long) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
auto sys_output = system_strftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
TEST(chrono_test, tm) {
|
||||
auto time = fmt::gmtime(290088000);
|
||||
test_time(time);
|
||||
if (set_tm_gmtoff(time, -28800)) {
|
||||
EXPECT_EQ(fmt::format(fmt::runtime("{:%z}"), time), "-0800");
|
||||
EXPECT_EQ(fmt::format(fmt::runtime("{:%Ez}"), time), "-08:00");
|
||||
EXPECT_EQ(fmt::format(fmt::runtime("{:%Oz}"), time), "-08:00");
|
||||
} else {
|
||||
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
|
||||
fmt::format_error, "no timezone");
|
||||
}
|
||||
|
||||
// Timezone formatters tests makes sense for localtime.
|
||||
#if FMT_HAS_C99_STRFTIME
|
||||
spec_list = {"%z", "%Z"};
|
||||
#else
|
||||
spec_list = {"%Z"};
|
||||
#endif
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
auto sys_output = system_strftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
|
||||
if (spec == "%z") {
|
||||
sys_output.insert(sys_output.end() - 2, 1, ':');
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
||||
// the timezone in std::tm (if it presents on platform).
|
||||
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
std::vector<std::string> tz_names = {"GMT", "UTC"};
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1)));
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm)));
|
||||
}
|
||||
|
||||
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%z}", t1), "+0000");
|
||||
EXPECT_EQ(fmt::format("{:%z}", tm), "+0000");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%Ez}", t1), "+00:00");
|
||||
EXPECT_EQ(fmt::format("{:%Ez}", tm), "+00:00");
|
||||
|
||||
EXPECT_EQ(fmt::format("{:%Oz}", t1), "+00:00");
|
||||
EXPECT_EQ(fmt::format("{:%Oz}", tm), "+00:00");
|
||||
char tz[] = "EET";
|
||||
if (fmt::detail::set_tm_zone(time, tz)) {
|
||||
EXPECT_EQ(fmt::format(fmt::runtime("{:%Z}"), time), "EET");
|
||||
} else {
|
||||
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
|
||||
fmt::format_error, "no timezone");
|
||||
}
|
||||
}
|
||||
|
||||
#if FMT_USE_LOCAL_TIME
|
||||
|
||||
TEST(chrono_test, localtime) {
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
|
||||
TEST(chrono_test, daylight_savings_time_end) {
|
||||
// 2024-10-27 03:05 as the number of seconds since epoch in Europe/Kyiv time.
|
||||
// It is slightly after the DST end and passing it to to_sys will result in
|
||||
// an ambiguous time error:
|
||||
// 2024-10-27 03:05:00 is ambiguous. It could be
|
||||
// 2024-10-27 03:05:00 EEST == 2024-10-27 00:05:00 UTC or
|
||||
// 2024-10-27 03:05:00 EET == 2024-10-27 01:05:00 UTC
|
||||
auto t =
|
||||
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(1729998300));
|
||||
EXPECT_EQ(fmt::format("{}", t), "2024-10-27 03:05:00");
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string {
|
||||
auto t = std::chrono::system_clock::to_time_t(
|
||||
std::chrono::current_zone()->to_sys(tp));
|
||||
auto tm = *std::localtime(&t);
|
||||
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
|
||||
}
|
||||
|
||||
TEST(chrono_test, local_system_clock_time_point) {
|
||||
# ifdef _WIN32
|
||||
return; // Not supported on Windows.
|
||||
# endif
|
||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
|
||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
|
||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
|
||||
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
|
||||
using time_point = std::chrono::local_time<std::chrono::seconds>;
|
||||
auto t2 = time_point(std::chrono::seconds(86400 + 42));
|
||||
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
|
||||
|
||||
std::vector<std::string> spec_list = {
|
||||
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
|
||||
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
|
||||
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
|
||||
"%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"};
|
||||
# ifndef _WIN32
|
||||
// Disabled on Windows because these formats are not consistent among
|
||||
// platforms.
|
||||
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
|
||||
# elif !FMT_HAS_C99_STRFTIME
|
||||
// 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(
|
||||
std::chrono::current_zone()->to_sys(t1));
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
auto sys_output = system_strftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format("{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
|
||||
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
|
||||
spec_list.cend()) {
|
||||
auto t = std::chrono::system_clock::to_time_t(
|
||||
std::chrono::current_zone()->to_sys(t1));
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
auto sys_output = system_strftime("%z", &tm);
|
||||
sys_output.insert(sys_output.end() - 2, 1, ':');
|
||||
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
|
||||
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
|
||||
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FMT_USE_LOCAL_TIME
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
TEST(chrono_test, format_default) {
|
||||
EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s");
|
||||
EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)),
|
||||
@ -606,12 +553,12 @@ auto format_tm(const std::tm& time, fmt::string_view spec,
|
||||
TEST(chrono_test, locale) {
|
||||
auto loc = get_locale("ja_JP.utf8");
|
||||
if (loc == std::locale::classic()) return;
|
||||
# define EXPECT_TIME(spec, time, duration) \
|
||||
{ \
|
||||
auto jp_loc = std::locale("ja_JP.utf8"); \
|
||||
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
||||
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
||||
}
|
||||
#define EXPECT_TIME(spec, time, duration) \
|
||||
{ \
|
||||
auto jp_loc = std::locale("ja_JP.utf8"); \
|
||||
EXPECT_EQ(format_tm(time, spec, jp_loc), \
|
||||
fmt::format(jp_loc, "{:L" spec "}", duration)); \
|
||||
}
|
||||
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
|
||||
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
|
||||
EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
|
||||
@ -766,8 +713,8 @@ TEST(chrono_test, weekday) {
|
||||
if (loc != std::locale::classic()) {
|
||||
auto saturdays = std::vector<std::string>{"sáb", "sá.", "sáb."};
|
||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat)));
|
||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat)));
|
||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm)));
|
||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", sat)));
|
||||
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", tm)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -817,11 +764,11 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
|
||||
}
|
||||
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(-123456789)), "-00.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(9123456789)), "09.123456789");
|
||||
// Verify that only the seconds part is extracted and printed.
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123456789)), "39.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123000000)), "39.123000000");
|
||||
{
|
||||
// Now the hour is printed, and we also test if negative doubles work.
|
||||
auto dur = nanoseconds_dbl{-99123456789};
|
||||
@ -832,7 +779,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
}
|
||||
// Check that durations with precision greater than std::chrono::seconds have
|
||||
// fixed precision, and print zeros even if there is no fractional part.
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds(7000000)),
|
||||
"07.000000");
|
||||
EXPECT_EQ(fmt::format("{:%S}",
|
||||
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
|
||||
@ -852,14 +799,12 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
|
||||
"-05:27.68");
|
||||
|
||||
// Check that floating point seconds with ratio<1,1> are printed.
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}),
|
||||
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>(1.5)),
|
||||
"01.500000");
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
|
||||
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>(-61.25)),
|
||||
"-01:01.250000");
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
// Disable the utc_clock test for windows, as the icu.dll used for tzdb
|
||||
// (time zone database) is not shipped with many windows versions.
|
||||
#if FMT_USE_UTC_TIME && !defined(_WIN32)
|
||||
@ -932,19 +877,11 @@ TEST(chrono_test, timestamp_sub_seconds) {
|
||||
auto t8 =
|
||||
sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789));
|
||||
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
|
||||
EXPECT_EQ(fmt::format("{:%T}", t8), "00:00:00.123456789");
|
||||
|
||||
auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::now());
|
||||
auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
|
||||
auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
|
||||
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
||||
fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
|
||||
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
|
||||
fmt::format("{:%Y-%m-%d %T}", t9));
|
||||
|
||||
auto t10 =
|
||||
auto t9 =
|
||||
sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000));
|
||||
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
|
||||
EXPECT_EQ(fmt::format("{:%S}", t9), "02.000");
|
||||
|
||||
auto epoch = sys_time<std::chrono::milliseconds>();
|
||||
auto d = std::chrono::milliseconds(250);
|
||||
@ -1033,7 +970,7 @@ TEST(chrono_test, glibc_extensions) {
|
||||
{
|
||||
auto t = std::tm();
|
||||
t.tm_year = -5 - 1900;
|
||||
EXPECT_EQ(fmt::format( "{:%Y}", t), "-005");
|
||||
EXPECT_EQ(fmt::format("{:%Y}", t), "-005");
|
||||
EXPECT_EQ(fmt::format("{:%_Y}", t), " -5");
|
||||
EXPECT_EQ(fmt::format("{:%-Y}", t), "-5");
|
||||
}
|
||||
@ -1045,8 +982,6 @@ TEST(chrono_test, glibc_extensions) {
|
||||
EXPECT_EQ(fmt::format("{:%_m}", t), " 7");
|
||||
EXPECT_EQ(fmt::format("{:%-m}", t), "7");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
TEST(chrono_test, out_of_range) {
|
||||
@ -1083,6 +1018,6 @@ TEST(chrono_test, year_month_day) {
|
||||
if (loc != std::locale::classic()) {
|
||||
auto months = std::vector<std::string>{"ene.", "ene"};
|
||||
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month)));
|
||||
EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month)));
|
||||
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L%b}", month)));
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,68 @@
|
||||
|
||||
#include <iterator> // std::back_inserter
|
||||
|
||||
#include "gtest-extra.h" // EXPECT_WRITE
|
||||
#include "gtest-extra.h" // EXPECT_WRITE, EXPECT_THROW_MSG
|
||||
|
||||
TEST(color_test, text_style) {
|
||||
EXPECT_FALSE(fmt::text_style().has_foreground());
|
||||
EXPECT_FALSE(fmt::text_style().has_background());
|
||||
EXPECT_FALSE(fmt::text_style().has_emphasis());
|
||||
|
||||
EXPECT_TRUE(fg(fmt::rgb(0)).has_foreground());
|
||||
EXPECT_FALSE(fg(fmt::rgb(0)).has_background());
|
||||
EXPECT_FALSE(fg(fmt::rgb(0)).has_emphasis());
|
||||
EXPECT_TRUE(bg(fmt::rgb(0)).has_background());
|
||||
EXPECT_FALSE(bg(fmt::rgb(0)).has_foreground());
|
||||
EXPECT_FALSE(bg(fmt::rgb(0)).has_emphasis());
|
||||
|
||||
EXPECT_TRUE(
|
||||
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_foreground());
|
||||
EXPECT_TRUE(
|
||||
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_background());
|
||||
EXPECT_FALSE(
|
||||
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_emphasis());
|
||||
|
||||
EXPECT_EQ(fg(fmt::rgb(0x000000)) | fg(fmt::rgb(0x000000)),
|
||||
fg(fmt::rgb(0x000000)));
|
||||
EXPECT_EQ(fg(fmt::rgb(0x00000F)) | fg(fmt::rgb(0x00000F)),
|
||||
fg(fmt::rgb(0x00000F)));
|
||||
EXPECT_EQ(fg(fmt::rgb(0xC0F000)) | fg(fmt::rgb(0x000FEE)),
|
||||
fg(fmt::rgb(0xC0FFEE)));
|
||||
|
||||
EXPECT_THROW_MSG(
|
||||
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::black),
|
||||
fmt::format_error, "can't OR a terminal color");
|
||||
EXPECT_THROW_MSG(
|
||||
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::white),
|
||||
fmt::format_error, "can't OR a terminal color");
|
||||
EXPECT_THROW_MSG(
|
||||
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::black),
|
||||
fmt::format_error, "can't OR a terminal color");
|
||||
EXPECT_THROW_MSG(
|
||||
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::white),
|
||||
fmt::format_error, "can't OR a terminal color");
|
||||
EXPECT_THROW_MSG(fg(fmt::terminal_color::black) | fg(fmt::color::black),
|
||||
fmt::format_error, "can't OR a terminal color");
|
||||
EXPECT_THROW_MSG(bg(fmt::terminal_color::black) | bg(fmt::color::black),
|
||||
fmt::format_error, "can't OR a terminal color");
|
||||
|
||||
EXPECT_NO_THROW(fg(fmt::terminal_color::white) |
|
||||
bg(fmt::terminal_color::white));
|
||||
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | bg(fmt::rgb(0xFFFFFF)));
|
||||
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | fmt::text_style());
|
||||
EXPECT_NO_THROW(bg(fmt::terminal_color::white) | fmt::text_style());
|
||||
}
|
||||
|
||||
TEST(color_test, format) {
|
||||
EXPECT_EQ(fmt::format(fmt::text_style(), "no style"), "no style");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 0, 0)) | fg(fmt::rgb(0, 20, 30)),
|
||||
"rgb(255,20,30)"),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||
EXPECT_EQ(
|
||||
fmt::format(fg(fmt::rgb(0, 0, 0)) | fg(fmt::rgb(0, 0, 0)), "rgb(0,0,0)"),
|
||||
"\x1b[38;2;000;000;000mrgb(0,0,0)\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
|
||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||
EXPECT_EQ(
|
||||
|
@ -421,3 +421,24 @@ TEST(compile_time_formatting_test, multibyte_fill) {
|
||||
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_USE_CONSTEXPR_STRING
|
||||
|
||||
TEST(compile_test, constexpr_format) {
|
||||
{
|
||||
constexpr auto result = []() {
|
||||
return fmt::format(FMT_COMPILE("{}"), 42) == "42";
|
||||
}();
|
||||
EXPECT_TRUE(result);
|
||||
}
|
||||
|
||||
{
|
||||
// Test with a larger string to avoid small string optimization.
|
||||
constexpr auto result = []() {
|
||||
return fmt::format(FMT_COMPILE("{:100}"), ' ') == std::string(100, ' ');
|
||||
}();
|
||||
EXPECT_TRUE(result);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FMT_USE_CONSTEXPR_STRING
|
||||
|
@ -307,7 +307,6 @@ struct slow_float {
|
||||
auto format_as(slow_float f) -> float { return f; }
|
||||
|
||||
namespace std {
|
||||
template <> struct is_floating_point<double_double> : std::true_type {};
|
||||
template <> struct numeric_limits<double_double> {
|
||||
// is_iec559 is true for double-double in libstdc++.
|
||||
static constexpr bool is_iec559 = true;
|
||||
@ -315,12 +314,13 @@ template <> struct numeric_limits<double_double> {
|
||||
static constexpr int digits10 = 33;
|
||||
};
|
||||
|
||||
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_floating_point<double_double> : std::true_type {};
|
||||
template <> struct is_floating_point<slow_float> : std::true_type {};
|
||||
template <> struct is_fast_float<slow_float> : std::false_type {};
|
||||
namespace dragonbox {
|
||||
template <> struct float_info<slow_float> {
|
||||
|
@ -481,6 +481,12 @@ TEST(memory_buffer_test, max_size_allocator_overflow) {
|
||||
EXPECT_THROW(buffer.resize(161), std::exception);
|
||||
}
|
||||
|
||||
TEST(memory_buffer_test, back_insert_iterator) {
|
||||
fmt::memory_buffer buf;
|
||||
using iterator = decltype(std::back_inserter(buf));
|
||||
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<iterator>::value);
|
||||
}
|
||||
|
||||
TEST(format_test, digits2_alignment) {
|
||||
auto p =
|
||||
fmt::detail::bit_cast<fmt::detail::uintptr_t>(fmt::detail::digits2(0));
|
||||
@ -552,6 +558,10 @@ TEST(format_test, arg_errors) {
|
||||
format_error, "argument not found");
|
||||
}
|
||||
|
||||
TEST(format_test, display_width_precision) {
|
||||
EXPECT_EQ(fmt::format("{:.5}", "🐱🐱🐱"), "🐱🐱");
|
||||
}
|
||||
|
||||
template <int N> struct test_format {
|
||||
template <typename... T>
|
||||
static auto format(fmt::string_view fmt, const T&... args) -> std::string {
|
||||
@ -582,6 +592,9 @@ TEST(format_test, named_arg) {
|
||||
EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'),
|
||||
fmt::arg("A_", "A"), fmt::arg("_1", 1)));
|
||||
EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42");
|
||||
EXPECT_EQ(fmt::format("{value:{width}}", fmt::arg("value", -42),
|
||||
fmt::arg("width", 4)),
|
||||
" -42");
|
||||
EXPECT_EQ("st",
|
||||
fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2)));
|
||||
EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2");
|
||||
@ -599,6 +612,9 @@ TEST(format_test, named_arg) {
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{a} {}"), fmt::arg("a", 2), 42),
|
||||
format_error,
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format("{a}", fmt::arg("a", 1), fmt::arg("a", 10)),
|
||||
format_error, "duplicate named arg");
|
||||
}
|
||||
|
||||
TEST(format_test, auto_arg_index) {
|
||||
@ -1079,9 +1095,6 @@ TEST(format_test, precision) {
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
|
||||
format_error, "invalid format specifier");
|
||||
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
|
||||
fmt::detail::max_value<int>()),
|
||||
format_error, "number is too big");
|
||||
EXPECT_THROW_MSG(
|
||||
(void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
|
||||
format_error, "number is too big");
|
||||
@ -1093,6 +1106,32 @@ TEST(format_test, precision) {
|
||||
EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
|
||||
}
|
||||
|
||||
TEST(format_test, large_precision) {
|
||||
// Iterator used to abort the actual output.
|
||||
struct throwing_iterator {
|
||||
auto operator=(char) -> throwing_iterator& {
|
||||
throw std::runtime_error("aborted");
|
||||
return *this;
|
||||
}
|
||||
auto operator*() -> throwing_iterator& { return *this; }
|
||||
auto operator++() -> throwing_iterator& { return *this; }
|
||||
auto operator++(int) -> throwing_iterator { return *this; }
|
||||
};
|
||||
auto it = throwing_iterator();
|
||||
|
||||
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}}"), 1.0,
|
||||
fmt::detail::max_value<int>()),
|
||||
std::runtime_error, "aborted");
|
||||
|
||||
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}e}"), 1.0,
|
||||
fmt::detail::max_value<int>() - 1),
|
||||
std::runtime_error, "aborted");
|
||||
|
||||
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:.{}e}"), 42.0,
|
||||
fmt::detail::max_value<int>()),
|
||||
format_error, "number is too big");
|
||||
}
|
||||
|
||||
TEST(format_test, utf8_precision) {
|
||||
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
|
||||
EXPECT_EQ(fmt::detail::compute_width(result), 4);
|
||||
@ -1821,54 +1860,6 @@ TEST(format_test, big_print) {
|
||||
EXPECT_WRITE(stdout, big_print(), std::string(count, 'x'));
|
||||
}
|
||||
|
||||
// Windows CRT implements _IOLBF incorrectly (full buffering).
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
# ifndef _WIN32
|
||||
TEST(format_test, line_buffering) {
|
||||
auto pipe = fmt::pipe();
|
||||
|
||||
int write_fd = pipe.write_end.descriptor();
|
||||
auto write_end = pipe.write_end.fdopen("w");
|
||||
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
|
||||
write_end.print("42\n");
|
||||
close(write_fd);
|
||||
try {
|
||||
write_end.close();
|
||||
} catch (const std::system_error&) {
|
||||
}
|
||||
|
||||
auto read_end = pipe.read_end.fdopen("r");
|
||||
std::thread reader([&]() {
|
||||
int n = 0;
|
||||
int result = fscanf(read_end.get(), "%d", &n);
|
||||
(void)result;
|
||||
EXPECT_EQ(n, 42);
|
||||
});
|
||||
|
||||
reader.join();
|
||||
}
|
||||
# endif
|
||||
|
||||
TEST(format_test, buffer_boundary) {
|
||||
auto pipe = fmt::pipe();
|
||||
|
||||
auto write_end = pipe.write_end.fdopen("w");
|
||||
setvbuf(write_end.get(), nullptr, _IOFBF, 4096);
|
||||
for (int i = 3; i < 4094; i++)
|
||||
write_end.print("{}", (i % 73) != 0 ? 'x' : '\n');
|
||||
write_end.print("{} {}", 1234, 567);
|
||||
write_end.close();
|
||||
|
||||
auto read_end = pipe.read_end.fdopen("r");
|
||||
char buf[4091] = {};
|
||||
size_t n = fread(buf, 1, sizeof(buf), read_end.get());
|
||||
EXPECT_EQ(n, sizeof(buf));
|
||||
EXPECT_STREQ(fgets(buf, sizeof(buf), read_end.get()), "1234 567");
|
||||
}
|
||||
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
struct deadlockable {
|
||||
int value = 0;
|
||||
mutable std::mutex mutex;
|
||||
@ -1982,8 +1973,8 @@ TEST(format_test, unpacked_args) {
|
||||
|
||||
constexpr char with_null[3] = {'{', '}', '\0'};
|
||||
constexpr char no_null[2] = {'{', '}'};
|
||||
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
|
||||
static constexpr const char static_no_null[2] = {'{', '}'};
|
||||
static constexpr char static_with_null[3] = {'{', '}', '\0'};
|
||||
static constexpr char static_no_null[2] = {'{', '}'};
|
||||
|
||||
TEST(format_test, compile_time_string) {
|
||||
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
|
||||
@ -2584,3 +2575,26 @@ TEST(base_test, format_byte) {
|
||||
EXPECT_EQ(s, "42");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Only defined after the test case.
|
||||
struct incomplete_type;
|
||||
extern const incomplete_type& external_instance;
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<incomplete_type> : formatter<int> {
|
||||
auto format(const incomplete_type& x, context& ctx) const -> appender;
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(incomplete_type_test, format) {
|
||||
EXPECT_EQ(fmt::format("{}", external_instance), "42");
|
||||
}
|
||||
|
||||
struct incomplete_type {};
|
||||
const incomplete_type& external_instance = {};
|
||||
|
||||
auto fmt::formatter<incomplete_type>::format(const incomplete_type&,
|
||||
fmt::context& ctx) const
|
||||
-> fmt::appender {
|
||||
return formatter<int>::format(42, ctx);
|
||||
}
|
||||
|
@ -8,11 +8,12 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#if !defined(__GNUC__) || __GNUC__ >= 5
|
||||
#define FMT_BUILTIN_TYPES 0
|
||||
#include "fmt/format.h"
|
||||
# define FMT_BUILTIN_TYPES 0
|
||||
# include "fmt/format.h"
|
||||
|
||||
TEST(no_builtin_types_test, format) {
|
||||
EXPECT_EQ(fmt::format("{}", 42), "42");
|
||||
EXPECT_EQ(fmt::format("{}", 42L), "42");
|
||||
}
|
||||
|
||||
TEST(no_builtin_types_test, double_is_custom_type) {
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <cstdlib> // std::exit
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
@ -513,4 +514,79 @@ TEST(file_test, fdopen) {
|
||||
int read_fd = pipe.read_end.descriptor();
|
||||
EXPECT_EQ(read_fd, FMT_POSIX(fileno(pipe.read_end.fdopen("r").get())));
|
||||
}
|
||||
|
||||
// Windows CRT implements _IOLBF incorrectly (full buffering).
|
||||
# ifndef _WIN32
|
||||
TEST(file_test, line_buffering) {
|
||||
auto pipe = fmt::pipe();
|
||||
|
||||
int write_fd = pipe.write_end.descriptor();
|
||||
auto write_end = pipe.write_end.fdopen("w");
|
||||
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
|
||||
write_end.print("42\n");
|
||||
close(write_fd);
|
||||
try {
|
||||
write_end.close();
|
||||
} catch (const std::system_error&) {
|
||||
}
|
||||
|
||||
auto read_end = pipe.read_end.fdopen("r");
|
||||
std::thread reader([&]() {
|
||||
int n = 0;
|
||||
int result = fscanf(read_end.get(), "%d", &n);
|
||||
(void)result;
|
||||
EXPECT_EQ(n, 42);
|
||||
});
|
||||
|
||||
reader.join();
|
||||
}
|
||||
# endif // _WIN32
|
||||
|
||||
TEST(file_test, buffer_boundary) {
|
||||
auto pipe = fmt::pipe();
|
||||
|
||||
auto write_end = pipe.write_end.fdopen("w");
|
||||
setvbuf(write_end.get(), nullptr, _IOFBF, 4096);
|
||||
for (int i = 3; i < 4094; i++)
|
||||
write_end.print("{}", (i % 73) != 0 ? 'x' : '\n');
|
||||
write_end.print("{} {}", 1234, 567);
|
||||
write_end.close();
|
||||
|
||||
auto read_end = pipe.read_end.fdopen("r");
|
||||
char buf[4091] = {};
|
||||
size_t n = fread(buf, 1, sizeof(buf), read_end.get());
|
||||
EXPECT_EQ(n, sizeof(buf));
|
||||
EXPECT_STREQ(fgets(buf, sizeof(buf), read_end.get()), "1234 567");
|
||||
}
|
||||
|
||||
TEST(file_test, io_putting) {
|
||||
auto pipe = fmt::pipe();
|
||||
auto read_end = pipe.read_end.fdopen("r");
|
||||
auto write_end = pipe.write_end.fdopen("w");
|
||||
|
||||
size_t read_size = 0;
|
||||
auto reader = std::thread([&]() {
|
||||
size_t n = 0;
|
||||
do {
|
||||
char buf[4096] = {};
|
||||
n = fread(buf, 1, sizeof(buf), read_end.get());
|
||||
read_size += n;
|
||||
} while (n != 0);
|
||||
});
|
||||
|
||||
// This initialize buffers but doesn't set _IO_CURRENTLY_PUTTING.
|
||||
fseek(write_end.get(), 0, SEEK_SET);
|
||||
|
||||
size_t write_size = 0;
|
||||
for (int i = 0; i <= 20000; ++i) {
|
||||
auto s = fmt::format("{}\n", i);
|
||||
fmt::print(write_end.get(), "{}", s);
|
||||
write_size += s.size();
|
||||
}
|
||||
|
||||
write_end.close();
|
||||
reader.join();
|
||||
EXPECT_EQ(read_size, write_size);
|
||||
}
|
||||
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
@ -232,9 +232,12 @@ auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
|
||||
return t.get<N>();
|
||||
}
|
||||
|
||||
// https://github.com/llvm/llvm-project/issues/39218
|
||||
FMT_PRAGMA_CLANG(diagnostic ignored "-Wmismatched-tags")
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {};
|
||||
struct tuple_size<tuple_like> : public std::integral_constant<size_t, 2> {};
|
||||
|
||||
template <size_t N> struct tuple_element<N, tuple_like> {
|
||||
using type = decltype(std::declval<tuple_like>().get<N>());
|
||||
@ -327,7 +330,7 @@ template <typename T> class noncopyable_range {
|
||||
explicit noncopyable_range(Args&&... args)
|
||||
: vec(std::forward<Args>(args)...) {}
|
||||
|
||||
noncopyable_range(noncopyable_range const&) = delete;
|
||||
noncopyable_range(const noncopyable_range&) = delete;
|
||||
noncopyable_range(noncopyable_range&) = delete;
|
||||
|
||||
auto begin() -> iterator { return vec.begin(); }
|
||||
@ -421,7 +424,7 @@ TEST(ranges_test, join_tuple) {
|
||||
auto t5 = tuple_like{42, "foo"};
|
||||
EXPECT_EQ(fmt::format("{}", fmt::join(t5, ", ")), "42, foo");
|
||||
|
||||
# if FMT_TUPLE_JOIN_SPECIFIERS
|
||||
#if FMT_TUPLE_JOIN_SPECIFIERS
|
||||
// Specs applied to each element.
|
||||
auto t5 = std::tuple<int, int, long>(-3, 100, 1);
|
||||
EXPECT_EQ(fmt::format("{:+03}", fmt::join(t5, ", ")), "-03, +100, +01");
|
||||
@ -434,7 +437,7 @@ TEST(ranges_test, join_tuple) {
|
||||
int y = -1;
|
||||
auto t7 = std::tuple<int, int&, const int&>(3, y, y);
|
||||
EXPECT_EQ(fmt::format("{:03}", fmt::join(t7, ", ")), "003, -01, -01");
|
||||
# endif
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ranges_test, join_initializer_list) {
|
||||
@ -454,7 +457,7 @@ struct zstring {
|
||||
auto end() const -> zstring_sentinel { return {}; }
|
||||
};
|
||||
|
||||
# ifdef __cpp_lib_ranges
|
||||
#ifdef __cpp_lib_ranges
|
||||
struct cpp20_only_range {
|
||||
struct iterator {
|
||||
int val = 0;
|
||||
@ -484,7 +487,7 @@ struct cpp20_only_range {
|
||||
};
|
||||
|
||||
static_assert(std::input_iterator<cpp20_only_range::iterator>);
|
||||
# endif
|
||||
#endif
|
||||
|
||||
TEST(ranges_test, join_sentinel) {
|
||||
auto hello = zstring{"hello"};
|
||||
@ -512,13 +515,13 @@ TEST(ranges_test, join_range) {
|
||||
const auto z = std::vector<int>(3u, 0);
|
||||
EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
|
||||
|
||||
# ifdef __cpp_lib_ranges
|
||||
#ifdef __cpp_lib_ranges
|
||||
EXPECT_EQ(fmt::format("{}", cpp20_only_range{.lo = 0, .hi = 5}),
|
||||
"[0, 1, 2, 3, 4]");
|
||||
EXPECT_EQ(
|
||||
fmt::format("{}", fmt::join(cpp20_only_range{.lo = 0, .hi = 5}, ",")),
|
||||
"0,1,2,3,4");
|
||||
# endif
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace adl {
|
||||
|
53
test/scan.h
53
test/scan.h
@ -229,6 +229,8 @@ enum class scan_type {
|
||||
uint_type,
|
||||
long_long_type,
|
||||
ulong_long_type,
|
||||
double_type,
|
||||
float_type,
|
||||
string_type,
|
||||
string_view_type,
|
||||
custom_type
|
||||
@ -251,6 +253,8 @@ template <typename Context> class basic_scan_arg {
|
||||
unsigned* uint_value_;
|
||||
long long* long_long_value_;
|
||||
unsigned long long* ulong_long_value_;
|
||||
double* double_value_;
|
||||
float* float_value_;
|
||||
std::string* string_;
|
||||
string_view* string_view_;
|
||||
detail::custom_scan_arg<Context> custom_;
|
||||
@ -276,6 +280,10 @@ template <typename Context> class basic_scan_arg {
|
||||
: type_(scan_type::long_long_type), long_long_value_(&value) {}
|
||||
FMT_CONSTEXPR basic_scan_arg(unsigned long long& value)
|
||||
: type_(scan_type::ulong_long_type), ulong_long_value_(&value) {}
|
||||
FMT_CONSTEXPR basic_scan_arg(double& value)
|
||||
: type_(scan_type::double_type), double_value_(&value) {}
|
||||
FMT_CONSTEXPR basic_scan_arg(float& value)
|
||||
: type_(scan_type::float_type), float_value_(&value) {}
|
||||
FMT_CONSTEXPR basic_scan_arg(std::string& value)
|
||||
: type_(scan_type::string_type), string_(&value) {}
|
||||
FMT_CONSTEXPR basic_scan_arg(string_view& value)
|
||||
@ -305,6 +313,10 @@ template <typename Context> class basic_scan_arg {
|
||||
return vis(*long_long_value_);
|
||||
case scan_type::ulong_long_type:
|
||||
return vis(*ulong_long_value_);
|
||||
case scan_type::double_type:
|
||||
return vis(*double_value_);
|
||||
case scan_type::float_type:
|
||||
return vis(*float_value_);
|
||||
case scan_type::string_type:
|
||||
return vis(*string_);
|
||||
case scan_type::string_view_type:
|
||||
@ -457,6 +469,47 @@ auto read(scan_iterator it, T& value, const format_specs& specs = {})
|
||||
return it;
|
||||
}
|
||||
|
||||
auto read(scan_iterator it, double& value, const format_specs& = {})
|
||||
-> scan_iterator {
|
||||
if (it == scan_sentinel()) return it;
|
||||
|
||||
// Simple floating-point parsing
|
||||
bool negative = *it == '-';
|
||||
if (negative) {
|
||||
++it;
|
||||
if (it == scan_sentinel()) report_error("invalid input");
|
||||
}
|
||||
|
||||
double result = 0.0;
|
||||
// Parse integer part
|
||||
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
|
||||
result = result * 10.0 + (*it - '0');
|
||||
++it;
|
||||
}
|
||||
|
||||
// Parse decimal part if present
|
||||
if (it != scan_sentinel() && *it == '.') {
|
||||
++it;
|
||||
double fraction = 0.1;
|
||||
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
|
||||
result += (*it - '0') * fraction;
|
||||
fraction *= 0.1;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
value = negative ? -result : result;
|
||||
return it;
|
||||
}
|
||||
|
||||
auto read(scan_iterator it, float& value, const format_specs& specs = {})
|
||||
-> scan_iterator {
|
||||
double temp;
|
||||
it = read(it, temp, specs);
|
||||
value = static_cast<float>(temp);
|
||||
return it;
|
||||
}
|
||||
|
||||
auto read(scan_iterator it, std::string& value, const format_specs& = {})
|
||||
-> scan_iterator {
|
||||
while (it != scan_sentinel() && *it != ' ') value.push_back(*it++);
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fmt/os.h" // fmt::system_category
|
||||
#include "fmt/os.h" // fmt::system_category
|
||||
#include "gtest-extra.h" // StartsWith
|
||||
|
||||
#ifdef __cpp_lib_filesystem
|
||||
@ -49,7 +49,7 @@ TEST(std_test, path) {
|
||||
}
|
||||
|
||||
// Intentionally delayed include to test #4303
|
||||
#include "fmt/ranges.h"
|
||||
# include "fmt/ranges.h"
|
||||
|
||||
// Test ambiguity problem described in #2954.
|
||||
TEST(ranges_std_test, format_vector_path) {
|
||||
@ -276,18 +276,18 @@ TEST(std_test, variant) {
|
||||
|
||||
TEST(std_test, error_code) {
|
||||
auto& generic = std::generic_category();
|
||||
EXPECT_EQ("generic:42",
|
||||
fmt::format(FMT_STRING("{0}"), std::error_code(42, generic)));
|
||||
EXPECT_EQ(" generic:42",
|
||||
fmt::format(FMT_STRING("{:>12}"), std::error_code(42, generic)));
|
||||
EXPECT_EQ("generic:42 ",
|
||||
fmt::format(FMT_STRING("{:12}"), std::error_code(42, generic)));
|
||||
EXPECT_EQ("system:42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(42, fmt::system_category())));
|
||||
EXPECT_EQ("system:-42",
|
||||
fmt::format(FMT_STRING("{0}"),
|
||||
std::error_code(-42, fmt::system_category())));
|
||||
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
|
||||
EXPECT_EQ(fmt::format("{:>12}", std::error_code(42, generic)),
|
||||
" generic:42");
|
||||
EXPECT_EQ(fmt::format("{:12}", std::error_code(42, generic)), "generic:42 ");
|
||||
EXPECT_EQ(fmt::format("{}", std::error_code(42, fmt::system_category())),
|
||||
"system:42");
|
||||
EXPECT_EQ(fmt::format("{}", std::error_code(-42, fmt::system_category())),
|
||||
"system:-42");
|
||||
auto ec = std::make_error_code(std::errc::value_too_large);
|
||||
EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
|
||||
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
|
||||
"\"generic:42\"");
|
||||
}
|
||||
|
||||
template <typename Catch> void exception_test() {
|
||||
@ -383,11 +383,12 @@ TEST(std_test, format_atomic) {
|
||||
|
||||
#ifdef __cpp_lib_atomic_flag_test
|
||||
TEST(std_test, format_atomic_flag) {
|
||||
std::atomic_flag f = ATOMIC_FLAG_INIT;
|
||||
std::atomic_flag f;
|
||||
(void)f.test_and_set();
|
||||
EXPECT_EQ(fmt::format("{}", f), "true");
|
||||
|
||||
const std::atomic_flag cf = ATOMIC_FLAG_INIT;
|
||||
f.clear();
|
||||
const std::atomic_flag& cf = f;
|
||||
EXPECT_EQ(fmt::format("{}", cf), "false");
|
||||
}
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
@ -412,5 +413,31 @@ TEST(std_test, format_shared_ptr) {
|
||||
|
||||
TEST(std_test, format_reference_wrapper) {
|
||||
int num = 35;
|
||||
EXPECT_EQ("35", fmt::to_string(std::cref(num)));
|
||||
EXPECT_EQ(fmt::to_string(std::cref(num)), "35");
|
||||
EXPECT_EQ(fmt::to_string(std::ref(num)), "35");
|
||||
EXPECT_EQ(fmt::format("{}", std::cref(num)), "35");
|
||||
EXPECT_EQ(fmt::format("{}", std::ref(num)), "35");
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/fmtlib/fmt/issues/4424.
|
||||
struct type_with_format_as {};
|
||||
int format_as(type_with_format_as) { return 20; }
|
||||
|
||||
TEST(std_test, format_reference_wrapper_with_format_as) {
|
||||
type_with_format_as t;
|
||||
EXPECT_EQ(fmt::to_string(std::cref(t)), "20");
|
||||
EXPECT_EQ(fmt::to_string(std::ref(t)), "20");
|
||||
EXPECT_EQ(fmt::format("{}", std::cref(t)), "20");
|
||||
EXPECT_EQ(fmt::format("{}", std::ref(t)), "20");
|
||||
}
|
||||
|
||||
struct type_with_format_as_string {};
|
||||
std::string format_as(type_with_format_as_string) { return "foo"; }
|
||||
|
||||
TEST(std_test, format_reference_wrapper_with_format_as_string) {
|
||||
type_with_format_as_string t;
|
||||
EXPECT_EQ(fmt::to_string(std::cref(t)), "foo");
|
||||
EXPECT_EQ(fmt::to_string(std::ref(t)), "foo");
|
||||
EXPECT_EQ(fmt::format("{}", std::cref(t)), "foo");
|
||||
EXPECT_EQ(fmt::format("{}", std::ref(t)), "foo");
|
||||
}
|
||||
|
@ -171,6 +171,13 @@ TEST(xchar_test, join) {
|
||||
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_byte
|
||||
TEST(xchar_test, join_bytes) {
|
||||
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
|
||||
EXPECT_EQ(fmt::format(L"{}", fmt::join(v, L", ")), L"1, 2, 3");
|
||||
}
|
||||
#endif
|
||||
|
||||
enum streamable_enum {};
|
||||
|
||||
std::wostream& operator<<(std::wostream& os, streamable_enum) {
|
||||
@ -224,106 +231,9 @@ TEST(xchar_test, chrono) {
|
||||
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
|
||||
EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
|
||||
EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
|
||||
}
|
||||
|
||||
std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
|
||||
std::locale* locptr = nullptr) {
|
||||
auto loc = locptr ? *locptr : std::locale::classic();
|
||||
auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
|
||||
std::wostringstream os;
|
||||
os.imbue(loc);
|
||||
facet.put(os, os, L' ', timeptr, format.c_str(),
|
||||
format.c_str() + format.size());
|
||||
#ifdef _WIN32
|
||||
// Workaround a bug in older versions of Universal CRT.
|
||||
auto str = os.str();
|
||||
if (str == L"-0000") str = L"+0000";
|
||||
return str;
|
||||
#else
|
||||
return os.str();
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(chrono_test_wchar, time_point) {
|
||||
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now());
|
||||
|
||||
std::vector<std::wstring> spec_list = {
|
||||
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
|
||||
L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U",
|
||||
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
|
||||
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"};
|
||||
#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 !FMT_HAS_C99_STRFTIME
|
||||
// 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"};
|
||||
#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);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
auto sys_output = system_wcsftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
}
|
||||
|
||||
// Timezone formatters tests makes sense for localtime.
|
||||
#if FMT_HAS_C99_STRFTIME
|
||||
spec_list = {L"%z", L"%Z"};
|
||||
#else
|
||||
spec_list = {L"%Z"};
|
||||
#endif
|
||||
for (const auto& spec : spec_list) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
auto sys_output = system_wcsftime(spec, &tm);
|
||||
|
||||
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
|
||||
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
|
||||
|
||||
if (spec == L"%z") {
|
||||
sys_output.insert(sys_output.end() - 2, 1, L':');
|
||||
EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
|
||||
EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
|
||||
}
|
||||
}
|
||||
|
||||
// Separate tests for UTC, since std::time_put can use local time and ignoring
|
||||
// the timezone in std::tm (if it presents on platform).
|
||||
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
|
||||
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
|
||||
}
|
||||
|
||||
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
|
||||
auto t = std::chrono::system_clock::to_time_t(t1);
|
||||
auto tm = *std::gmtime(&t);
|
||||
|
||||
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
|
||||
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
|
||||
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
|
||||
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
|
||||
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
|
||||
}
|
||||
auto t = fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
|
||||
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t), "1979-03-12 12:00:00");
|
||||
}
|
||||
|
||||
TEST(xchar_test, color) {
|
||||
|
Reference in New Issue
Block a user