Squashed 'external/fmt/' changes from 093b39ca5..353bd895a

353bd895a Add FMT_EXPORT on ranges.h customization points (#4476)
953cffa70 Replace memset with constexpr fill_n in bigint::align (#4471)
571c02d47 Add xchar support for std::byte formatter (#4480)
f4345467f Fix compilation on clang-21 / libc++-21 (#4477)
1ef834807 Properly constrain `detail::copy` optimization (#4474)
a5dccffa5 Add double and float support to scan test
4a149f513 Test non-SSO constexpr string formatting
067bc479b Avoid redundant work when processing UTF-8 strings (#4475)
730fd4d9a Remove redundant tests
5860688d7 Enable constexpr support for fmt::format (fmtlib#3403) (#4456)
46be88bc1 Cleanup FP formatting
cc8891490 Export fmt::dynamic_format_arg_store in fmt module (#4459)
fc0c76a07 Handle large precision
6332a3852 Bump ossf/scorecard-action from 2.4.0 to 2.4.2 (#4462)
02de29e00 Remove a reference to a compromised account
6d51c78c1 Cleanup FP formatting
0f4e9d0bd Cleanup FP formatting
d9d50495a Optimize the default FP formatting
befbc5fdb Fix ADL lookup for memory_buffer
8aa1d6a9f Minor cleanup
6d79757a3 Interpret precision as display width (#4443)
1ff0b7f5e Cleanup warning suppression
ea985e84f Remove some implicit conversions (#4447)
f7033da09 Avoid include locale inline if C++20 modules are enabled (#4451)
b723c021d Give useful error when misusing fmt::ptr. (#4453)
3ba3c390f Clarify that formatting of pointers is disallowed
ab161a71c Fix some typos in comments (#4448)
b5266fd3b Remove some redundant `const`s (#4445)
9b0ebd443 Cleanup base-test
7af94e559 Remove old gcc workaround
2924fcf8f Cleanup base-test
102752ad4 Update docs
a6cd72c9e Cleanup base-test
07885271a Minor cleanup
4999416e5 Fix reference_wrapper ambiguity with format_as (#4434)
55a8f6a4b Change component prefix for NSIS compatibility (#4442)
eb9a95d42 Clarify that formatting of pointers is disallowed
d5c33e4f4 Make template parameter order consistent
a2225f288 Remove unused include
b43b2f953 Cleanup standard formatters
1312b4a16 Cleanup standard formatters
4404dc05d Consolidate implementation details
7bb6fcb32 Bump version
59259a5fd Make a doc directory if it doesn't exist
542ea7c40 Clarify that Formatter parameter is deprecated
40626af88 Update version
7fdd6846b Bump version
6caff7ed9 Cleanup test
71a548387 Update changelog
448929d49 Update and apply clang-format
26d87edab Bump github/codeql-action from 3.28.13 to 3.28.16 (#4432)
505ee058f Update changelog
ccab41719 Update changelog
ec1349d34 Update changelog
0ed2a65a8 Clarify why we use __builtin_strlen instead of strlen
e22c94307 Update changelog
b252bad3c Update changelog
268083123 Cleanun string_view
8978ab09b Avoiding __builtin_strlen (#4429)
c936e2e44 Implement debug format for error_code
a7d7b894c Implement the s specifier for error_code
e98155a6f Remove redundant specializations
41b3bed4d Clarify why we don't use qualified names
67d9e4932 Update changelog
9db5e4df2 Don't specialize std::is_floating_point
906eaf2dd Make specifier order consistent
9f6c12c3d Remove deprecated localtime from docs
2d0518b5f Fix cmake error in pedantic mode (#4426)
c81cbed2b Simplify test
c7925241c Remove `core.h` from README (#4422)
c70913835 Add support for incomplete types
db405954c Remove `fmt/core.h` from docs (#4421)
0a917ee2f Minor comment tweak
969d4aef6 Update doc image
8061c7c8c Cleanup duration formatter
7b59df411 Remove redundant member
b8192d233 Fix build error with MSVC v141 (#4413)
e814b5fab Reduce template parametrization
ed0d216f7 Fix localization and formatting of timezone names
bd9554a29 Fix formatting of timezone names
f086dc0d2 Fix timezone handling in tm
f10b6dd81 Improve chrono formatting
f470b9c56 Cleanup chrono tests and set consistent TZ
b28214487 Fix handling of %Z
6d69f0c5f Improve chorno tests
da776c9a6 Test timezone
64db979e3 Added a missing FMT_STRING in fmt::println() (#4407)
5f2e61fdd Cleanup chrono detail
b3d45e1d3 Remove fmt_detail
5f6fb96df Bump github/codeql-action from 3.28.8 to 3.28.13 (#4403)
5199e0f88 Fix a flush issue on libstdc++
2f5843057 Move buffering tests to os-test
d5d32c1e8 Bazel support: Update platforms to 0.0.11 (#4400)
204661287 Improve local_time test
e1ab38336 Report an error when timezone is not available
b9e0e94a0 Enable more chrono tests on Windows
a81842428 Update changelog
f53055efe Revert "Workaround an ABI issue in spdlog"
b2dfcb2b8 Fix local_time test
7ac97cbd1 Enable some local_time tests and make them deterministic
17898794a Use fmt::local_time
443a8ef34 Deprecate fmt::localtime
3607e92dc Bump version
43e31614c Test ambiguous time
989826ce5 Update changelog
9d6e24c64 Fix handling of long with FMT_BUILTIN_TYPES=0
0843317e0 Update changelog
784eac839 Workaround an ABI issue in spdlog
6fdf225a3 Always inline value ctors in optimized gcc mode only
332da79bf Always inline value ctors
7b273fbb5 Minor cleanup
191c504b1 Cleanup build config
d13fb6092 Cleanup build config
dd780fde4 Add clang-3.4
37e647471 Fix dynamic named arg format spec handling (#4361)
77c0fc07d Switch to supported ubuntu image
9212ff6ca Apply coding conventions and use constexpr
864bdf963 Report error on duplicate named arg names (#4367)
b776cf66f Optimize `text_style` using bit packing (#4363)
bdbf957b9 Bump msys2/setup-msys2 from 2.25.0 to 2.27.0
577fd3be8 Fix TU-local entity exposition error in GCC 15
faac8b1fa Remove exports in std.h
123913715 Update version
8c1059b92 Update changelog
4e5aafbf4 Bump version
db30fb3b8 Update changelog
3401ce2be Fix ABI compatibility
7f7695524 Fix conflict with std::ignore (#4356)
251320fcb Add .vs folder to .gitignore (#4355)
94ab51cb8 Simplify implementation of `operator""_cf` (#4349)
0ca42e836 Workaround an MSVC v140 bug
ed27df576 Replace forward slashes by backslashes in BMI path for MSVC. (#4344)
d42a068db Apply coding conventions
f2cec917d Move is_compiled_string to public API (#4342)
d5b866e24 fix gcc 8.3 compile errors (#4336)
5676e408f Bump github/codeql-action from 3.27.0 to 3.28.8 (#4337)
71d24b564 Bump actions/upload-artifact from 4.4.0 to 4.6.0 (#4339)
c9267da4d Fix typo in `FMT_HAS_BUILTIN` check
373855c1b Clarify difference in FP representation
52eeeb52a Make exponent threshold depend on representation (#3649)
9cf9f38ed Update version
4946bdb72 Update changelog
01a5b56f0 Fix error of unitialized variable FMT_HEADERS
cb6fdf219 Restore constraint on map formatter (#4326)
f841ae61e Fix #4303: avoid instantiating formatter<const T> (#4325)
a3d05d70c Silence a constexpr warning when compiling with MSVC and /W4 (#4322)
41539c29f Workaround a bug in gcc 6 (#4318)
aabe63910 Tweak changelog
f90090be2 Update changelog
9ff9c695d Bump version
06ad1224e Update changelog
5f0572acd Workaround a compilation error on gcc 9.4
898d43857 Fix formatting into std::ostreambuf_iterator using a compiled format (#4312)
937b7c5c1 Add args() accessor back to fmt::format_context (#4310)
01914f038 Reduce size of basic_specs
c43da3570 Workaround an ICE when using modules with gcc 14.2 and earlier
8303d140a Update version
b0b3dc5ff Bump version
586ea06f0 Rename set_fill to copy_fill_from
5750f434f Update changelog
bfbdc2be9 Add parameter to the fallback to_sys function.
87e007267 Update changelog
d57040f94 Prefix components
21aa0956d Restore ABI compatibility
3f864a450 Address MSVC C4127 warning when formatting non unicode `tm` (#4299)

git-subtree-dir: external/fmt
git-subtree-split: 353bd895a2bf9d0b1bc5977dc002fb6e0cdb0960
This commit is contained in:
irisz64
2025-06-26 22:16:38 +02:00
parent 5ff285a9ed
commit fca3b644b3
44 changed files with 2166 additions and 1597 deletions

View File

@@ -7,6 +7,7 @@ IndentCaseLabels: false
AlwaysBreakTemplateDeclarations: false
DerivePointerAlignment: false
AllowShortCaseLabelsOnASingleLine: true
QualifierAlignment: Left
AlignConsecutiveShortCaseStatements:
Enabled: true
AcrossEmptyLines: true

View File

@@ -25,7 +25,7 @@ jobs:
language: c++
- name: Upload crash
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts

View File

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

View File

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

View File

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

View File

@@ -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
@@ -52,7 +52,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: SARIF file
path: results.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@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
with:
sarif_file: results.sarif

View File

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

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
*.xcodeproj
*~
.vscode/
.vs/
/CMakeScripts
/Testing
/_CPack_Packages

View File

@@ -27,7 +27,13 @@ endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target)
if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
if(CMAKE_GENERATOR STREQUAL "Ninja")
# Ninja dyndep expects the .ifc output to be located in a specific relative path
file(RELATIVE_PATH BMI_DIR "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir")
else()
set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI})
@@ -69,8 +75,6 @@ function(add_module_library name)
target_compile_options(${name} PUBLIC -fmodules-ts)
endif ()
target_compile_definitions(${name} PRIVATE FMT_MODULE)
if (FMT_USE_CMAKE_MODULES)
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
FILES ${sources})
@@ -201,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)
@@ -295,6 +298,7 @@ function(add_headers VAR)
endfunction()
# Define the fmt library, its includes and the needed defines.
set(FMT_HEADERS)
add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
format-inl.h os.h ostream.h printf.h ranges.h std.h
xchar.h)
@@ -427,7 +431,7 @@ if (FMT_INSTALL)
# Install the library and headers.
install(TARGETS ${INSTALL_TARGETS}
COMPONENT core
COMPONENT fmt_core
EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
@@ -443,13 +447,13 @@ if (FMT_INSTALL)
# Install version, config and target files.
install(FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR}
COMPONENT core)
COMPONENT fmt_core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::
COMPONENT core)
COMPONENT fmt_core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT core)
COMPONENT fmt_core)
endif ()
function(add_doc_target)
@@ -486,7 +490,7 @@ function(add_doc_target)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT doc OPTIONAL)
COMPONENT fmt_doc OPTIONAL)
endfunction()
if (FMT_DOC)

View File

@@ -1,3 +1,187 @@
# 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
(https://github.com/fmtlib/fmt/issues/4359).
- Improved the logic of switching between fixed and exponential format for
`float` (https://github.com/fmtlib/fmt/issues/3649).
- Moved `is_compiled_string` to the public API
(https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb.
- Simplified implementation of `operator""_cf`
(https://github.com/fmtlib/fmt/pull/4349). Thanks @LocalSpook.
- Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329).
Thanks @LocalSpook.
- Fixed handling of BMI paths with the Ninja generator
(https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn.
- Fixed gcc 8.3 compile errors (https://github.com/fmtlib/fmt/issues/4331,
https://github.com/fmtlib/fmt/pull/4336). Thanks @sergiud.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/pull/4356).
Thanks @dinomight.
# 11.1.3 - 2025-01-25
- Fixed compilation on GCC 9.4 (https://github.com/fmtlib/fmt/issues/4313).
- Worked around an internal compiler error when using C++20 modules with GCC
14.2 and earlier (https://github.com/fmtlib/fmt/issues/4295).
- Worked around a bug in GCC 6 (https://github.com/fmtlib/fmt/issues/4318).
- Fixed an issue caused by instantiating `formatter<const T>`
(https://github.com/fmtlib/fmt/issues/4303,
https://github.com/fmtlib/fmt/pull/4325). Thanks @timsong-cpp.
- Fixed formatting into `std::ostreambuf_iterator` when using format string
compilation (https://github.com/fmtlib/fmt/issues/4309,
https://github.com/fmtlib/fmt/pull/4312). Thanks @phprus.
- Restored a constraint on the map formatter so that it correctly reports as
unformattable when the element is (https://github.com/fmtlib/fmt/pull/4326).
Thanks @timsong-cpp.
- Reduced the size of format specs (https://github.com/fmtlib/fmt/issues/4298).
- Readded `args()` to `fmt::format_context`
(https://github.com/fmtlib/fmt/issues/4307,
https://github.com/fmtlib/fmt/pull/4310). Thanks @Erroneous1.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/issues/4314,
https://github.com/fmtlib/fmt/pull/4322). Thanks @ZehMatt.
- Fixed a pedantic mode error in the CMake config
(https://github.com/fmtlib/fmt/pull/4327). Thanks @rlalik.
# 11.1.2 - 2025-01-12
- Fixed ABI compatibility with earlier 11.x versions
(https://github.com/fmtlib/fmt/issues/4292).
- Added `wchar_t` support to the `std::bitset` formatter
(https://github.com/fmtlib/fmt/issues/4285,
https://github.com/fmtlib/fmt/pull/4286,
https://github.com/fmtlib/fmt/issues/4289,
https://github.com/fmtlib/fmt/pull/4290). Thanks @phprus.
- Prefixed CMake components with `fmt-` to simplify usage of {fmt} via
`add_subdirectory` (https://github.com/fmtlib/fmt/issues/4283).
- Updated docs for meson (https://github.com/fmtlib/fmt/pull/4291).
Thanks @trim21.
- Fixed a compilation error in chrono on nvcc
(https://github.com/fmtlib/fmt/issues/4297,
https://github.com/fmtlib/fmt/pull/4301). Thanks @breyerml.
- Fixed various warnings
(https://github.com/fmtlib/fmt/pull/4288,
https://github.com/fmtlib/fmt/pull/4299). Thanks @GamesTrap and @edo9300.
# 11.1.1 - 2024-12-27
- Fixed ABI compatibility with earlier 11.x versions

View File

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

View File

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

View File

@@ -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"));
@@ -413,11 +415,11 @@ locale:
that take `std::locale` as a parameter. The locale type is a template
parameter to avoid the expensive `<locale>` include.
::: format(detail::locale_ref, format_string<T...>, T&&...)
::: format(const Locale&, format_string<T...>, T&&...)
::: format_to(OutputIt, detail::locale_ref, format_string<T...>, T&&...)
::: format_to(OutputIt, const Locale&, format_string<T...>, T&&...)
::: formatted_size(detail::locale_ref, format_string<T...>, T&&...)
::: formatted_size(const Locale&, format_string<T...>, T&&...)
<a id="legacy-checks"></a>
### Legacy Compile-Time Checks
@@ -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)
@@ -669,5 +669,13 @@ following differences:
- Names are defined in the `fmt` namespace instead of `std` to avoid
collisions with standard library implementations.
- Width calculation doesn't use grapheme clusterization. The latter has
been implemented in a separate branch but hasn't been integrated yet.
- The default floating-point representation in {fmt} uses the smallest
precision that provides round-trip guarantees similarly to other languages
like Java and Python. `std::format` is currently specified in terms of
`std::to_chars` which tries to generate the smallest number of characters
(ignoring redundant digits and sign in exponent) and may procude more
decimal digits than necessary.

View File

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

View File

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

View File

@@ -21,7 +21,7 @@
#endif
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 110101
#define FMT_VERSION 110201
// Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__)
@@ -96,9 +96,9 @@
// Detect C++14 relaxed constexpr.
#ifdef FMT_USE_CONSTEXPR
// Use the provided definition.
#elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L
// GCC only allows throw in constexpr since version 6:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371.
#elif FMT_GCC_VERSION >= 702 && FMT_CPLUSPLUS >= 201402L
// GCC only allows constexpr member functions in non-literal types since 7.2:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297.
# define FMT_USE_CONSTEXPR 1
#elif FMT_ICC_VERSION
# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628
@@ -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 { \
@@ -294,15 +302,8 @@
#endif
#define FMT_APPLY_VARIADIC(expr) \
using ignore = int[]; \
(void)ignore { 0, (expr, 0)... }
// Enable minimal optimizations for more compact code in debug mode.
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__)
FMT_PRAGMA_GCC(optimize("Og"))
#endif
FMT_PRAGMA_CLANG(diagnostic push)
using unused = int[]; \
(void)unused { 0, (expr, 0)... }
FMT_BEGIN_NAMESPACE
@@ -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(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value) {
size_ = __builtin_strlen(detail::narrow(s));
#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
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 {};
@@ -739,13 +738,15 @@ class basic_specs {
max_fill_size = 4
};
size_t data_ = 1 << fill_size_shift;
unsigned data_ = 1 << fill_size_shift;
static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 18, "");
// Character (code unit) type is erased to prevent template bloat.
char fill_data_[max_fill_size] = {' '};
FMT_CONSTEXPR void set_fill_size(size_t size) {
data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift);
data_ = (data_ & ~fill_size_mask) |
(static_cast<unsigned>(size) << fill_size_shift);
}
public:
@@ -843,7 +844,7 @@ class basic_specs {
fill_data_[i & 3] = static_cast<char>(s[i]);
}
FMT_CONSTEXPR void set_fill(const basic_specs& specs) {
FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) {
set_fill_size(specs.fill_size());
for (size_t i = 0; i < max_fill_size; ++i)
fill_data_[i] = specs.fill_data_[i];
@@ -1030,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 {};
@@ -1062,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;
@@ -1069,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++};
}
@@ -1082,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>;
@@ -1119,7 +1137,7 @@ using use_formatter =
bool_constant<(std::is_class<T>::value || std::is_enum<T>::value ||
std::is_union<T>::value || std::is_array<T>::value) &&
!has_to_string_view<T>::value && !is_named_arg<T>::value &&
!use_format_as<T>::value && !use_format_as_member<T>::value>;
!use_format_as<T>::value && !use_format_as_member<U>::value>;
template <typename Char, typename T, typename U = remove_const_t<T>>
auto has_formatter_impl(T* p, buffered_context<Char>* ctx = nullptr)
@@ -1660,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>
@@ -1704,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;
}
@@ -2004,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&&
@@ -2018,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 {
@@ -2027,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;
@@ -2234,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) {
@@ -2261,28 +2307,27 @@ template <> struct is_output_iterator<appender, char> : std::true_type {};
template <typename It, typename T>
struct is_output_iterator<
It, T,
void_t<decltype(*std::declval<decay_t<It>&>()++ = std::declval<T>())>>
: std::true_type {};
enable_if_t<std::is_assignable<decltype(*std::declval<decay_t<It>&>()++),
T>::value>> : std::true_type {};
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to an std::locale to avoid a heavy <locale> include.
struct locale_ref {
class locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc);
template <typename Locale> locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
public:
template <typename Locale> auto get() const -> Locale;
};
@@ -2310,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)
@@ -2344,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;
};
@@ -2656,6 +2702,7 @@ class context {
FMT_CONSTEXPR auto arg_id(string_view name) const -> int {
return args_.get_id(name);
}
auto args() const -> const format_args& { return args_; }
// Returns an iterator to the beginning of the output range.
FMT_CONSTEXPR auto out() const -> iterator { return out_; }
@@ -2701,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()));
@@ -2728,9 +2775,9 @@ template <typename... T> struct fstring {
std::is_same<typename S::char_type, char>::value)>
FMT_ALWAYS_INLINE fstring(const S&) : str(S()) {
FMT_CONSTEXPR auto sv = string_view(S());
FMT_CONSTEXPR int ignore =
FMT_CONSTEXPR int unused =
(parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore);
detail::ignore_unused(unused);
}
fstring(runtime_format_string<> fmt) : str(fmt.str) {}

View File

@@ -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.
@@ -261,7 +246,7 @@ namespace detail {
using utc_clock = std::chrono::utc_clock;
#else
struct utc_clock {
void to_sys();
template <typename T> void to_sys(T);
};
#endif
@@ -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;
};
@@ -364,7 +349,7 @@ void write_codecvt(codecvt_result<CodeUnit>& out, string_view in,
template <typename OutputIt>
auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc)
-> OutputIt {
if (detail::use_utf8 && loc != get_classic_locale()) {
if (const_check(detail::use_utf8) && loc != get_classic_locale()) {
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
// gcc-4.
#if FMT_MSC_VERSION != 0 || \
@@ -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(&gtm);
std::tm ltm = gmtime(gt);
std::time_t lt = std::mktime(&ltm);
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);
}
};

View File

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

View File

@@ -19,11 +19,11 @@ FMT_BEGIN_NAMESPACE
// A compile-time string which is compiled into fast formatting code.
FMT_EXPORT class compiled_string {};
namespace detail {
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
namespace detail {
/**
* Converts a string literal `s` into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires C++17
@@ -41,18 +41,8 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N, fmt::detail::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
constexpr explicit operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#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;
}
@@ -425,7 +415,7 @@ constexpr auto compile_format_string(S fmt) {
}
template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) {
@@ -446,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;
@@ -461,9 +451,9 @@ constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
FMT_ENABLE_IF(is_compiled_string<S>::value)>
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] == '}') {
@@ -488,7 +478,7 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
@@ -503,7 +493,7 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
#endif
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
@@ -513,7 +503,7 @@ auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
-> size_t {
auto buf = detail::counting_buffer<>();
@@ -522,7 +512,7 @@ FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) {
auto buf = memory_buffer();
fmt::format_to(appender(buf), fmt, args...);
@@ -530,7 +520,7 @@ void print(std::FILE* f, const S& fmt, const Args&... args) {
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) {
print(stdout, fmt, args...);
}
@@ -538,9 +528,7 @@ void print(const S& fmt, const Args&... args) {
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>();
return FMT_COMPILE(Str.data);
}
} // namespace literals
#endif

View File

@@ -22,7 +22,7 @@
#include "format.h"
#if FMT_USE_LOCALE
#if FMT_USE_LOCALE && !defined(FMT_MODULE)
# include <locale>
#endif
@@ -84,7 +84,7 @@ using std::locale;
using std::numpunct;
using std::use_facet;
template <typename Locale, enable_if_t<(sizeof(Locale::collate) != 0), int>>
template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, locale>::value, "");
}
@@ -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;
}

View File

@@ -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;
@@ -227,7 +256,9 @@ FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) {
#if defined(FMT_USE_STRING_VIEW)
template <typename Char> using std_string_view = std::basic_string_view<Char>;
#else
template <typename T> struct std_string_view {};
template <typename Char> struct std_string_view {
operator basic_string_view<Char>() const;
};
#endif
template <typename Char, Char... C> struct string_literal {
@@ -523,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;
}
@@ -552,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];
@@ -666,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 {};
@@ -703,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__)
@@ -719,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>;
@@ -736,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
@@ -1042,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]);
@@ -1203,7 +1219,7 @@ FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value,
}
template <typename Char, typename UInt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value)>
FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<OutputIt>>::value)>
FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits)
-> OutputIt {
if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) {
@@ -1223,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);
@@ -1485,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 {
@@ -1517,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) {}
@@ -1611,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>());
@@ -1839,7 +1862,9 @@ template <typename Char> class digit_grouping {
}
public:
explicit digit_grouping(locale_ref loc, bool localized = true) {
template <typename Locale,
FMT_ENABLE_IF(std::is_same<Locale, locale_ref>::value)>
explicit digit_grouping(Locale loc, bool localized = true) {
if (!localized) return;
auto sep = thousands_sep<Char>(loc);
grouping_ = sep.grouping;
@@ -1960,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};
@@ -2014,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;
@@ -2106,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) {
@@ -2270,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) {
@@ -2324,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,
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, exp_upper = 16;
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 {
@@ -2444,15 +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,
locale_ref loc) -> OutputIt {
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, 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, loc);
return do_write_float<Char, digit_grouping<Char>>(out, f, specs, s,
exp_upper, loc);
}
}
@@ -2467,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())
@@ -2723,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;
}
@@ -3284,9 +3345,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision,
return exp;
}
template <typename Char, typename OutputIt, typename T>
FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
locale_ref loc) -> OutputIt {
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;
// Use signbit because value < 0 is false for NaN.
sign s = detail::signbit(value) ? sign::minus : specs.sign();
@@ -3299,15 +3363,15 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
if (specs.width != 0) --specs.width;
}
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));
return write_float<Char>(out, dec, specs, s, loc);
auto dec = dragonbox::to_decimal(static_cast<fast_float_t<T>>(value));
return write_float<Char>(out, dec, specs, s, exp_upper, loc);
}
}
@@ -3335,16 +3399,7 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
specs.precision = precision;
auto f = big_decimal_fp{buffer.data(), static_cast<int>(buffer.size()), exp};
return write_float<Char>(out, f, specs, s, 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);
return write_float<Char>(out, f, specs, s, exp_upper, loc);
}
template <typename Char, typename OutputIt, typename T,
@@ -3353,23 +3408,38 @@ 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, {});
// 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>
@@ -3639,6 +3709,12 @@ FMT_CONSTEXPR auto native_formatter<T, Char, TYPE>::format(
return write<Char>(ctx.out(), val, specs, ctx.locale());
}
// DEPRECATED! https://github.com/fmtlib/fmt/issues/4292.
template <typename T, typename Enable = void>
struct is_locale : std::false_type {};
template <typename T>
struct is_locale<T, void_t<decltype(T::classic())>> : std::true_type {};
// DEPRECATED!
template <typename Char = char> struct vformat_args {
using type = basic_format_args<buffered_context<Char>>;
@@ -3805,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);
}
@@ -3830,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
@@ -3960,7 +4037,7 @@ template <typename T, typename Char = char> struct nested_formatter {
write(basic_appender<Char>(buf));
auto specs = format_specs();
specs.width = width_;
specs.set_fill(specs_);
specs.copy_fill_from(specs_);
specs.set_align(specs_.align());
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
@@ -4120,41 +4197,46 @@ FMT_API void format_system_error(detail::buffer<char>& out, int error_code,
// Can be used to report errors from destructors.
FMT_API void report_system_error(int error_code, const char* message) noexcept;
inline auto vformat(detail::locale_ref loc, string_view fmt, format_args args)
template <typename Locale, FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
inline auto vformat(const Locale& loc, string_view fmt, format_args args)
-> std::string {
auto buf = memory_buffer();
detail::vformat_to(buf, fmt, args, loc);
detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));
return {buf.data(), buf.size()};
}
template <typename... T>
FMT_INLINE auto format(detail::locale_ref loc, format_string<T...> fmt,
T&&... args) -> std::string {
template <typename Locale, typename... T,
FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
FMT_INLINE auto format(const Locale& loc, format_string<T...> fmt, T&&... args)
-> std::string {
return vformat(loc, fmt.str, vargs<T...>{{args...}});
}
template <typename OutputIt,
template <typename OutputIt, typename Locale,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, detail::locale_ref loc, string_view fmt,
auto vformat_to(OutputIt out, const Locale& loc, string_view fmt,
format_args args) -> OutputIt {
auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, fmt, args, loc);
detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
FMT_INLINE auto format_to(OutputIt out, detail::locale_ref loc,
template <typename OutputIt, typename Locale, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value&&
detail::is_locale<Locale>::value)>
FMT_INLINE auto format_to(OutputIt out, const Locale& loc,
format_string<T...> fmt, T&&... args) -> OutputIt {
return fmt::vformat_to(out, loc, fmt.str, vargs<T...>{{args...}});
}
template <typename... T>
FMT_NODISCARD FMT_INLINE auto formatted_size(detail::locale_ref loc,
template <typename Locale, typename... T,
FMT_ENABLE_IF(detail::is_locale<Locale>::value)>
FMT_NODISCARD FMT_INLINE auto formatted_size(const Locale& loc,
format_string<T...> fmt,
T&&... args) -> size_t {
auto buf = detail::counting_buffer<>();
detail::vformat_to(buf, fmt.str, vargs<T...>{{args...}}, loc);
detail::vformat_to(buf, fmt.str, vargs<T...>{{args...}},
detail::locale_ref(loc));
return buf.count();
}
@@ -4183,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)];
@@ -4191,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()};

View File

@@ -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
@@ -150,7 +150,7 @@ inline void vprint(std::ostream& os, string_view fmt, format_args args) {
FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::vargs<T...> vargs = {{args...}};
if (detail::use_utf8) return vprint(os, fmt.str, vargs);
if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt.str, vargs);
detail::write_buffer(os, buffer);
@@ -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

View File

@@ -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;
@@ -527,7 +528,9 @@ struct formatter<
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
enable_if_t<conjunction<
bool_constant<range_format_kind<R, Char>::value == range_format::map>,
detail::is_formattable_delayed<R, Char>>::value>> {
private:
using map_type = detail::maybe_const_range<R>;
using element_type = detail::uncvref_type<map_type>;
@@ -668,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;
@@ -683,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>());
}
@@ -723,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;
@@ -752,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;
};
@@ -772,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);
}
};
@@ -823,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};
}

View File

@@ -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_;
@@ -696,7 +677,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto outer_specs = format_specs();
outer_specs.width = specs.width;
outer_specs.set_fill(specs);
outer_specs.copy_fill_from(specs);
outer_specs.set_align(specs.align());
specs.width = 0;
@@ -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_

View File

@@ -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};
}
@@ -191,9 +187,11 @@ auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename S, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto vformat(detail::locale_ref loc, const S& fmt,
template <typename Locale, typename S,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat(const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
@@ -202,10 +200,11 @@ inline auto vformat(detail::locale_ref loc, const S& fmt,
return {buf.data(), buf.size()};
}
template <typename S, typename... T,
template <typename Locale, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto format(detail::locale_ref loc, const S& fmt, T&&... args)
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& fmt, T&&... args)
-> std::basic_string<Char> {
return vformat(loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
@@ -232,11 +231,12 @@ inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename S, typename OutputIt, typename... Args,
template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt,
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
@@ -244,11 +244,12 @@ inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt,
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... T,
template <typename Locale, typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, detail::locale_ref loc, const S& fmt,
inline auto format_to(OutputIt out, const Locale& loc, const S& fmt,
T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(fmt),
@@ -317,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);
@@ -325,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...);
}

View File

@@ -1,5 +1,7 @@
module;
#define FMT_MODULE
#ifdef _MSVC_LANG
# define FMT_CPLUSPLUS _MSVC_LANG
#else

View File

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

View File

@@ -1 +1 @@
7.1.2
8.1.1

View File

@@ -1,3 +1,5 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "fmt",
srcs = [

View File

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

View File

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

View File

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

View File

@@ -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,16 +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) {
auto copy = [](fmt::appender app, const fmt::format_context& ctx) {
return fmt::format_context(app, ctx.args(), ctx.locale());
};
fmt::detail::ignore_unused(copy);
}

View File

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

View File

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

View File

@@ -7,6 +7,8 @@
#include "fmt/compile.h"
#include <iterator>
#include <list>
#include <type_traits>
#include <vector>
@@ -199,6 +201,21 @@ TEST(compile_test, format_to_n) {
EXPECT_STREQ("2a", buffer);
}
TEST(compile_test, output_iterators) {
std::list<char> out;
fmt::format_to(std::back_inserter(out), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", std::string(out.begin(), out.end()));
std::stringstream s;
fmt::format_to(std::ostream_iterator<char>(s), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", s.str());
std::stringstream s2;
fmt::format_to(std::ostreambuf_iterator<char>(s2), FMT_COMPILE("{}.{:06d}"),
42, 43);
EXPECT_EQ("42.000043", s2.str());
}
# if FMT_USE_CONSTEVAL && (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940)
TEST(compile_test, constexpr_formatted_size) {
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
@@ -299,6 +316,17 @@ TEST(compile_test, compile_format_string_literal) {
}
#endif
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename S> auto check_is_compiled_string(const S&) -> bool {
return fmt::is_compiled_string<S>::value;
}
TEST(compile_test, is_compiled_string) {
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("asdf")));
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("{}")));
}
#endif
// MSVS 2019 19.29.30145.0 - OK
// MSVS 2022 19.32.31332.0, 19.37.32826.1 - compile-test.cc(362,3): fatal error
// C1001: Internal compiler error.
@@ -310,7 +338,7 @@ TEST(compile_test, compile_format_string_literal) {
(FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930)) && \
defined(__cpp_lib_is_constant_evaluated)
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
template <typename T> constexpr auto operator==(const T& rhs) const -> bool {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
@@ -393,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

View File

@@ -307,19 +307,20 @@ 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;
static constexpr int digits = 106;
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> {
@@ -341,7 +342,7 @@ TEST(format_impl_test, write_dragon_even) {
auto s = std::string();
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
// Specializing is_floating_point is broken in MSVC.
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "3.355445e+07");
}
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)

View File

@@ -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) {
@@ -1068,7 +1084,8 @@ TEST(format_test, precision) {
EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123.");
EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23");
EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1019666400");
EXPECT_EQ(fmt::format("{}", 123456789.0f), "1.2345679e+08");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1.0196664e+09");
EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01");
EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34");
@@ -1078,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");
@@ -1092,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);
@@ -1820,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;
@@ -1981,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");
@@ -2102,6 +2094,10 @@ TEST(format_test, output_iterators) {
std::stringstream s;
fmt::format_to(std::ostream_iterator<char>(s), "{}", 42);
EXPECT_EQ("42", s.str());
std::stringstream s2;
fmt::format_to(std::ostreambuf_iterator<char>(s2), "{}.{:06d}", 42, 43);
EXPECT_EQ("42.000043", s2.str());
}
TEST(format_test, fill_via_appender) {
@@ -2579,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);
}

View File

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

View File

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

View File

@@ -47,6 +47,8 @@ TEST(ranges_test, format_array_of_literals) {
}
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
struct unformattable {};
TEST(ranges_test, format_vector) {
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
@@ -65,6 +67,9 @@ TEST(ranges_test, format_vector) {
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
EXPECT_FALSE(fmt::is_formattable<unformattable>::value);
EXPECT_FALSE(fmt::is_formattable<std::vector<unformattable>>::value);
}
TEST(ranges_test, format_nested_vector) {
@@ -83,6 +88,8 @@ TEST(ranges_test, format_map) {
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
EXPECT_FALSE((fmt::is_formattable<std::map<int, unformattable>>::value));
}
struct test_map_value {};
@@ -146,6 +153,7 @@ template <typename T> class flat_set {
TEST(ranges_test, format_flat_set) {
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
"{\"one\", \"two\"}");
EXPECT_FALSE(fmt::is_formattable<flat_set<unformattable>>::value);
}
namespace adl {
@@ -169,8 +177,6 @@ TEST(ranges_test, format_pair) {
EXPECT_EQ(fmt::format("{:n}", p), "421.5");
}
struct unformattable {};
TEST(ranges_test, format_tuple) {
auto t =
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
@@ -180,7 +186,6 @@ TEST(ranges_test, format_tuple) {
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value));
@@ -227,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>());
@@ -322,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(); }
@@ -416,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");
@@ -429,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) {
@@ -449,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;
@@ -479,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"};
@@ -507,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 {
@@ -655,6 +663,8 @@ TEST(ranges_test, container_adaptor) {
m.push(2);
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
}
EXPECT_FALSE(fmt::is_formattable<std::stack<unformattable>>::value);
}
struct tieable {

View File

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

View File

@@ -12,14 +12,18 @@
#include <string>
#include <vector>
#include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "fmt/os.h" // fmt::system_category
#include "gtest-extra.h" // StartsWith
#ifdef __cpp_lib_filesystem
TEST(std_test, path) {
using std::filesystem::path;
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
// see #4303
const path p = "/usr/bin";
EXPECT_EQ(fmt::format("{}", p), "/usr/bin");
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
@@ -44,6 +48,9 @@ TEST(std_test, path) {
# endif
}
// Intentionally delayed include to test #4303
# include "fmt/ranges.h"
// Test ambiguity problem described in #2954.
TEST(ranges_std_test, format_vector_path) {
auto p = std::filesystem::path("foo/bar.txt");
@@ -269,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() {
@@ -376,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
@@ -405,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");
}

View File

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