Compare commits

..

No commits in common. "master" and "11.0.1" have entirely different histories.

75 changed files with 7675 additions and 8852 deletions

View File

@ -6,10 +6,3 @@ IndentPPDirectives: AfterHash
IndentCaseLabels: false
AlwaysBreakTemplateDeclarations: false
DerivePointerAlignment: false
AllowShortCaseLabelsOnASingleLine: true
QualifierAlignment: Left
AlignConsecutiveShortCaseStatements:
Enabled: true
AcrossEmptyLines: true
AcrossComments: true
AlignCaseColons: false

View File

@ -1,4 +0,0 @@
Checks: modernize-use-trailing-return-type
CheckOptions:
- key: modernize-use-trailing-return-type.TransformLambdas
value: none

View File

@ -10,22 +10,20 @@ jobs:
steps:
- name: Build fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master
with:
oss-fuzz-project-name: 'fmt'
dry-run: false
language: c++
- name: Run fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master
with:
oss-fuzz-project-name: 'fmt'
fuzz-seconds: 300
dry-run: false
language: c++
- name: Upload crash
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts

View File

@ -7,10 +7,11 @@ permissions:
jobs:
build:
runs-on: ubuntu-22.04
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Add Ubuntu mirrors
run: |
@ -24,7 +25,7 @@ jobs:
run: |
sudo apt update
sudo apt install doxygen
pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1
pip install mkdocs-material==9.5.25 mkdocstrings==0.25.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

@ -8,21 +8,48 @@ on:
permissions:
contents: read
pull-requests: write
jobs:
format_code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Install clang-format
run: |
wget https://apt.llvm.org/llvm.sh
sudo bash ./llvm.sh 21
sudo apt install clang-format-21
uses: aminya/setup-cpp@290824452986e378826155f3379d31bce8753d76 # v0.37.0
with:
clangformat: 17.0.5
- name: Run clang-format
id: clang_format
run: |
find include src -name '*.h' -o -name '*.cc' | \
xargs clang-format-21 -i -style=file -fallback-style=none
git diff --exit-code
find include src -name '*.h' -o -name '*.cc' | xargs clang-format -i -style=file -fallback-style=none
git diff | tee fmt.patch
if [ -s fmt.patch ]; then
exit 1
fi
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
if: failure() && steps.clang_format.outcome == 'failure'
with:
script: |
const fs = require('fs');
const patch = fs.readFileSync('fmt.patch', { encoding: 'utf8' });
const comment = `clang-format 17.0.5 found issues in the formatting in your code:
<details>
<summary>
View the diff from clang-format:
</summary>
\`\`\`diff
${patch}
\`\`\`
</details>
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});

View File

@ -7,143 +7,85 @@ permissions:
jobs:
build:
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
strategy:
matrix:
cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11]
cxx: [g++-4.8, g++-10, clang++-9]
build_type: [Debug, Release]
std: [11]
shared: [""]
include:
- cxx: g++-4.9
- cxx: clang++-3.6
- cxx: g++-11
- cxx: g++-4.8
install: sudo apt install g++-4.8
- cxx: g++-8
build_type: Debug
std: 14
install: sudo apt install g++-11
- cxx: g++-11
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
build_type: Debug
std: 17
- cxx: g++-11
build_type: Debug
std: 20
install: sudo apt install g++-11
- cxx: g++-13
build_type: Release
std: 23
install: sudo apt install g++-13
shared: -DBUILD_SHARED_LIBS=ON
- cxx: clang++-11
- cxx: clang++-8
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
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-11
- cxx: clang++-14
install: sudo apt install clang-9
- cxx: clang++-11
build_type: Debug
std: 20
- cxx: clang++-14
- cxx: clang++-11
build_type: Debug
std: 20
cxxflags: -stdlib=libc++
install: sudo apt install libc++-14-dev libc++abi-14-dev
install: sudo apt install libc++-11-dev libc++abi-11-dev
- shared: -DBUILD_SHARED_LIBS=ON
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set timezone
run: sudo timedatectl set-timezone 'Europe/Kyiv'
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
- name: Install GCC 4.9
- name: Add repositories for older GCC
run: |
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' }}
# Below two repos provide GCC 4.8, 5.5 and 6.4
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main'
sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe'
# Below two repos additionally update GCC 6 to 6.5
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates main'
# sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic-updates universe'
if: ${{ matrix.cxx == 'g++-4.8' }}
- name: Add repositories for newer GCC
run: |
sudo apt-add-repository ppa:ubuntu-toolchain-r/test
if: ${{ matrix.cxx == 'g++-13' }}
if: ${{ matrix.cxx == 'g++-11' }}
- name: Add Ubuntu mirrors
run: |
# 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
# 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
- name: Create build environment
- name: Create Build Environment
run: |
sudo apt update
${{matrix.install}}
@ -156,12 +98,10 @@ jobs:
CXX: ${{matrix.cxx}}
CXXFLAGS: ${{matrix.cxxflags}}
run: |
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
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
- name: Build
working-directory: ${{runner.workspace}}/build

View File

@ -9,23 +9,22 @@ jobs:
build:
strategy:
matrix:
os: [macos-14]
os: [macos-13, macos-14]
build_type: [Debug, Release]
std: [11, 17, 20, 23]
shared: [""]
std: [11, 17, 20]
exclude:
- { os: macos-13, std: 11 }
- { os: macos-13, std: 17 }
include:
- os: macos-14
std: 23
build_type: Release
shared: -DBUILD_SHARED_LIBS=ON
- shared: -DBUILD_SHARED_LIBS=ON
runs-on: '${{ matrix.os }}'
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set timezone
run: sudo systemsetup -settimezone 'Europe/Minsk'
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
- name: Select Xcode 14.3 (macOS 13)
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"

View File

@ -29,12 +29,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
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@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
with:
sarif_file: results.sarif

View File

@ -10,18 +10,27 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
# windows-2019 has MSVC 2019 installed;
# windows-2022 has MSVC 2022 installed:
# https://github.com/actions/virtual-environments.
os: [windows-2022]
os: [windows-2019]
platform: [Win32, x64]
toolset: [v142]
toolset: [v140, v141, v142]
standard: [14, 17, 20]
shared: ["", -DBUILD_SHARED_LIBS=ON]
build_type: [Debug, Release]
exclude:
- { toolset: v140, standard: 17 }
- { toolset: v140, standard: 20 }
- { toolset: v141, standard: 14 }
- { toolset: v141, standard: 20 }
- { toolset: v142, standard: 14 }
- { platform: Win32, toolset: v140 }
- { platform: Win32, toolset: v141 }
- { platform: Win32, standard: 14 }
- { platform: Win32, standard: 20 }
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
include:
@ -32,10 +41,10 @@ jobs:
standard: 20
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set timezone
run: tzutil /s "FLE Standard Time"
run: tzutil /s "Ekaterinburg Standard Time"
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -72,14 +81,14 @@ jobs:
sys: [ mingw64, ucrt64 ]
steps:
- name: Set timezone
run: tzutil /s "FLE Standard Time"
run: tzutil /s "Ekaterinburg Standard Time"
shell: cmd
- uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638 # v2.28.0
- uses: msys2/setup-msys2@d0e80f58dffbc64f6a3a1f43527d469b4fc7b6c8 # v2.23.0
with:
release: false
msystem: ${{matrix.sys}}
pacboy: cc:p cmake:p ninja:p lld:p
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Configure
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
env: { LDFLAGS: -fuse-ld=lld }

3
.gitignore vendored
View File

@ -3,14 +3,11 @@
*.xcodeproj
*~
.vscode/
.cache/
.vs/
/CMakeScripts
/Testing
/_CPack_Packages
/install_manifest.txt
CMakeCache.txt
CMakeUserPresets.json
CMakeFiles
CPack*.cmake
CTestTestfile.cmake

View File

@ -9,9 +9,7 @@ endif ()
# or if it is the master project.
if (NOT DEFINED FMT_MASTER_PROJECT)
set(FMT_MASTER_PROJECT OFF)
# NOTE: source vs current_source detection is unreliable
# this heuristic is more generally applicable esp w.r.t FetchContent
if (NOT DEFINED PROJECT_NAME)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(FMT_MASTER_PROJECT ON)
message(STATUS "CMake version: ${CMAKE_VERSION}")
endif ()
@ -29,15 +27,12 @@ endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target)
if (MSVC)
if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI})
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endif()
endif ()
endfunction()
@ -74,6 +69,8 @@ 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})
@ -163,7 +160,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
# Options that control generation of various targets.
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ON)
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
@ -204,7 +201,8 @@ if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
endif ()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
include(CheckCXXCompilerFlag)
include(JoinPaths)
@ -242,13 +240,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
endif ()
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
-Wduplicated-cond)
# Workaround for GCC regression
# [12/13/14/15 regression] New (since gcc 12) false positive null-dereference in vector.resize
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108860
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnull-dereference)
endif ()
-Wnull-dereference -Wduplicated-cond)
endif ()
set(WERROR_FLAG -Werror)
endif ()
@ -297,7 +289,6 @@ 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)
@ -328,7 +319,7 @@ else ()
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
endif ()
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE PUBLIC
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@ -368,8 +359,8 @@ if (NOT MSVC)
# Unicode is always supported on compilers other than MSVC.
elseif (FMT_UNICODE)
# Unicode support requires compiling with /utf-8.
target_compile_options(fmt PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
target_compile_options(fmt-header-only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
target_compile_options(fmt PUBLIC $<$<COMPILE_LANGUAGE:CXX>:/utf-8>)
target_compile_options(fmt-header-only INTERFACE $<$<COMPILE_LANGUAGE:CXX>:/utf-8>)
else ()
target_compile_definitions(fmt PUBLIC FMT_UNICODE=0)
endif ()
@ -378,7 +369,7 @@ target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
target_include_directories(fmt-header-only
${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE INTERFACE
${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@ -429,9 +420,7 @@ if (FMT_INSTALL)
endif()
# Install the library and headers.
install(TARGETS ${INSTALL_TARGETS}
COMPONENT fmt_core
EXPORT ${targets_export_name}
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
@ -444,15 +433,13 @@ if (FMT_INSTALL)
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
# Install version, config and target files.
install(FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR}
COMPONENT fmt_core)
install(
FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR})
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::
COMPONENT fmt_core)
NAMESPACE fmt::)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT fmt_core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
endif ()
function(add_doc_target)
@ -488,8 +475,7 @@ function(add_doc_target)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT fmt_doc OPTIONAL)
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL)
endfunction()
if (FMT_DOC)

View File

@ -1,698 +1,3 @@
# 12.1.0 - 2025-10-29
- Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog
benchmarks (https://github.com/fmtlib/fmt/pull/4541). Thanks @fyrsta7.
- Worked around an ABI incompatibility in `std::locale_ref` between clang and
gcc (https://github.com/fmtlib/fmt/issues/4573).
- Made `std::variant` and `std::expected` formatters work with `format_as`
(https://github.com/fmtlib/fmt/issues/4574,
https://github.com/fmtlib/fmt/pull/4575). Thanks @phprus.
- Made `fmt::join<string_view>` work with C++ modules
(https://github.com/fmtlib/fmt/issues/4379,
https://github.com/fmtlib/fmt/pull/4577). Thanks @Arghnews.
- Exported `fmt::is_compiled_string` and `operator""_cf` from the module
(https://github.com/fmtlib/fmt/pull/4544). Thanks @CrackedMatter.
- Fixed a compatibility issue with C++ modules in clang
(https://github.com/fmtlib/fmt/pull/4548). Thanks @tsarn.
- Added support for cv-qualified types to the `std::optional` formatter
(https://github.com/fmtlib/fmt/issues/4561,
https://github.com/fmtlib/fmt/pull/4562). Thanks @OleksandrKvl.
- Added demangling support (used in exception and `std::type_info` formatters)
for libc++ and clang-cl
(https://github.com/fmtlib/fmt/issues/4542,
https://github.com/fmtlib/fmt/pull/4560,
https://github.com/fmtlib/fmt/issues/4568,
https://github.com/fmtlib/fmt/pull/4571).
Thanks @FatihBAKIR and @rohitsutreja.
- Switched to global `malloc`/`free` to enable allocator customization
(https://github.com/fmtlib/fmt/issues/4569,
https://github.com/fmtlib/fmt/pull/4570). Thanks @rohitsutreja.
- Made the `FMT_USE_CONSTEVAL` macro configurable by users
(https://github.com/fmtlib/fmt/pull/4546). Thanks @SnapperTT.
- Fixed compilation with locales disabled in the header-only mode
(https://github.com/fmtlib/fmt/issues/4550).
- Fixed compilation with clang 21 and `-std=c++20`
(https://github.com/fmtlib/fmt/issues/4552).
- Fixed a dynamic linking issue with clang-cl
(https://github.com/fmtlib/fmt/issues/4576,
https://github.com/fmtlib/fmt/pull/4584). Thanks @FatihBAKIR.
- Fixed a warning suppression leakage on gcc
(https://github.com/fmtlib/fmt/pull/4588). Thanks @ZedThree.
- Made more internal color APIs `constexpr`
(https://github.com/fmtlib/fmt/pull/4581). Thanks @ishani.
- Fixed compatibility with clang as a host compiler for NVCC
(https://github.com/fmtlib/fmt/pull/4564). Thanks @valgur.
- Fixed various warnings and lint issues
(https://github.com/fmtlib/fmt/issues/4565,
https://github.com/fmtlib/fmt/pull/4572,
https://github.com/fmtlib/fmt/pull/4557).
Thanks @LiangHuDream and @teruyamato0731.
- Improved documentation
(https://github.com/fmtlib/fmt/issues/4549,
https://github.com/fmtlib/fmt/pull/4551,
https://github.com/fmtlib/fmt/issues/4566,
https://github.com/fmtlib/fmt/pull/4567,
https://github.com/fmtlib/fmt/pull/4578,).
Thanks @teruyamato0731, @petersteneteg and @zimmerman-dev.
# 12.0.0 - 2025-09-17
- Optimized the default floating point formatting
(https://github.com/fmtlib/fmt/issues/3675,
https://github.com/fmtlib/fmt/issues/4516). In particular, formatting a
`double` with format string compilation into a stack allocated buffer is
more than 60% faster in version 12.0 compared to 11.2 according to
[dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark):
```
Function Time (ns) Speedup
fmt11 34.471 1.00x
fmt12 21.000 1.64x
```
<img width="766" height="609" src="https://github.com/user-attachments/assets/d7d768ad-7543-468c-b0bb-449abf73b31b" />
- Added `constexpr` support to `fmt::format`. For example:
```c++
#include <fmt/compile.h>
using namespace fmt::literals;
std::string s = fmt::format(""_cf, 42);
```
now works at compile time provided that `std::string` supports `constexpr`
(https://github.com/fmtlib/fmt/issues/3403,
https://github.com/fmtlib/fmt/pull/4456). Thanks @msvetkin.
- Added `FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time.
For example:
```c++
#include <fmt/compile.h>
constexpr auto s = FMT_STATIC_FORMAT("{}", 42);
```
compiles to just
```s
__ZL1s:
.asciiz "42"
```
It can be accessed as a C string with `s.c_str()` or as a string view with
`s.str()`.
- Improved C++20 module support
(https://github.com/fmtlib/fmt/pull/4451,
https://github.com/fmtlib/fmt/pull/4459,
https://github.com/fmtlib/fmt/pull/4476,
https://github.com/fmtlib/fmt/pull/4488,
https://github.com/fmtlib/fmt/issues/4491,
https://github.com/fmtlib/fmt/pull/4495).
Thanks @arBmind, @tkhyn, @Mishura4, @anonymouspc and @autoantwort.
- Switched to using estimated display width in precision. For example:
```c++
fmt::print("|{:.4}|\n|1234|\n", "🐱🐱🐱");
```
prints
![](https://github.com/user-attachments/assets/6c4446b3-13eb-43b9-b74a-b4543540ad6a)
because `🐱` has an estimated width of 2
(https://github.com/fmtlib/fmt/issues/4272,
https://github.com/fmtlib/fmt/pull/4443,
https://github.com/fmtlib/fmt/pull/4475).
Thanks @nikhilreddydev and @localspook.
- Fix interaction between debug presentation, precision, and width for strings
(https://github.com/fmtlib/fmt/pull/4478). Thanks @localspook.
- Implemented allocator propagation on `basic_memory_buffer` move
(https://github.com/fmtlib/fmt/issues/4487,
https://github.com/fmtlib/fmt/pull/4490). Thanks @toprakmurat.
- Fixed an ambiguity between `std::reference_wrapper<T>` and `format_as`
formatters (https://github.com/fmtlib/fmt/issues/4424,
https://github.com/fmtlib/fmt/pull/4434). Thanks @jeremy-rifkin.
- Removed the following deprecated APIs:
- `has_formatter`: use `is_formattable` instead,
- `basic_format_args::parse_context_type`,
`basic_format_args::formatter_type` and similar aliases in context types,
- wide stream overload of `fmt::printf`,
- wide stream overloads of `fmt::print` that take text styles,
- `is_*char` traits,
- `fmt::localtime`.
- Deprecated wide overloads of `fmt::fprintf` and `fmt::sprintf`.
- Improved diagnostics for the incorrect usage of `fmt::ptr`
(https://github.com/fmtlib/fmt/pull/4453). Thanks @TobiSchluter.
- Made handling of ANSI escape sequences more efficient
(https://github.com/fmtlib/fmt/pull/4511,
https://github.com/fmtlib/fmt/pull/4528).
Thanks @localspook and @Anas-Hamdane.
- Fixed a buffer overflow on all emphasis flags set
(https://github.com/fmtlib/fmt/pull/4498). Thanks @dominicpoeschko.
- Fixed an integer overflow for precision close to the max `int` value.
- Fixed compatibility with WASI (https://github.com/fmtlib/fmt/issues/4496,
https://github.com/fmtlib/fmt/pull/4497). Thanks @whitequark.
- Fixed `back_insert_iterator` detection, preventing a fallback on slower path
that handles arbitrary iterators (https://github.com/fmtlib/fmt/issues/4454).
- Fixed handling of invalid glibc `FILE` buffers
(https://github.com/fmtlib/fmt/issues/4469).
- Added `wchar_t` support to the `std::byte` formatter
(https://github.com/fmtlib/fmt/issues/4479,
https://github.com/fmtlib/fmt/pull/4480). Thanks @phprus.
- Changed component prefix from `fmt-` to `fmt_` for compatibility with
NSIS/CPack on Windows, e.g. `fmt-doc` changed to `fmt_doc`
(https://github.com/fmtlib/fmt/issues/4441,
https://github.com/fmtlib/fmt/pull/4442). Thanks @n-stein.
- Added the `FMT_CUSTOM_ASSERT_FAIL` macro to simplify providing a custom
`fmt::assert_fail` implementation (https://github.com/fmtlib/fmt/pull/4505).
Thanks @HazardyKnusperkeks.
- Switched to `FMT_THROW` on reporting format errors so that it can be
overriden by users when exceptions are disabled
(https://github.com/fmtlib/fmt/pull/4521). Thanks @HazardyKnusperkeks.
- Improved master project detection and disabled install targets when using
{fmt} as a subproject by default (https://github.com/fmtlib/fmt/pull/4536).
Thanks @crueter.
- Made various code improvements
(https://github.com/fmtlib/fmt/pull/4445,
https://github.com/fmtlib/fmt/pull/4448,
https://github.com/fmtlib/fmt/pull/4473,
https://github.com/fmtlib/fmt/pull/4522).
Thanks @localspook, @tchaikov and @way4sahil.
- Added Conan instructions to the docs
(https://github.com/fmtlib/fmt/pull/4537). Thanks @uilianries.
- Removed Bazel files to avoid issues with downstream packaging
(https://github.com/fmtlib/fmt/pull/4530). Thanks @mering.
- Added more entries for generated files to `.gitignore`
(https://github.com/fmtlib/fmt/pull/4355,
https://github.com/fmtlib/fmt/pull/4512).
Thanks @dinomight and @localspook.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/pull/4447,
https://github.com/fmtlib/fmt/issues/4470,
https://github.com/fmtlib/fmt/pull/4474,
https://github.com/fmtlib/fmt/pull/4477,
https://github.com/fmtlib/fmt/pull/4471,
https://github.com/fmtlib/fmt/pull/4483,
https://github.com/fmtlib/fmt/pull/4515,
https://github.com/fmtlib/fmt/issues/4533,
https://github.com/fmtlib/fmt/pull/4534).
Thanks @dodomorandi, @localspook, @remyjette, @Tomek-Stolarczyk, @Mishura4,
@mattiasljungstrom and @FatihBAKIR.
# 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/issues/4282,
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/4335,
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
(https://github.com/fmtlib/fmt/issues/4278).
- Defined CMake components (`core` and `doc`) to allow docs to be installed
separately (https://github.com/fmtlib/fmt/pull/4276).
Thanks @carlsmedstad.
# 11.1.0 - 2024-12-25
- Improved C++20 module support
(https://github.com/fmtlib/fmt/issues/4081,
https://github.com/fmtlib/fmt/pull/4083,
https://github.com/fmtlib/fmt/pull/4084,
https://github.com/fmtlib/fmt/pull/4152,
https://github.com/fmtlib/fmt/issues/4153,
https://github.com/fmtlib/fmt/pull/4169,
https://github.com/fmtlib/fmt/issues/4190,
https://github.com/fmtlib/fmt/issues/4234,
https://github.com/fmtlib/fmt/pull/4239).
Thanks @kamrann and @Arghnews.
- Reduced debug (unoptimized) binary code size and the number of template
instantiations when passing formatting arguments. For example, unoptimized
binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and
~60% on clang (x86-64).
GCC:
- Before: 161 instructions of which 105 are in reusable functions
([godbolt](https://www.godbolt.org/z/s9bGoo4ze)).
- After: 116 instructions of which 60 are in reusable functions
([godbolt](https://www.godbolt.org/z/r7GGGxMs6)).
Clang:
- Before: 310 instructions of which 251 are in reusable functions
([godbolt](https://www.godbolt.org/z/Ts88b7M9o)).
- After: 194 instructions of which 135 are in reusable functions
([godbolt](https://www.godbolt.org/z/vcrjP8ceW)).
- Added an experimental `fmt::writer` API that can be used for writing to
different destinations such as files or strings
(https://github.com/fmtlib/fmt/issues/2354).
For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)):
```c++
#include <fmt/os.h>
void write_text(fmt::writer w) {
w.print("The answer is {}.", 42);
}
int main() {
// Write to FILE.
write_text(stdout);
// Write to fmt::ostream.
auto f = fmt::output_file("myfile");
write_text(f);
// Write to std::string.
auto sb = fmt::string_buffer();
write_text(sb);
std::string s = sb.str();
}
```
- Added width and alignment support to the formatter of `std::error_code`.
- Made `std::expected<void, E>` formattable
(https://github.com/fmtlib/fmt/issues/4145,
https://github.com/fmtlib/fmt/pull/4148).
For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)):
```c++
fmt::print("{}", std::expected<void, int>());
```
prints
```
expected()
```
Thanks @phprus.
- Made `fmt::is_formattable<void>` SFINAE-friendly
(https://github.com/fmtlib/fmt/issues/4147).
- Added support for `_BitInt` formatting when using clang
(https://github.com/fmtlib/fmt/issues/4007,
https://github.com/fmtlib/fmt/pull/4072,
https://github.com/fmtlib/fmt/issues/4140,
https://github.com/fmtlib/fmt/issues/4173,
https://github.com/fmtlib/fmt/pull/4176).
For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)):
```c++
using int42 = _BitInt(42);
fmt::print("{}", int42(100));
```
Thanks @Arghnews.
- Added the `n` specifier for tuples and pairs
(https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc.
- Added support for tuple-like types to `fmt::join`
(https://github.com/fmtlib/fmt/issues/4226,
https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus.
- Made more types formattable at compile time
(https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH.
- Implemented a more efficient compile-time `fmt::formatted_size`
(https://github.com/fmtlib/fmt/issues/4102,
https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus.
- Fixed compile-time formatting of some string types
(https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd.
- Made compiled version of `fmt::format_to` work with
`std::back_insert_iterator<std::vector<char>>`
(https://github.com/fmtlib/fmt/issues/4206,
https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus.
- Added a formatter for `std::reference_wrapper`
(https://github.com/fmtlib/fmt/pull/4163,
https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus.
- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j`
and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH.
- Made microseconds formatted as `us` instead of `µs` if the Unicode support is
disabled (https://github.com/fmtlib/fmt/issues/4088).
- Fixed an unreleased regression in transcoding of surrogate pairs
(https://github.com/fmtlib/fmt/issues/4094,
https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus.
- Made `fmt::appender` satisfy `std::output_iterator` concept
(https://github.com/fmtlib/fmt/issues/4092,
https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus.
- Made `std::iterator_traits<fmt::appender>` standard-conforming
(https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter.
- Made it easier to reuse `fmt::formatter<std::string_view>` for types with
an implicit conversion to `std::string_view`
(https://github.com/fmtlib/fmt/issues/4036,
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
- Made it possible to disable `<filesystem>` use via `FMT_CPP_LIB_FILESYSTEM`
for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK
(https://github.com/fmtlib/fmt/issues/4257,
https://github.com/fmtlib/fmt/pull/4258,
https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus.
- Fixed compatibility with platforms that use 80-bit `long double`
(https://github.com/fmtlib/fmt/issues/4245,
https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma.
- Added support for UTF-32 code units greater than `0xFFFF` in fill
(https://github.com/fmtlib/fmt/issues/4201).
- Fixed handling of legacy encodings on Windows with GCC
(https://github.com/fmtlib/fmt/issues/4162).
- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference
(https://github.com/fmtlib/fmt/issues/4261,
https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel.
- Added `fmt::dynamic_format_arg_store::size`
(https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch.
- Removed the ability to control locale usage via an undocumented
`FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`.
- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other
similar macros.
- Improved include directory ordering to reduce the chance of including
incorrect headers when using multiple versions of {fmt}
(https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan.
- Made it possible to compile a subset of {fmt} without the C++ runtime.
- Improved documentation and README
(https://github.com/fmtlib/fmt/pull/4066,
https://github.com/fmtlib/fmt/issues/4117,
https://github.com/fmtlib/fmt/issues/4203,
https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh.
- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110,
https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado.
- Improved CI (https://github.com/fmtlib/fmt/pull/4155,
https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/issues/2708,
https://github.com/fmtlib/fmt/issues/4091,
https://github.com/fmtlib/fmt/issues/4109,
https://github.com/fmtlib/fmt/issues/4113,
https://github.com/fmtlib/fmt/issues/4125,
https://github.com/fmtlib/fmt/issues/4129,
https://github.com/fmtlib/fmt/pull/4130,
https://github.com/fmtlib/fmt/pull/4131,
https://github.com/fmtlib/fmt/pull/4132,
https://github.com/fmtlib/fmt/issues/4133,
https://github.com/fmtlib/fmt/issues/4144,
https://github.com/fmtlib/fmt/issues/4150,
https://github.com/fmtlib/fmt/issues/4158,
https://github.com/fmtlib/fmt/pull/4159,
https://github.com/fmtlib/fmt/issues/4160,
https://github.com/fmtlib/fmt/pull/4170,
https://github.com/fmtlib/fmt/issues/4177,
https://github.com/fmtlib/fmt/pull/4187,
https://github.com/fmtlib/fmt/pull/4188,
https://github.com/fmtlib/fmt/pull/4194,
https://github.com/fmtlib/fmt/pull/4200,
https://github.com/fmtlib/fmt/issues/4205,
https://github.com/fmtlib/fmt/issues/4207,
https://github.com/fmtlib/fmt/pull/4208,
https://github.com/fmtlib/fmt/pull/4210,
https://github.com/fmtlib/fmt/issues/4220,
https://github.com/fmtlib/fmt/issues/4231,
https://github.com/fmtlib/fmt/issues/4232,
https://github.com/fmtlib/fmt/pull/4233,
https://github.com/fmtlib/fmt/pull/4236,
https://github.com/fmtlib/fmt/pull/4267,
https://github.com/fmtlib/fmt/pull/4271).
Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega,
@c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao.
# 11.0.2 - 2024-07-20
- Fixed compatibility with non-POSIX systems
(https://github.com/fmtlib/fmt/issues/4054,
https://github.com/fmtlib/fmt/issues/4060).
- Fixed performance regressions when using `std::back_insert_iterator` with
`fmt::format_to` (https://github.com/fmtlib/fmt/issues/4070).
- Fixed handling of `std::generator` and move-only iterators
(https://github.com/fmtlib/fmt/issues/4053,
https://github.com/fmtlib/fmt/pull/4057). Thanks @Arghnews.
- Made `formatter<std::string_view>::parse` work with types convertible to
`std::string_view` (https://github.com/fmtlib/fmt/issues/4036,
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
- Made `volatile void*` formattable
(https://github.com/fmtlib/fmt/issues/4049,
https://github.com/fmtlib/fmt/pull/4056). Thanks @Arghnews.
- Made `Glib::ustring` not be confused with `std::string`
(https://github.com/fmtlib/fmt/issues/4052).
- Made `fmt::context` iterator compatible with STL algorithms that rely on
iterator category (https://github.com/fmtlib/fmt/issues/4079).
# 11.0.1 - 2024-07-05
- Fixed version number in the inline namespace
@ -708,9 +13,6 @@
(https://github.com/fmtlib/fmt/pull/4034,
https://github.com/fmtlib/fmt/pull/4050). Thanks @tesch1 and @phprus.
- Fixed ADL issues in `fmt::printf` when using C++20
(https://github.com/fmtlib/fmt/pull/4042). Thanks @toge.
- Removed a redundant check in the formatter for `std::expected`
(https://github.com/fmtlib/fmt/pull/4040). Thanks @phprus.
@ -936,9 +238,6 @@
- Fixed handling of negative ids in `fmt::basic_format_args::get`
(https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota.
- Fixed handling of a buffer boundary on flush
(https://github.com/fmtlib/fmt/issues/4229).
- Improved named argument validation
(https://github.com/fmtlib/fmt/issues/3817).

View File

@ -4,15 +4,14 @@
[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8880/badge)](https://www.bestpractices.dev/projects/8880)
[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt)
[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
**{fmt}** is an open-source formatting library providing a fast and safe
alternative to C stdio and C++ iostreams.
If you like this project, please consider donating to one of the funds
that help victims of the war in Ukraine: <https://u24.gov.ua/>.
that help victims of the war in Ukraine: <https://www.stopputin.net/>.
[Documentation](https://fmt.dev)
@ -25,12 +24,12 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
# Features
- Simple [format API](https://fmt.dev/latest/api/) with positional
- Simple [format API](https://fmt.dev/latest/api.html) with positional
arguments for localization
- Implementation of [C++20
std::format](https://en.cppreference.com/w/cpp/utility/format) and
[C++23 std::print](https://en.cppreference.com/w/cpp/io/print)
- [Format string syntax](https://fmt.dev/latest/syntax/) similar
- [Format string syntax](https://fmt.dev/latest/syntax.html) similar
to Python\'s
[format](https://docs.python.org/3/library/stdtypes.html#str.format)
- Fast IEEE 754 floating-point formatter with correct rounding,
@ -38,17 +37,17 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
[Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm
- Portable Unicode support
- Safe [printf
implementation](https://fmt.dev/latest/api/#printf-formatting)
implementation](https://fmt.dev/latest/api.html#printf-formatting)
including the POSIX extension for positional arguments
- Extensibility: [support for user-defined
types](https://fmt.dev/latest/api/#formatting-user-defined-types)
types](https://fmt.dev/latest/api.html#formatting-user-defined-types)
- High performance: faster than common standard library
implementations of `(s)printf`, iostreams, `to_string` and
`to_chars`, see [Speed tests](#speed-tests) and [Converting a
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, `base.h`, `format.h`
configuration consisting of just three files, `core.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
@ -59,8 +58,8 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
buffer overflow errors
- Ease of use: small self-contained code base, no external
dependencies, permissive MIT
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE)
- [Portability](https://fmt.dev/latest/#portability) with
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE.rst)
- [Portability](https://fmt.dev/latest/index.html#portability) with
consistent output across platforms and support for older compilers
- Clean warning-free codebase even on high warning levels such as
`-Wall -Wextra -pedantic`
@ -75,7 +74,7 @@ See the [documentation](https://fmt.dev) for more details.
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
``` c++
#include <fmt/base.h>
#include <fmt/core.h>
int main() {
fmt::print("Hello, world!\n");
@ -150,8 +149,8 @@ int main() {
}
```
This can be [up to 9 times faster than `fprintf`](
http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
This can be [5 to 9 times faster than
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
**Print with colors and text styles**
@ -178,17 +177,17 @@ Output on a modern terminal with Unicode support:
| Library | Method | Run Time, s |
|-------------------|---------------|-------------|
| libc | printf | 0.66 |
| libc++ | std::ostream | 1.63 |
| {fmt} 12.1 | fmt::print | 0.44 |
| Boost Format 1.88 | boost::format | 3.89 |
| Folly Format | folly::format | 1.28 |
| libc | printf | 0.91 |
| libc++ | std::ostream | 2.49 |
| {fmt} 9.1 | fmt::print | 0.74 |
| Boost Format 1.80 | boost::format | 6.26 |
| Folly Format | folly::format | 1.87 |
{fmt} is the fastest of the benchmarked methods, \~50% faster than
{fmt} is the fastest of the benchmarked methods, \~20% faster than
`printf`.
The above results were generated by building `tinyformat_test.cpp` on
macOS 15.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
taking the best of three runs. In the test, the format string
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
times with output sent to `/dev/null`; for further details refer to the
@ -217,11 +216,11 @@ in the following tables.
**Optimized build (-O3)**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------|
|---------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 |
| IOStreams | 28.4 | 98 | 84 |
| {fmt} `1122268` | 5.0 | 54 | 50 |
| tinyformat | 32.6 | 164 | 136 |
| IOStreams | 25.9 | 98 | 84 |
| fmt 83652df | 4.8 | 54 | 50 |
| tinyformat | 29.1 | 161 | 136 |
| Boost Format | 55.0 | 530 | 317 |
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
@ -230,12 +229,12 @@ binary size (within a rounding error on this system).
**Non-optimized build**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------|
|---------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 |
| IOStreams | 27.0 | 88 | 68 |
| {fmt} `1122268` | 4.7 | 87 | 84 |
| tinyformat | 28.1 | 185 | 145 |
| Boost Format | 38.9 | 678 | 381 |
| IOStreams | 23.4 | 92 | 68 |
| {fmt} 83652df | 4.4 | 89 | 85 |
| tinyformat | 24.5 | 204 | 161 |
| Boost Format | 36.4 | 831 | 462 |
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
to compare formatting function overhead only. Boost Format is a
@ -244,7 +243,7 @@ header-only library so it doesn\'t provide any linkage options.
## Running the tests
Please refer to [Building the
library](https://fmt.dev/latest/get-started/#building-from-source) for
library](https://fmt.dev/latest/usage.html#building-the-library) for
instructions on how to build the library and run the unit tests.
Benchmarks reside in a separate repository,
@ -292,14 +291,13 @@ converts to `std::print`.)
- [ccache](https://ccache.dev/): a compiler cache
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
analytical database management system
- [ContextVision](https://www.contextvision.com/): medical imaging software
- [Contour](https://github.com/contour-terminal/contour/): a modern
terminal emulator
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous
underwater vehicle
- [Drake](https://drake.mit.edu/): a planning, control, and analysis
toolbox for nonlinear dynamical systems (MIT)
- [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and
- [Envoy](https://lyft.github.io/envoy/): C++ L7 proxy and
communication bus (Lyft)
- [FiveM](https://fivem.net/): a modification framework for GTA V
- [fmtlog](https://github.com/MengRao/fmtlog): a performant
@ -458,7 +456,7 @@ second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html).
# Documentation License
The [Format String Syntax](https://fmt.dev/latest/syntax/) section
The [Format String Syntax](https://fmt.dev/latest/syntax.html) section
in the documentation is based on the one from Python [string module
documentation](https://docs.python.org/3/library/string.html#module-string).
For this reason, the documentation is distributed under the Python

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,
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink,
@tohammer and @0x8000-0000.
# 6.2.1 - 2020-05-09

View File

@ -14,7 +14,7 @@ The {fmt} library API consists of the following components:
- [`fmt/os.h`](#os-api): system APIs
- [`fmt/ostream.h`](#ostream-api): `std::ostream` support
- [`fmt/args.h`](#args-api): dynamic argument lists
- [`fmt/printf.h`](#printf-api): safe `printf`
- [`fmt/printf.h`](#printf-api): `printf` formatting
- [`fmt/xchar.h`](#xchar-api): optional `wchar_t` support
All functions and types provided by the library reside in namespace `fmt`
@ -79,8 +79,6 @@ 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
@ -222,7 +220,7 @@ You can also write a formatter for a hierarchy of classes:
```c++
// demo.h:
#include <type_traits>
#include <fmt/format.h>
#include <fmt/core.h>
struct A {
virtual ~A() {}
@ -271,16 +269,18 @@ that support C++20 `consteval`. On older compilers you can use the
Unused arguments are allowed as in Python's `str.format` and ordinary functions.
See [Type Erasure](#type-erasure) for an example of how to enable compile-time
checks in your own functions with `fmt::format_string` while avoiding template
bloat.
::: fstring
::: basic_format_string
::: format_string
::: runtime(string_view)
### Named Arguments
::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Type Erasure
You can create your own formatting function with compile-time checks and
@ -317,12 +317,6 @@ parameterized version.
::: basic_format_arg
### Named Arguments
::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Compatibility
::: basic_string_view
@ -381,17 +375,18 @@ allocator:
using custom_string =
std::basic_string<char, std::char_traits<char>, custom_allocator>;
auto vformat(custom_allocator alloc, fmt::string_view fmt,
fmt::format_args args) -> custom_string {
custom_string vformat(custom_allocator alloc, fmt::string_view format_str,
fmt::format_args args) {
auto buf = custom_memory_buffer(alloc);
fmt::vformat_to(std::back_inserter(buf), fmt, args);
fmt::vformat_to(std::back_inserter(buf), format_str, args);
return custom_string(buf.data(), buf.size(), alloc);
}
template <typename ...Args>
auto format(custom_allocator alloc, fmt::string_view fmt,
const Args& ... args) -> custom_string {
return vformat(alloc, fmt, fmt::make_format_args(args...));
inline custom_string format(custom_allocator alloc,
fmt::string_view format_str,
const Args& ... args) {
return vformat(alloc, format_str, fmt::make_format_args(args...));
}
The allocator will be used for the output container only. Formatting
@ -405,7 +400,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/format.h>
#include <fmt/core.h>
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
@ -415,11 +410,11 @@ locale:
that take `std::locale` as a parameter. The locale type is a template
parameter to avoid the expensive `<locale>` include.
::: format(locale_ref, format_string<T...>, T&&...)
::: format(const Locale&, format_string<T...>, T&&...)
::: format_to(OutputIt, locale_ref, format_string<T...>, T&&...)
::: format_to(OutputIt, const Locale&, format_string<T...>, T&&...)
::: formatted_size(locale_ref, format_string<T...>, T&&...)
::: formatted_size(const Locale&, format_string<T...>, T&&...)
<a id="legacy-checks"></a>
### Legacy Compile-Time Checks
@ -475,9 +470,9 @@ chrono-format-specifications).
#include <fmt/chrono.h>
int main() {
auto now = std::chrono::system_clock::now();
std::time_t t = std::time(nullptr);
fmt::print("The date is {:%Y-%m-%d}.\n", now);
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
// Output: The date is 2020-11-07.
// (with 2020-11-07 replaced by the current date)
@ -490,6 +485,8 @@ chrono-format-specifications).
// Output: strftime-like format: 03:15:30
}
::: localtime(std::time_t)
::: gmtime(std::time_t)
<a id="std-api"></a>
@ -501,13 +498,10 @@ chrono-format-specifications).
- [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag)
- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code)
- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception)
- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path)
- [`std::monostate`](
https://en.cppreference.com/w/cpp/utility/variant/monostate)
- [`std::monostate`](https://en.cppreference.com/w/cpp/utility/variant/monostate)
- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional)
- [`std::source_location`](
https://en.cppreference.com/w/cpp/utility/source_location)
- [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location)
- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id)
- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant)
@ -515,7 +509,7 @@ chrono-format-specifications).
::: ptr(const std::shared_ptr<T>&)
### Variants
### Formatting Variants
A `std::variant` is only formattable if every variant alternative is
formattable, and requires the `__cpp_lib_variant` [library
@ -531,87 +525,39 @@ feature](https://en.cppreference.com/w/cpp/feature_test).
fmt::print("{}", std::variant<std::monostate, char>());
// Output: variant(monostate)
## Bit-Fields and Packed Structs
To format a bit-field or a field of a struct with `__attribute__((packed))`
applied to it, you need to convert it to the underlying or compatible type via
a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)):
```c++
struct smol {
int bit : 1;
};
auto s = smol();
fmt::print("{}", +s.bit);
```
This is a known limitation of "perfect" forwarding in C++.
<a id="compile-api"></a>
## Compile-Time Support
## Format String Compilation
`fmt/compile.h` provides format string compilation and compile-time
(`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf`
user-defined literal defined in namespace `fmt::literals`. Format strings
marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into
efficient formatting code at compile-time. This supports arguments of built-in
and string types as well as user-defined types with `format` methods taking
`fmt/compile.h` provides format string compilation enabled via the
`FMT_COMPILE` macro or the `_cf` user-defined literal defined in
namespace `fmt::literals`. Format strings marked with `FMT_COMPILE`
or `_cf` are parsed, checked and converted into efficient formatting
code at compile-time. This supports arguments of built-in and string
types as well as user-defined types with `format` functions taking
the format context type as a template parameter in their `formatter`
specializations. For example ([run](https://www.godbolt.org/z/3c13erEoq)):
struct point {
double x;
double y;
};
specializations. For example:
template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
constexpr auto parse(format_parse_context& ctx);
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const {
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
auto format(const point& p, FormatContext& ctx) const;
};
using namespace fmt::literals;
std::string s = fmt::format("{}"_cf, point(4, 2));
Format string compilation can generate more binary code compared to the
default API and is only recommended in places where formatting is a
performance bottleneck.
The same APIs support formatting at compile time e.g. in `constexpr`
and `consteval` functions. Additionally there is an experimental
`FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time. Compile-time formatting works with built-in
and user-defined formatters that have `constexpr` `format` methods.
Example:
template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
constexpr auto format(const point& p, FormatContext& ctx) const {
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
};
constexpr auto s = FMT_STATIC_FORMAT("{}", point(4, 2));
const char* cstr = s.c_str(); // Points the static string "(4, 2)".
::: operator""_cf
::: FMT_COMPILE
::: FMT_STATIC_FORMAT
::: operator""_cf
<a id="color-api"></a>
## Terminal Colors and Text Styles
`fmt/color.h` provides support for terminal color and text style output.
::: print(text_style, format_string<T...>, T&&...)
::: print(const text_style&, format_string<T...>, T&&...)
::: fg(detail::color_type)
@ -624,8 +570,6 @@ Example:
::: ostream
::: output_file(cstring_view, T...)
::: windows_error
<a id="ostream-api"></a>
@ -665,7 +609,7 @@ that can be used to construct format argument lists dynamically.
::: dynamic_format_arg_store
<a id="printf-api"></a>
## Safe `printf`
## `printf` Formatting
The header `fmt/printf.h` provides `printf`-like formatting
functionality. The following functions use [printf format string
@ -676,9 +620,9 @@ if an argument type doesn't match its format specification.
::: printf(string_view, const T&...)
::: fprintf(std::FILE*, string_view, const T&...)
::: fprintf(std::FILE*, const S&, const T&...)
::: sprintf(string_view, const T&...)
::: sprintf(const S&, const T&...)
<a id="xchar-api"></a>
## Wide Strings
@ -686,6 +630,8 @@ if an argument type doesn't match its format specification.
The optional header `fmt/xchar.h` provides support for `wchar_t` and
exotic character types.
::: is_char
::: wstring_view
::: wformat_context
@ -700,63 +646,5 @@ 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 produce more
decimal digits than necessary.
## Configuration Options
{fmt} provides configuration via CMake options and preprocessor macros to
enable or disable features and to optimize for binary size. For example, you
can disable OS-specific APIs defined in `fmt/os.h` with `-DFMT_OS=OFF` when
configuring CMake.
### CMake Options
- **`FMT_OS`**: When set to `OFF`, disables OS-specific APIs (`fmt/os.h`).
- **`FMT_UNICODE`**: When set of `OFF`, disables Unicode support on
Windows/MSVC. Unicode support is always enabled on other platforms.
### Macros
- **`FMT_HEADER_ONLY`**: Enables the header-only mode when defined. It is an
alternative to using the `fmt::fmt-header-only` CMake target.
Default: not defined.
- **`FMT_USE_EXCEPTIONS`**: Disables the use of exceptions when set to `0`.
Default: `1` (`0` if compiled with `-fno-exceptions`).
- **`FMT_USE_LOCALE`**: When set to `0`, disables locale support.
Default: `1` (`0` when `FMT_OPTIMIZE_SIZE > 1`).
- **`FMT_CUSTOM_ASSERT_FAIL`**: When set to `1`, allows users to provide a
custom `fmt::assert_fail` function which is called on assertion failures and,
if exceptions are disabled, on runtime errors. Default: `0`.
- **`FMT_BUILTIN_TYPES`**: When set to `0`, disables built-in handling of
arithmetic and string types other than `int`. This reduces library size at
the cost of per-call overhead. Default: `1`.
- **`FMT_OPTIMIZE_SIZE`**: Controls binary size optimizations:
- `0` - off (default)
- `1` - disables locale support and applies some optimizations
- `2` - disables some Unicode features, named arguments and applies more
aggressive optimizations
### Binary Size Optimization
To minimize the binary footprint of {fmt} as much as possible at the cost of
some features, you can use the following configuration:
- CMake options:
- `FMT_OS=OFF`
- Macros:
- `FMT_BUILTIN_TYPES=0`
- `FMT_OPTIMIZE_SIZE=2`

View File

@ -20,7 +20,6 @@
margin-left: 1em;
}
code,
pre > code.decl {
white-space: pre-wrap;
}

View File

@ -78,17 +78,6 @@ community contributors. If the version is out of date, please [create an
issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg
repository. -->
### Conan
You can download and install {fmt} using the [Conan](https://conan.io/) package manager:
conan install -r conancenter --requires="fmt/[*]" --build=missing
<!-- The {fmt} package in Conan Center is maintained by
[ConanCenterIndex](https://github.com/conan-io/conan-center-index) community.
If the version is out of date or the package does not work,
please create an issue or pull request on the Conan Center Index repository. -->
## Building from Source
CMake works by generating native makefiles or project files that can be
@ -213,7 +202,7 @@ For a static build, use the following subproject definition:
For the header-only version, use:
fmt = subproject('fmt', default_options: ['header-only=true'])
fmt = subproject('fmt')
fmt_dep = fmt.get_variable('fmt_header_only_dep')
### Android NDK

View File

@ -76,7 +76,7 @@ hide:
<p>
The default is <b>locale-independent</b>, but you can opt into localized
formatting and {fmt} makes it work with Unicode, addressing issues in the
standard library.
standard libary.
</p>
</div>
@ -122,8 +122,8 @@ 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.6, MSVC 19.10
(2017) and later. Newer compiler and standard library features are used
C++11</b> features which are available in GCC 4.8, Clang 3.4, MSVC 19.0
(2015) and later. Newer compiler and standard library features are used
if available, and enable additional functionality.
</p>
<p>

View File

@ -589,7 +589,8 @@ The available presentation types (*chrono_type*) are:
represented with seconds, then the format is a decimal floating-point number
with a fixed format and a precision matching that of the precision of the
input (or to a microseconds precision if the conversion to floating-point
decimal seconds cannot be made within 18 fractional digits). The modified
decimal seconds cannot be made within 18 fractional digits). The character
for the decimal point is localized according to the locale. The modified
command <code>%OS</code> produces the locale's alternative representation.
</td>
</tr>
@ -706,19 +707,19 @@ The available padding modifiers (*padding_modifier*) are:
| Type | Meaning |
|-------|-----------------------------------------|
| `'_'` | Pad a numeric result with spaces. |
| `'-'` | Do not pad a numeric result string. |
| `'-'` | Pad a numeric result with spaces. |
| `'_'` | Do not pad a numeric result string. |
| `'0'` | Pad a numeric result string with zeros. |
These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
`'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types.
`'V'` and `'W'` presentation types.
## Range Format Specifications
Format specifications for range types have the following syntax:
<pre><code class="language-json"
>range_format_spec ::= ["n"][range_type][":" range_underlying_spec]</code>
>range_format_spec ::= ["n"][range_type][range_underlying_spec]</code>
</pre>
The `'n'` option formats the range without the opening and closing brackets.
@ -761,16 +762,14 @@ fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [h, e, l, l, o]
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [104, 101, 108, 108, 111]
fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e});
// Output: 3.141593, 2.718282
```
## Format Examples
This section contains examples of the format syntax and comparison with
the `printf` formatting.
the printf formatting.
In most of the cases the syntax is similar to the `printf` formatting,
In most of the cases the syntax is similar to the printf formatting,
with the addition of the `{}` and with `:` used instead of `%`. For
example, `"%03.2f"` can be translated to `"{:03.2f}"`.

View File

@ -17,6 +17,7 @@
#include "format.h" // std_string_view
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
@ -71,13 +72,19 @@ 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`.
*/
FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
template <typename Context>
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, char_type>::value;
detail::mapped_type_constant<T, Context>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
@ -90,7 +97,7 @@ FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
};
template <typename T>
using stored_t = conditional_t<
using stored_type = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
@ -105,37 +112,41 @@ FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
friend class basic_format_args<Context>;
auto get_types() const -> unsigned long long {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
auto data() const -> const basic_format_arg<Context>* {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(arg);
data_.emplace_back(detail::make_arg<Context>(arg));
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty())
data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
data_.emplace_back(detail::unwrap(arg.value));
if (named_info_.empty()) {
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
data_.insert(data_.begin(), {zero_ptr, 0});
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0] = {named_info_.data(), named_info_.size()};
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
constexpr dynamic_format_arg_store() = default;
operator basic_format_args<Context>() const {
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
!named_info_.empty());
}
/**
* Adds an argument into the dynamic store for later passing to a formatting
* function.
@ -153,7 +164,7 @@ FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
@ -189,7 +200,7 @@ FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
}
@ -199,20 +210,17 @@ FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = {};
dynamic_args_ = detail::dynamic_arg_list();
}
/// Reserves space to store at least `new_cap` arguments including
/// `new_cap_named` named arguments.
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
"set of arguments includes set of named arguments");
"Set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
/// Returns the number of elements in the store.
auto size() const noexcept -> size_t { return data_.size(); }
};
FMT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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 {
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)
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)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
constexpr rgb(color hex)
FMT_CONSTEXPR rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
@ -205,135 +205,97 @@ struct rgb {
namespace detail {
// A bit-packed variant of an RGB color, a terminal color, or unset color.
// see text_style for the bit-packing scheme.
// color is a struct of either a rgb color or a terminal color.
struct color_type {
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() 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 auto value() const noexcept -> uint32_t {
return value_ & 0xFFFFFF;
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 color_type(uint32_t value) noexcept : value_(value) {}
uint32_t value_ = 0;
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;
};
} // 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
: style_(static_cast<uint64_t>(em) << 54) {}
: set_foreground_color(), set_background_color(), ems(em) {}
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
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");
style_ |= rhs.style_;
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));
return *this;
}
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
friend FMT_CONSTEXPR auto operator|(text_style lhs, const 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 (style_ & (1 << 24)) != 0;
return set_foreground_color;
}
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return (style_ & (1ULL << 51)) != 0;
return set_background_color;
}
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return (style_ >> 54) != 0;
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return style_ & 0x3FFFFFF;
return foreground_color;
}
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style");
return (style_ >> 27) & 0x3FFFFFF;
return background_color;
}
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return static_cast<emphasis>(style_ >> 54);
return ems;
}
private:
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
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;
}
}
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style;
@ -341,19 +303,23 @@ class text_style {
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style;
uint64_t style_ = 0;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
/// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style {
return foreground.value_;
return text_style(true, foreground);
}
/// Creates a text style from the background color.
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style {
return static_cast<uint64_t>(background.value_) << 27;
return text_style(false, background);
}
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
@ -364,39 +330,41 @@ FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
namespace detail {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(color_type text_color,
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code
// sequence.
if (text_color.is_terminal_color()) {
if (!text_color.is_rgb) {
bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value();
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[size++] = static_cast<Char>('1');
buffer[index++] = static_cast<Char>('1');
value %= 100u;
}
buffer[size++] = static_cast<Char>('0' + value / 10u);
buffer[size++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[size++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value());
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
size = 19;
buffer[19] = static_cast<Char>(0);
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {};
@ -409,28 +377,26 @@ template <typename Char> struct ansi_color_escape {
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
size_t index = 0;
for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue;
buffer[size++] = static_cast<Char>('0' + em_codes[i]);
buffer[size++] = static_cast<Char>(';');
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
}
buffer[size - 1] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>(0);
}
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR auto end() const noexcept -> const Char* {
return buffer + size;
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {
return buffer + basic_string_view<Char>(buffer).size();
}
private:
static constexpr size_t num_emphases = 8;
Char buffer[7u + 4u * num_emphases] = {};
size_t size = 0;
Char buffer[7u + 3u * num_emphases + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept {
@ -446,13 +412,13 @@ template <typename Char> struct ansi_color_escape {
};
template <typename Char>
FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
}
template <typename Char>
FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;");
}
@ -468,33 +434,40 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
buffer.append(reset_color.begin(), reset_color.end());
}
template <typename T> struct styled_arg : view {
template <typename T> struct styled_arg : detail::view {
const T& value;
text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {}
};
template <typename Char>
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) {
void vformat_to(
buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffered_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
auto foreground = make_foreground_color<Char>(ts.get_foreground());
has_style = true;
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
auto background = make_background_color<Char>(ts.get_background());
has_style = true;
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
vformat_to(buf, fmt, args);
if (ts != text_style()) reset_color<Char>(buf);
detail::vformat_to(buf, format_str, args, {});
if (has_style) detail::reset_color<Char>(buf);
}
} // namespace detail
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
inline void vprint(FILE* f, const 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()));
@ -510,8 +483,9 @@ inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
T&&... args) {
vprint(f, ts, fmt, fmt::make_format_args(args...));
}
/**
@ -524,11 +498,11 @@ void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(text_style ts, format_string<T...> fmt, T&&... args) {
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
return print(stdout, ts, fmt, std::forward<T>(args)...);
}
inline auto vformat(text_style ts, string_view fmt, format_args args)
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
-> std::string {
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
@ -548,16 +522,16 @@ inline auto vformat(text_style ts, string_view fmt, format_args args)
* ```
*/
template <typename... T>
inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
-> std::string {
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
return fmt::vformat(ts, fmt, fmt::make_format_args(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, text_style ts, string_view fmt, format_args args)
-> OutputIt {
auto vformat_to(OutputIt out, const 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);
@ -575,9 +549,9 @@ auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
*/
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
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...}});
inline auto format_to(OutputIt out, const text_style& ts,
format_string<T...> fmt, T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt, fmt::make_format_args(args...));
}
template <typename T, typename Char>
@ -586,30 +560,31 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto& ts = arg.style;
const auto& value = arg.value;
auto out = ctx.out();
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
out = std::copy(emphasis.begin(), emphasis.end(), out);
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground());
out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
out = std::copy(foreground.begin(), foreground.end(), out);
}
if (ts.has_background()) {
has_style = true;
auto background =
detail::make_background_color<Char>(ts.get_background());
out = detail::copy<Char>(background.begin(), background.end(), out);
out = std::copy(background.begin(), background.end(), out);
}
out = formatter<T, Char>::format(arg.value, ctx);
out = formatter<T, Char>::format(value, ctx);
if (has_style) {
auto reset_color = string_view("\x1b[0m");
out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
out = std::copy(reset_color.begin(), reset_color.end(), out);
}
return out;
}

View File

@ -15,10 +15,17 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
// A compile-time string which is compiled into fast formatting code.
class compiled_string {};
FMT_EXPORT class compiled_string {};
namespace detail {
template <typename T, typename InputIt>
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
-> counting_iterator {
return it + (end - begin);
}
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
@ -35,47 +42,34 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
*/
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit)
#else
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
/**
* Converts a string literal into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires support
* for class types in constant template parameters (a C++20 feature).
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* using namespace fmt::literals;
* std::string s = fmt::format("{}"_cf, 42);
*/
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail::fixed_string Str> constexpr auto operator""_cf() {
return FMT_COMPILE(Str.data);
}
} // namespace literals
template <typename Char, size_t N,
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
explicit constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
FMT_END_EXPORT
namespace detail {
template <typename T, typename... Tail>
constexpr auto first(const T& value, const Tail&...) -> const T& {
auto first(const T& value, const Tail&...) -> const T& {
return value;
}
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... T> struct type_list {};
template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr auto get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) -> const auto& {
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
@ -83,32 +77,9 @@ constexpr auto get([[maybe_unused]] const T& first,
return detail::get<N - 1>(rest...);
}
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <int N, typename T, typename... Args, typename Char>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
if constexpr (is_static_named_arg<T>()) {
if (name == T::name) return N;
}
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<N + 1, Args...>(name);
(void)name; // Workaround an MSVC bug about "unused" parameter.
return -1;
}
# endif
template <typename... Args, typename Char>
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<0, Args...>(name);
# endif
(void)name;
return -1;
}
template <typename Char, typename... Args>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) -> int {
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
return get_arg_index_by_name<Args...>(name);
}
@ -128,8 +99,8 @@ template <typename Char> struct text {
basic_string_view<Char> data;
using char_type = Char;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data);
}
};
@ -138,8 +109,8 @@ template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char>
constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
-> text<Char> {
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
size_t size) {
return {{&s[pos], size}};
}
@ -147,8 +118,8 @@ template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
*out++ = value;
return out;
}
@ -156,7 +127,7 @@ template <typename Char> struct code_unit {
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr auto get_arg_checked(const Args&... args) -> const T& {
constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
@ -169,18 +140,17 @@ template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename V, int N> struct field {
template <typename Char, typename T, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
const T& arg = get_arg_checked<T, N>(args...);
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out);
} else {
return write<Char>(out, arg);
}
return write<Char>(out, arg);
}
};
@ -193,10 +163,10 @@ template <typename Char> struct runtime_named_field {
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static auto try_format_argument(
constexpr static bool try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
@ -206,8 +176,8 @@ template <typename Char> struct runtime_named_field {
return false;
}
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
FMT_THROW(format_error("argument with specified name is not found"));
@ -220,17 +190,17 @@ template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename V, int N> struct spec_field {
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
formatter<V, Char> fmt;
formatter<T, Char> fmt;
template <typename OutputIt, typename... T>
constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
-> OutputIt {
template <typename OutputIt, typename... Args>
constexpr FMT_INLINE OutputIt format(OutputIt out,
const Args&... args) const {
const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<V, N>(args...), ctx);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
}
};
@ -242,8 +212,8 @@ template <typename L, typename R> struct concat {
R rhs;
using char_type = typename L::char_type;
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
@ -253,14 +223,14 @@ template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R>
constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
constexpr concat<L, R> make_concat(L lhs, R rhs) {
return {lhs, rhs};
}
struct unknown_format {};
template <typename Char>
constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break;
}
@ -293,8 +263,8 @@ template <typename T, typename Char> struct parse_specs_result {
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
int next_arg_id) -> parse_specs_result<T, Char> {
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
@ -305,36 +275,32 @@ constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
}
template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id;
constexpr auto on_auto() -> int {
constexpr int on_auto() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr auto on_index(int id) -> int {
kind = arg_id_kind::index;
constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr auto on_name(basic_string_view<Char> id) -> int {
kind = arg_id_kind::name;
constexpr int on_name(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
};
template <typename Char> struct parse_arg_id_result {
arg_id_kind kind;
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
@ -397,18 +363,18 @@ constexpr auto compile_format_string(S fmt) {
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.kind == arg_id_kind::index) {
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.index;
constexpr auto arg_index = arg_id_result.arg_id.val.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
fmt);
} else if constexpr (arg_id_result.kind == arg_id_kind::name) {
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
if constexpr (arg_index >= 0) {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
@ -417,7 +383,8 @@ constexpr auto compile_format_string(S fmt) {
arg_index, next_id>(fmt);
} else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
fmt);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
@ -438,7 +405,7 @@ constexpr auto compile_format_string(S fmt) {
}
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_ENABLE_IF(detail::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) {
@ -456,28 +423,27 @@ FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... T,
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
const T&... args)
-> std::basic_string<Char> {
FMT_INLINE 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;
}
template <typename OutputIt, typename CompiledFormat, typename... T,
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
const T&... args) -> OutputIt {
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
-> std::basic_string<typename S::char_type> {
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) {
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] == '}') {
@ -490,97 +456,72 @@ FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
}
}
}
constexpr auto compiled = detail::compile<T...>(S());
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<T>(args)...);
std::forward<Args>(args)...);
} else {
return fmt::format(compiled, std::forward<T>(args)...);
return fmt::format(compiled, std::forward<Args>(args)...);
}
}
template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
constexpr auto compiled = detail::compile<T...>(S());
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::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)>,
detail::unknown_format>()) {
return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<T>(args)...);
std::forward<Args>(args)...);
} else {
return fmt::format_to(out, compiled, std::forward<T>(args)...);
return fmt::format_to(out, compiled, std::forward<Args>(args)...);
}
}
#endif
template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::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;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...);
return {buf.out(), buf.count()};
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return buf.count();
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
-> size_t {
return fmt::format_to(detail::counting_iterator(), fmt, args...).count();
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, T&&... args) {
auto buf = memory_buffer();
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
detail::print(f, {buf.data(), buf.size()});
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) {
memory_buffer buffer;
fmt::format_to(std::back_inserter(buffer), fmt, args...);
detail::print(f, {buffer.data(), buffer.size()});
}
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, T&&... args) {
print(stdout, fmt, std::forward<T>(args)...);
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) {
print(stdout, fmt, args...);
}
template <size_t N> class static_format_result {
private:
char data[N];
public:
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; }
FMT_CONSTEXPR auto c_str() const -> const char* { return data; }
};
/**
* Formats arguments according to the format string `fmt_str` and produces
* a string of the exact required size at compile time. Both the format string
* and the arguments must be compile-time expressions.
*
* The resulting string can be accessed as a C string via `c_str()` or as
* a `fmt::string_view` via `str()`.
*
* **Example**:
*
* // Produces the static string "42" at compile time.
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
* const char* s = result.c_str();
*/
#define FMT_STATIC_FORMAT(fmt_str, ...) \
fmt::static_format_result< \
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
FMT_COMPILE(fmt_str), __VA_ARGS__)
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail_exported::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>();
}
} // namespace literals
#endif
FMT_END_EXPORT
FMT_END_NAMESPACE

View File

@ -14,6 +14,10 @@
# include <climits>
# include <cmath>
# include <exception>
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
# include <locale>
# endif
#endif
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
@ -22,61 +26,17 @@
#include "format.h"
#if FMT_USE_LOCALE && !defined(FMT_MODULE)
# include <locale>
#endif
#ifndef FMT_FUNC
# define FMT_FUNC
#endif
#if defined(FMT_USE_FULL_CACHE_DRAGONBOX)
// Use the provided definition.
#elif defined(__OPTIMIZE_SIZE__)
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
#else
# define FMT_USE_FULL_CACHE_DRAGONBOX 1
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
#ifndef FMT_CUSTOM_ASSERT_FAIL
FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
// Use unchecked std::fprintf to avoid triggering another assertion when
// writing to stderr fails.
// writing to stderr fails
std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
abort();
// Chosen instead of std::abort to satisfy Clang in CUDA mode during device
// code pass.
std::terminate();
}
#endif
#if FMT_USE_LOCALE
namespace detail {
using std::locale;
using std::numpunct;
using std::use_facet;
} // namespace detail
#else
namespace detail {
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
} // namespace detail
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
using namespace detail;
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
namespace detail {
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
string_view message) noexcept {
@ -101,67 +61,86 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
FMT_ASSERT(out.size() <= inline_buffer_size, "");
}
FMT_FUNC void do_report_error(format_func func, int error_code,
FMT_FUNC void report_error(format_func func, int error_code,
const char* message) noexcept {
memory_buffer full_message;
func(full_message, error_code, message);
// Don't use fwrite_all because the latter may throw.
// Don't use fwrite_fully because the latter may throw.
if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0)
std::fputc('\n', stderr);
}
// A wrapper around fwrite that throws on error.
inline void fwrite_all(const void* ptr, size_t count, FILE* stream) {
inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) {
size_t written = std::fwrite(ptr, 1, count, stream);
if (written < count)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, "");
}
template <typename Locale> auto locale_ref::get() const -> Locale {
static_assert(std::is_same<Locale, std::locale>::value, "");
return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
}
template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>());
auto& facet = std::use_facet<std::numpunct<Char>>(loc.get<std::locale>());
auto grouping = facet.grouping();
auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
return {std::move(grouping), thousands_sep};
}
template <typename Char>
FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char {
return use_facet<numpunct<Char>>(loc.get<locale>()).decimal_point();
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
.decimal_point();
}
#else
template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result<Char> {
return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR};
}
template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.';
}
#endif
#if FMT_USE_LOCALE
FMT_FUNC auto write_loc(appender out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool {
#ifdef FMT_STATIC_THOUSANDS_SEPARATOR
value.visit(loc_writer<>{
out, specs, std::string(1, FMT_STATIC_THOUSANDS_SEPARATOR), "\3", "."});
return true;
#else
auto locale = loc.get<std::locale>();
// We cannot use the num_put<char> facet because it may produce output in
// a wrong encoding.
using facet = format_facet<std::locale>;
if (std::has_facet<facet>(locale))
return use_facet<facet>(locale).put(out, value, specs);
return std::use_facet<facet>(locale).put(out, value, specs);
return facet(locale).put(out, value, specs);
}
#endif
}
} // namespace detail
FMT_FUNC void report_error(const char* message) {
#if FMT_MSC_VERSION || defined(__NVCC__)
// Silence unreachable code warnings in MSVC and NVCC because these
// are nearly impossible to fix in a generic code.
volatile bool b = true;
if (!b) return;
#endif
FMT_THROW(format_error(message));
}
template <typename Locale> typename Locale::id format_facet<Locale>::id;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& np = detail::use_facet<detail::numpunct<char>>(loc);
grouping_ = np.grouping();
if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep());
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
grouping_ = numpunct.grouping();
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
}
#if FMT_USE_LOCALE
template <>
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
appender out, loc_value val, const format_specs& specs) const -> bool {
@ -184,11 +163,11 @@ inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {
}
// Compilers should be able to optimize this into the ror instruction.
FMT_INLINE auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
r &= 31;
return (n >> r) | (n << (32 - r));
}
FMT_INLINE auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
r &= 63;
return (n >> r) | (n << (64 - r));
}
@ -222,7 +201,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 div_small_pow10_infos_struct {
FMT_INLINE_VARIABLE constexpr struct {
uint32_t divisor;
int shift_amount;
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
@ -285,7 +264,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 uint64_t pow10_significands[] = {
static constexpr const uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
@ -380,7 +359,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 uint128_fallback pow10_significands[] = {
static constexpr const uint128_fallback pow10_significands[] = {
#if FMT_USE_FULL_CACHE_DRAGONBOX
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0x9faacf3df73609b1, 0x77b191618c54e9ad},
@ -1047,7 +1026,7 @@ template <> struct cache_accessor<double> {
#if FMT_USE_FULL_CACHE_DRAGONBOX
return pow10_significands[k - float_info<double>::min_k];
#else
static constexpr uint64_t powers_of_5_64[] = {
static constexpr const uint64_t powers_of_5_64[] = {
0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
@ -1107,7 +1086,7 @@ template <> struct cache_accessor<double> {
return {r.high(), r.low() == 0};
}
static auto compute_delta(const cache_entry_type& cache, int beta) noexcept
static auto compute_delta(cache_entry_type const& cache, int beta) noexcept
-> uint32_t {
return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
}
@ -1159,8 +1138,8 @@ auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {
exponent <= case_shorter_interval_left_endpoint_upper_threshold;
}
// Remove trailing zeros from n and return the number of zeros removed (float).
FMT_INLINE auto remove_trailing_zeros(uint32_t& n, int s = 0) noexcept -> int {
// Remove trailing zeros from n and return the number of zeros removed (float)
FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
FMT_ASSERT(n != 0, "");
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
constexpr uint32_t mod_inv_5 = 0xcccccccd;
@ -1180,19 +1159,22 @@ FMT_INLINE auto remove_trailing_zeros(uint32_t& n, int s = 0) noexcept -> int {
return s;
}
// Removes trailing zeros and returns the number of zeros removed (double).
FMT_INLINE auto remove_trailing_zeros(uint64_t& n) noexcept -> int {
// Removes trailing zeros and returns the number of zeros removed (double)
FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
FMT_ASSERT(n != 0, "");
// This magic number is ceil(2^90 / 10^8).
constexpr uint64_t magic_number = 12379400392853802749ull;
auto nm = umul128(n, magic_number);
// Is n is divisible by 10^8?
constexpr uint32_t ten_pow_8 = 100000000u;
if ((n % ten_pow_8) == 0) {
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
// If yes, work with the quotient...
auto n32 = static_cast<uint32_t>(n / ten_pow_8);
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
// ... and use the 32 bit variant of the function
int num_zeros = remove_trailing_zeros(n32, 8);
int s = remove_trailing_zeros(n32, 8);
n = n32;
return num_zeros;
return s;
}
// If n is not divisible by 10^8, work with n itself.
@ -1217,7 +1199,7 @@ FMT_INLINE auto remove_trailing_zeros(uint64_t& n) noexcept -> int {
// The main algorithm for shorter interval case
template <typename T>
FMT_INLINE auto shorter_interval_case(int exponent) noexcept -> decimal_fp<T> {
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
decimal_fp<T> ret_value;
// Compute k and beta
const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);
@ -1443,7 +1425,7 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
FMT_FUNC void report_system_error(int error_code,
const char* message) noexcept {
do_report_error(format_system_error, error_code, message);
report_error(format_system_error, error_code, message);
}
FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
@ -1456,40 +1438,17 @@ FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
namespace detail {
FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
locale_ref loc) {
auto out = appender(buf);
if (fmt.size() == 2 && equal2(fmt.data(), "{}"))
return args.get(0).visit(default_arg_formatter<char>{out});
parse_format_string(fmt,
format_handler<>{parse_context<>(fmt), {out, args, loc}});
}
template <typename T> struct span {
T* data;
size_t size;
};
template <typename F> auto flockfile(F* f) -> decltype(_lock_file(f)) {
_lock_file(f);
}
template <typename F> auto funlockfile(F* f) -> decltype(_unlock_file(f)) {
_unlock_file(f);
}
#ifndef getc_unlocked
template <typename F> auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) {
return _fgetc_nolock(f);
}
#ifdef _WIN32
inline void flockfile(FILE* f) { _lock_file(f); }
inline void funlockfile(FILE* f) { _unlock_file(f); }
inline int getc_unlocked(FILE* f) { return _fgetc_nolock(f); }
#endif
template <typename F = FILE, typename Enable = void>
struct has_flockfile : std::false_type {};
template <typename F>
struct has_flockfile<F, void_t<decltype(flockfile(&std::declval<F&>()))>>
: std::true_type {};
// A FILE wrapper. F is FILE defined as a template parameter to make system API
// detection work.
template <typename F> class file_base {
@ -1533,7 +1492,7 @@ template <typename F> class glibc_file : public file_base<F> {
}
void init_buffer() {
if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return;
if (this->file_->_IO_write_ptr) return;
// Force buffer initialization by placing and removing a char in a buffer.
putc_unlocked(0, this->file_);
--this->file_->_IO_write_ptr;
@ -1553,11 +1512,10 @@ template <typename F> class glibc_file : public file_base<F> {
void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; }
auto needs_flush() const -> bool {
bool needs_flush() const {
if ((this->file_->_flags & line_buffered) == 0) return false;
char* end = this->file_->_IO_write_end;
auto size = max_of<ptrdiff_t>(this->file_->_IO_write_ptr - end, 0);
return memchr(end, '\n', static_cast<size_t>(size));
return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end));
}
void flush() { fflush_unlocked(this->file_); }
@ -1581,7 +1539,7 @@ template <typename F> class apple_file : public file_base<F> {
void init_buffer() {
if (this->file_->_p) return;
// Force buffer initialization by placing and removing a char in a buffer.
if (!FMT_CLANG_ANALYZER) putc_unlocked(0, this->file_);
putc_unlocked(0, this->file_);
--this->file_->_p;
++this->file_->_w;
}
@ -1602,7 +1560,7 @@ template <typename F> class apple_file : public file_base<F> {
this->file_->_w -= size;
}
auto needs_flush() const -> bool {
bool needs_flush() const {
if ((this->file_->_flags & line_buffered) == 0) return false;
return memchr(this->file_->_p + this->file_->_w, '\n',
to_unsigned(-this->file_->_w));
@ -1643,7 +1601,7 @@ template <typename F> class fallback_file : public file_base<F> {
};
#ifndef FMT_USE_FALLBACK_FILE
# define FMT_USE_FALLBACK_FILE 0
# define FMT_USE_FALLBACK_FILE 1
#endif
template <typename F,
@ -1661,15 +1619,7 @@ inline auto get_file(FILE* f, ...) -> fallback_file<FILE> { return f; }
using file_ref = decltype(get_file(static_cast<FILE*>(nullptr), 0));
template <typename F = FILE, typename Enable = void>
class file_print_buffer : public buffer<char> {
public:
explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {}
};
template <typename F>
class file_print_buffer<F, enable_if_t<has_flockfile<F>::value>>
: public buffer<char> {
private:
file_ref file_;
@ -1684,7 +1634,7 @@ class file_print_buffer<F, enable_if_t<has_flockfile<F>::value>>
}
public:
explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) {
explicit file_print_buffer(FILE* f) : buffer(grow, size_t()), file_(f) {
flockfile(f);
file_.init_buffer();
auto buf = file_.get_write_buffer();
@ -1693,8 +1643,7 @@ class file_print_buffer<F, enable_if_t<has_flockfile<F>::value>>
~file_print_buffer() {
file_.advance_write_buffer(size());
bool flush = file_.needs_flush();
F* f = file_; // Make funlockfile depend on the template parameter F
funlockfile(f); // for the system API detection to work.
funlockfile(file_);
if (flush) fflush(file_);
}
};
@ -1720,7 +1669,7 @@ FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args,
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args);
if (newline) buffer.push_back('\n');
fwrite_all(buffer.data(), buffer.size(), f);
fwrite_fully(buffer.data(), buffer.size(), f);
}
#endif
@ -1732,7 +1681,7 @@ FMT_FUNC void print(std::FILE* f, string_view text) {
if (write_console(fd, text)) return;
}
#endif
fwrite_all(text.data(), text.size(), f);
fwrite_fully(text.data(), text.size(), f);
}
} // namespace detail
@ -1743,9 +1692,8 @@ FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) {
}
FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {
if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>())
return vprint_buffered(f, fmt, args);
auto&& buffer = detail::file_print_buffer<>(f);
if (!detail::file_ref(f).is_buffered()) return vprint_buffered(f, fmt, args);
auto&& buffer = detail::file_print_buffer(f);
return detail::vformat_to(buffer, fmt, args);
}

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,7 @@
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
!defined(__wasm__)
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
# else
@ -119,7 +118,7 @@ FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
}
FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
format_args args);
/**
@ -136,9 +135,10 @@ FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
* **Example**:
*
* // This throws a system_error with the description
* // cannot open file 'foo': The system cannot find the file specified.
* // or similar (system message may vary) if the file doesn't exist.
* const char *filename = "foo";
* // cannot open file 'madeup': The system cannot find the file
* specified.
* // or similar (system message may vary).
* const char *filename = "madeup";
* LPOFSTRUCT of = LPOFSTRUCT();
* HFILE file = OpenFile(filename, &of, OF_READ);
* if (file == HFILE_ERROR) {
@ -146,10 +146,10 @@ FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
* "cannot open file '{}'", filename);
* }
*/
template <typename... T>
auto windows_error(int error_code, string_view message, const T&... args)
-> std::system_error {
return vwindows_error(error_code, message, vargs<T...>{{args...}});
template <typename... Args>
std::system_error windows_error(int error_code, string_view message,
const Args&... args) {
return vwindows_error(error_code, message, fmt::make_format_args(args...));
}
// Reports a Windows error without throwing an exception.
@ -161,6 +161,14 @@ inline auto system_category() noexcept -> const std::error_category& {
}
#endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
}
#endif
// A buffered file.
class buffered_file {
private:
@ -168,24 +176,24 @@ class buffered_file {
friend class file;
inline explicit buffered_file(FILE* f) : file_(f) {}
explicit buffered_file(FILE* f) : file_(f) {}
public:
buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file.
inline buffered_file() noexcept : file_(nullptr) {}
buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() noexcept;
public:
inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
inline auto operator=(buffered_file&& other) -> buffered_file& {
auto operator=(buffered_file&& other) -> buffered_file& {
close();
file_ = other.file_;
other.file_ = nullptr;
@ -199,13 +207,13 @@ class buffered_file {
FMT_API void close();
// Returns the pointer to a FILE object representing this file.
inline auto get() const noexcept -> FILE* { return file_; }
auto get() const noexcept -> FILE* { return file_; }
FMT_API auto descriptor() const -> int;
template <typename... T>
inline void print(string_view fmt, const T&... args) {
fmt::vargs<T...> vargs = {{args...}};
const auto& vargs = fmt::make_format_args(args...);
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
: fmt::vprint(file_, fmt, vargs);
}
@ -240,7 +248,7 @@ class FMT_API file {
};
// Constructs a file object which doesn't represent any file.
inline file() noexcept : fd_(-1) {}
file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag);
@ -249,10 +257,10 @@ class FMT_API file {
file(const file&) = delete;
void operator=(const file&) = delete;
inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw.
inline auto operator=(file&& other) -> file& {
auto operator=(file&& other) -> file& {
close();
fd_ = other.fd_;
other.fd_ = -1;
@ -263,7 +271,7 @@ class FMT_API file {
~file() noexcept;
// Returns the file descriptor.
inline auto descriptor() const noexcept -> int { return fd_; }
auto descriptor() const noexcept -> int { return fd_; }
// Closes the file.
void close();
@ -316,9 +324,9 @@ auto getpagesize() -> long;
namespace detail {
struct buffer_size {
constexpr buffer_size() = default;
buffer_size() = default;
size_t value = 0;
FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size();
bs.value = val;
return bs;
@ -329,7 +337,7 @@ struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
constexpr ostream_params() {}
ostream_params() {}
template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
@ -350,47 +358,59 @@ struct ostream_params {
# endif
};
} // namespace detail
FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
/// A fast buffered output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class ostream : private detail::buffer<char> {
class file_buffer final : public buffer<char> {
private:
file file_;
FMT_API ostream(cstring_view path, const detail::ostream_params& params);
FMT_API static void grow(buffer<char>& buf, size_t);
public:
FMT_API ostream(ostream&& other) noexcept;
FMT_API ~ostream();
FMT_API file_buffer(cstring_view path, const ostream_params& params);
FMT_API file_buffer(file_buffer&& other) noexcept;
FMT_API ~file_buffer();
operator writer() {
detail::buffer<char>& buf = *this;
return buf;
}
inline void flush() {
void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream;
inline void close() {
void close() {
flush();
file_.close();
}
};
} // namespace detail
constexpr auto buffer_size = detail::buffer_size();
/// A fast output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream {
private:
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: buffer_(path, params) {}
public:
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { buffer_.close(); }
/// Formats `args` according to specifications in `fmt` and writes the
/// output to the file.
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});
vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...));
}
};

View File

@ -22,39 +22,66 @@
#include "chrono.h" // formatbuf
#ifdef _MSVC_STL_UPDATE
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
#else
# define FMT_MSVC_STL_UPDATE 0
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
// Generate a unique explicit instantiation in every translation unit using a
// tag type in an anonymous namespace.
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
namespace {
struct file_access_tag {};
} // namespace
template <typename Tag, typename BufType, FILE* BufType::* FileMemberPtr>
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
#if FMT_MSVC_STL_UPDATE
#if FMT_MSC_VERSION
template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
#endif
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
-> bool {
FILE* f = nullptr;
#if FMT_MSC_VERSION && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = get_file(*buf);
else
return false;
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
else
return false;
#else
ignore_unused(os, data, f);
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
return write_console(fd, data);
}
}
#endif
return false;
}
inline auto write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) -> bool {
return false;
}
// Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = make_unsigned_t<std::streamsize>;
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do {
@ -65,9 +92,21 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
} while (size != 0);
}
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
output.imbue(std::locale::classic()); // The default is always unlocalized.
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view {
const T& value;
};
} // namespace detail
// Formats an object of type T that has an overloaded ostream operator<<.
@ -78,11 +117,7 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
template <typename T, typename Context>
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
auto buffer = basic_memory_buffer<Char>();
auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
auto&& output = std::basic_ostream<Char>(&formatbuf);
output.imbue(std::locale::classic()); // The default is always unlocalized.
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
detail::format_value(buffer, value);
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
@ -113,30 +148,24 @@ constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}
inline void vprint(std::ostream& os, string_view fmt, format_args args) {
namespace detail {
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args);
FILE* f = nullptr;
#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = detail::get_file(*buf);
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;
}
}
#endif
detail::ignore_unused(f);
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
} // namespace detail
FMT_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str,
typename detail::vformat_args<Char>::type args) {
auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args);
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
detail::write_buffer(os, buffer);
}
@ -149,17 +178,32 @@ 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::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);
const auto& vargs = fmt::make_format_args(args...);
if (detail::use_utf8())
vprint(os, fmt, vargs);
else
detail::vprint_directly(os, fmt, vargs);
}
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
}
FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, FMT_STRING("{}\n"),
fmt::format(fmt, std::forward<T>(args)...));
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
}
FMT_END_NAMESPACE

View File

@ -9,7 +9,7 @@
#define FMT_PRINTF_H_
#ifndef FMT_MODULE
# include <algorithm> // std::find
# include <algorithm> // std::max
# include <limits> // std::numeric_limits
#endif
@ -18,6 +18,10 @@
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename Char> class basic_printf_context {
private:
basic_appender<Char> out_;
@ -29,7 +33,8 @@ template <typename Char> class basic_printf_context {
public:
using char_type = Char;
enum { builtin_types = 1 };
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
/// Constructs a `printf_context` object. References to the arguments are
/// stored in the context object so make sure they have appropriate lifetimes.
@ -40,7 +45,7 @@ template <typename Char> class basic_printf_context {
auto out() -> basic_appender<Char> { return out_; }
void advance_to(basic_appender<Char>) {}
auto locale() -> locale_ref { return {}; }
auto locale() -> detail::locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
@ -49,30 +54,14 @@ template <typename Char> class basic_printf_context {
namespace detail {
// Return the result via the out param to workaround gcc bug 77539.
template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
for (out = first; out != last; ++out) {
if (*out == value) return true;
}
return false;
}
template <>
inline auto find<false, char>(const char* first, const char* last, char value,
const char*& out) -> bool {
out =
static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));
return out != nullptr;
}
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IS_SIGNED> struct int_checker {
template <bool IsSigned> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool {
return value <= to_unsigned(max_value<int>());
unsigned max = to_unsigned(max_value<int>());
return value <= max;
}
inline static auto fits_in_int(bool) -> bool { return true; }
static auto fits_in_int(bool) -> bool { return true; }
};
template <> struct int_checker<true> {
@ -80,7 +69,7 @@ template <> struct int_checker<true> {
return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>();
}
inline static auto fits_in_int(int) -> bool { return true; }
static auto fits_in_int(int) -> bool { return true; }
};
struct printf_precision_handler {
@ -88,7 +77,7 @@ struct printf_precision_handler {
auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
report_error("number is too big");
return max_of(static_cast<int>(value), 0);
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -138,19 +127,25 @@ template <typename T, typename Context> class arg_converter {
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
if (is_signed)
arg_ = static_cast<int>(static_cast<target_type>(value));
else
arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));
if (is_signed) {
auto n = static_cast<int>(static_cast<target_type>(value));
arg_ = detail::make_arg<Context>(n);
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
} else {
if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
if (is_signed)
arg_ = static_cast<long long>(value);
else
arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);
auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else {
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
}
}
@ -177,7 +172,8 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
arg_ = static_cast<typename Context::char_type>(value);
auto c = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -198,13 +194,13 @@ class printf_width_handler {
format_specs& specs_;
public:
inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) {
specs_.set_align(align::left);
specs_.align = align::left;
width = 0 - width;
}
unsigned int_max = to_unsigned(max_value<int>());
@ -238,74 +234,69 @@ class printf_arg_formatter : public arg_formatter<Char> {
void write_null_pointer(bool is_string = false) {
auto s = this->specs;
s.set_type(presentation_type::none);
s.type = presentation_type::none;
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
}
template <typename T> void write(T value) {
detail::write<Char>(this->out, value, this->specs, this->locale);
}
public:
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { write(value); }
void operator()(monostate value) { base::operator()(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (!std::is_same<T, Char>::value) {
write(value);
base::operator()(value);
return;
}
format_specs s = this->specs;
if (s.type() != presentation_type::none &&
s.type() != presentation_type::chr) {
if (s.type != presentation_type::none && s.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
s.set_sign(sign::none);
s.clear_alt();
s.set_fill(' '); // Ignore '0' flag for char types.
s.sign = sign::none;
s.alt = false;
s.fill = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (s.align() == align::none || s.align() == align::numeric)
s.set_align(align::right);
detail::write<Char>(this->out, static_cast<Char>(value), s);
if (s.align == align::none || s.align == align::numeric)
s.align = align::right;
write<Char>(this->out, static_cast<Char>(value), s);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) {
write(value);
base::operator()(value);
}
void operator()(const char* value) {
if (value)
write(value);
base::operator()(value);
else
write_null_pointer(this->specs.type() != presentation_type::pointer);
write_null_pointer(this->specs.type != presentation_type::pointer);
}
void operator()(const wchar_t* value) {
if (value)
write(value);
base::operator()(value);
else
write_null_pointer(this->specs.type() != presentation_type::pointer);
write_null_pointer(this->specs.type != presentation_type::pointer);
}
void operator()(basic_string_view<Char> value) { write(value); }
void operator()(basic_string_view<Char> value) { base::operator()(value); }
void operator()(const void* value) {
if (value)
write(value);
base::operator()(value);
else
write_null_pointer();
}
void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = parse_context<Char>({});
auto parse_ctx = basic_format_parse_context<Char>({});
handle.format(parse_ctx, context_);
}
};
@ -314,14 +305,23 @@ template <typename Char>
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-': specs.set_align(align::left); break;
case '+': specs.set_sign(sign::plus); break;
case '0': specs.set_fill('0'); break;
case ' ':
if (specs.sign() != sign::plus) specs.set_sign(sign::space);
case '-':
specs.align = align::left;
break;
case '#': specs.set_alt(); break;
default: return;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill = '0';
break;
case ' ':
if (specs.sign != sign::plus) specs.sign = sign::space;
break;
case '#':
specs.alt = true;
break;
default:
return;
}
}
}
@ -339,7 +339,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs,
++it;
arg_index = value != -1 ? value : max_value<int>();
} else {
if (c == '0') specs.set_fill('0');
if (c == '0') specs.fill = '0';
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
@ -369,22 +369,43 @@ inline auto parse_printf_presentation_type(char c, type t, bool& upper)
using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) {
case 'd': return in(t, integral_set) ? pt::dec : pt::none;
case 'o': return in(t, integral_set) ? pt::oct : pt::none;
case 'X': upper = true; FMT_FALLTHROUGH;
case 'x': return in(t, integral_set) ? pt::hex : pt::none;
case 'E': upper = true; FMT_FALLTHROUGH;
case 'e': return in(t, float_set) ? pt::exp : pt::none;
case 'F': upper = true; FMT_FALLTHROUGH;
case 'f': return in(t, float_set) ? pt::fixed : pt::none;
case 'G': upper = true; FMT_FALLTHROUGH;
case 'g': return in(t, float_set) ? pt::general : pt::none;
case 'A': upper = true; FMT_FALLTHROUGH;
case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c': return in(t, integral_set) ? pt::chr : pt::none;
case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default: return pt::none;
case 'd':
return in(t, integral_set) ? pt::dec : pt::none;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'X':
upper = true;
FMT_FALLTHROUGH;
case 'x':
return in(t, integral_set) ? pt::hex : pt::none;
case 'E':
upper = true;
FMT_FALLTHROUGH;
case 'e':
return in(t, float_set) ? pt::exp : pt::none;
case 'F':
upper = true;
FMT_FALLTHROUGH;
case 'f':
return in(t, float_set) ? pt::fixed : pt::none;
case 'G':
upper = true;
FMT_FALLTHROUGH;
case 'g':
return in(t, float_set) ? pt::general : pt::none;
case 'A':
upper = true;
FMT_FALLTHROUGH;
case 'a':
return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
}
}
@ -394,7 +415,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
using iterator = basic_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = parse_context<Char>(format);
auto parse_ctx = basic_format_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
@ -403,9 +424,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
auto arg = context.arg(arg_index);
if (!arg) report_error("argument not found");
return arg;
return detail::get_arg(context, arg_index);
};
const Char* start = parse_ctx.begin();
@ -425,7 +444,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs();
specs.set_align(align::right);
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg);
@ -449,9 +468,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && is_integral_type(arg.type())) {
if (specs.precision >= 0 && arg.is_integral()) {
// Ignore '0' for non-numeric types or if '-' present.
specs.set_fill(' ');
specs.fill = ' ';
}
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = arg.visit(get_cstring<Char>());
@ -459,16 +478,15 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto nul = std::find(str, str_end, Char());
auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = sv;
}
if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
if (specs.fill_unit<Char>() == '0') {
if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
specs.set_align(align::numeric);
} else {
// Ignore '0' flag for non-numeric types or if '-' flag is also present.
specs.set_fill(' ');
arg = make_arg<basic_printf_context<Char>>(sv);
}
if (specs.alt && arg.visit(is_zero_int())) specs.alt = false;
if (specs.fill.template get<Char>() == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
else
specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-'
// flag is also present.
}
// Parse length and convert the argument to the required type.
@ -493,34 +511,44 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
convert_arg<long>(arg, t);
}
break;
case 'j': convert_arg<intmax_t>(arg, t); break;
case 'z': convert_arg<size_t>(arg, t); break;
case 't': convert_arg<std::ptrdiff_t>(arg, t); break;
case 'j':
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L':
// printf produces garbage when 'L' is omitted for long double, no
// need to do the same.
break;
default: --it; convert_arg<void>(arg, c);
default:
--it;
convert_arg<void>(arg, c);
}
// Parse type.
if (it == end) report_error("invalid format string");
char type = static_cast<char>(*it++);
if (is_integral_type(arg.type())) {
if (arg.is_integral()) {
// Normalize type.
switch (type) {
case 'i':
case 'u': type = 'd'; break;
case 'u':
type = 'd';
break;
case 'c':
arg.visit(char_converter<basic_printf_context<Char>>(arg));
break;
}
}
bool upper = false;
specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));
if (specs.type() == presentation_type::none)
specs.type = parse_printf_presentation_type(type, arg.type(), upper);
if (specs.type == presentation_type::none)
report_error("invalid format specifier");
if (upper) specs.set_upper();
specs.upper = upper;
start = it;
@ -555,7 +583,7 @@ inline auto vsprintf(basic_string_view<Char> fmt,
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
return {buf.data(), buf.size()};
return to_string(buf);
}
/**
@ -566,18 +594,14 @@ inline auto vsprintf(basic_string_view<Char> fmt,
*
* std::string message = fmt::sprintf("The answer is %d", 42);
*/
template <typename... T>
inline auto sprintf(string_view fmt, const T&... args) -> std::string {
return vsprintf(fmt, make_printf_args(args...));
}
template <typename... T>
FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
-> std::wstring {
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
template <typename S, typename... T, typename Char = char_t<S>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename Char>
auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args) -> int {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
@ -595,14 +619,17 @@ auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
*
* fmt::fprintf(stderr, "Don't %s!", "panic");
*/
template <typename... T>
inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
return vfprintf(f, fmt, make_printf_args(args...));
template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt),
make_printf_args<Char>(args...));
}
template <typename... T>
FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
template <typename Char>
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args)
-> int {
return vfprintf(stdout, fmt, args);
}
/**
@ -617,6 +644,11 @@ template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...));
}
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
}
FMT_END_EXPORT
FMT_END_NAMESPACE

View File

@ -11,6 +11,7 @@
#ifndef FMT_MODULE
# include <initializer_list>
# include <iterator>
# include <string>
# include <tuple>
# include <type_traits>
# include <utility>
@ -18,13 +19,6 @@
#include "format.h"
#if FMT_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
# define FMT_LIFETIMEBOUND [[clang::lifetimebound]]
#else
# define FMT_LIFETIMEBOUND
#endif
FMT_PRAGMA_CLANG(diagnostic error "-Wreturn-stack-address")
FMT_BEGIN_NAMESPACE
FMT_EXPORT
@ -37,7 +31,7 @@ template <typename T> class is_map {
template <typename> static void check(...);
public:
static constexpr bool value =
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -46,16 +40,29 @@ template <typename T> class is_set {
template <typename> static void check(...);
public:
static constexpr bool value =
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
};
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload
template <typename T, size_t N>
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
@ -69,13 +76,9 @@ struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
// Member function overloads.
template <typename T>
auto range_begin(T&& rng) -> decltype(static_cast<T&&>(rng).begin()) {
return static_cast<T&&>(rng).begin();
}
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
template <typename T>
auto range_end(T&& rng) -> decltype(static_cast<T&&>(rng).end()) {
return static_cast<T&&>(rng).end();
}
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
// ADL overloads. Only participate in overload resolution if member functions
// are not found.
@ -112,20 +115,21 @@ struct has_mutable_begin_end<
// SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
# undef FMT_DECLTYPE_RETURN
#endif
// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ {
template <typename U, typename V = typename std::remove_cv<U>::type>
static auto check(U* p) -> decltype(std::tuple_size<V>::value, 0);
template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
template <typename> static void check(...);
public:
static constexpr bool value =
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -159,7 +163,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 bool value = false;
static constexpr const bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <size_t... Is>
@ -175,7 +179,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
C>::value)...>{}));
public:
static constexpr bool value =
static constexpr const bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
@ -213,7 +217,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, size_t... Is>
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
@ -224,7 +228,7 @@ template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>()));
};
template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
using type = T&;
};
@ -241,6 +245,14 @@ using range_reference_type =
template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
@ -254,12 +266,12 @@ template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>;
// These are not generic lambdas for compatibility with C++11.
template <typename Char> struct parse_empty_specs {
template <typename ParseContext> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
parse_context<Char>& ctx;
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
@ -278,15 +290,14 @@ template <typename FormatContext> struct format_tuple_element {
} // namespace detail
FMT_EXPORT
template <typename T> struct is_tuple_like {
static constexpr bool value =
static constexpr const 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 bool value = detail::is_tuple_formattable_<T, C>::value;
static constexpr const bool value =
detail::is_tuple_formattable_<T, C>::value;
};
template <typename Tuple, typename Char>
@ -316,17 +327,11 @@ struct formatter<Tuple, Char,
closing_bracket_ = close;
}
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it != end && detail::to_ascii(*it) == 'n') {
++it;
set_brackets({}, {});
set_separator({});
}
if (it != end && *it != '}') report_error("invalid format specifier");
ctx.advance_to(it);
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
if (it != ctx.end() && *it != '}') report_error("invalid format specifier");
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
@ -341,24 +346,44 @@ struct formatter<Tuple, Char,
}
};
FMT_EXPORT
template <typename T, typename Char> struct is_range {
static constexpr bool value =
static constexpr const bool value =
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
};
namespace detail {
template <typename Context> struct range_mapper {
using mapper = arg_mapper<Context>;
template <typename T,
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value) -> T&& {
return static_cast<T&&>(value);
}
template <typename T,
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value)
-> decltype(mapper().map(static_cast<T&&>(value))) {
return mapper().map(static_cast<T&&>(value));
}
};
template <typename Char, typename Element>
using range_formatter_type = formatter<remove_cvref_t<Element>, Char>;
using range_formatter_type =
formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
.map(std::declval<Element>()))>,
Char>;
template <typename R>
using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char>
struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail
template <typename...> struct conjunction : std::true_type {};
@ -367,7 +392,6 @@ 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;
@ -391,7 +415,7 @@ struct range_formatter<
auto buf = basic_memory_buffer<Char>();
for (; it != end; ++it) buf.push_back(*it);
auto specs = format_specs();
specs.set_type(presentation_type::debug);
specs.type = presentation_type::debug;
return detail::write<Char>(
out, basic_string_view<Char>(buf.data(), buf.size()), specs);
}
@ -419,7 +443,8 @@ struct range_formatter<
closing_bracket_ = close;
}
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
detail::maybe_set_debug_format(underlying_, true);
@ -461,10 +486,11 @@ struct range_formatter<
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
auto mapper = detail::range_mapper<buffered_context<Char>>();
auto out = ctx.out();
auto it = detail::range_begin(range);
auto end = detail::range_end(range);
if (is_debug) return write_debug_string(out, std::move(it), end);
if (is_debug) return write_debug_string(out, it, end);
out = detail::copy<Char>(opening_bracket_, out);
int i = 0;
@ -472,7 +498,7 @@ struct range_formatter<
if (i > 0) out = detail::copy<Char>(separator_, out);
ctx.advance_to(out);
auto&& item = *it; // Need an lvalue
out = underlying_.format(item, ctx);
out = underlying_.format(mapper.map(item), ctx);
++i;
}
out = detail::copy<Char>(closing_bracket_, out);
@ -495,8 +521,13 @@ struct formatter<
range_format_kind<R, Char>::value != range_format::disabled &&
range_format_kind<R, Char>::value != range_format::map &&
range_format_kind<R, Char>::value != range_format::string &&
range_format_kind<R, Char>::value != range_format::debug_string>,
detail::is_formattable_delayed<R, Char>>::value>> {
range_format_kind<R, Char>::value != range_format::debug_string>
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>> {
private:
using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
@ -512,7 +543,8 @@ struct formatter<
detail::string_literal<Char, '}'>{});
}
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return range_formatter_.parse(ctx);
}
@ -527,9 +559,7 @@ struct formatter<
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<conjunction<
bool_constant<range_format_kind<R, Char>::value == range_format::map>,
detail::is_formattable_delayed<R, Char>>::value>> {
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
private:
using map_type = detail::maybe_const_range<R>;
using element_type = detail::uncvref_type<map_type>;
@ -541,7 +571,8 @@ struct formatter<
public:
FMT_CONSTEXPR formatter() {}
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it != end) {
@ -555,7 +586,7 @@ struct formatter<
}
ctx.advance_to(it);
}
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
@ -565,11 +596,12 @@ struct formatter<
basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
if (!no_delimiters_) out = detail::copy<Char>(open, out);
int i = 0;
auto mapper = detail::range_mapper<buffered_context<Char>>();
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
for (auto&& value : map) {
if (i > 0) out = detail::copy<Char>(sep, out);
ctx.advance_to(out);
detail::for_each2(formatters_, value,
detail::for_each2(formatters_, mapper.map(value),
detail::format_tuple_element<FormatContext>{
0, ctx, detail::string_literal<Char, ':', ' '>{}});
++i;
@ -599,7 +631,8 @@ struct formatter<
formatter<string_type, Char> underlying_;
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return underlying_.parse(ctx);
}
@ -640,22 +673,22 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
#endif
formatter<remove_cvref_t<value_type>, Char> value_formatter_;
using view = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>,
join_view<It, Sentinel, Char>>;
using view_ref = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>&,
join_view<It, Sentinel, Char>&&>;
public:
using nonlocking = void;
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
return value_formatter_.parse(ctx);
}
template <typename FormatContext>
auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) {
using iter =
conditional_t<std::is_copy_constructible<view>::value, It, It&>;
iter it = value.begin;
auto format(view_ref& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto it = std::forward<view_ref>(value).begin;
auto out = ctx.out();
if (it == value.end) return out;
out = value_formatter_.format(*it, ctx);
@ -670,123 +703,6 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
}
};
FMT_EXPORT
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
const Tuple& tuple;
basic_string_view<Char> sep;
tuple_join_view(const Tuple& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
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<Tuple, Char>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, std::tuple_size<Tuple>());
}
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, 0>)
-> const Char* {
return ctx.begin();
}
template <size_t N>
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, N>)
-> const Char* {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
report_error("incompatible format specs for tuple elements");
}
#endif
return end;
}
template <typename FormatContext>
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<Tuple, Char>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
using std::get;
auto out =
std::get<std::tuple_size<Tuple>::value - N>(formatters_)
.format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);
if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out);
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
};
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
bool_constant<range_format_kind<T, Char>::value ==
range_format::disabled>>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& v) -> all {
return {v.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(value), ctx);
}
};
FMT_BEGIN_EXPORT
/// Returns a view that formats the iterator range `[begin, end)` with elements
/// separated by `sep`.
template <typename It, typename Sentinel>
@ -808,25 +724,140 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
template <typename Range>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
}
private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
template <typename ParseContext>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
report_error("incompatible format specs for tuple elements");
}
#endif
return end;
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, 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, T...>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_)
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out);
return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
};
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
bool_constant<range_format_kind<T, Char>::value ==
range_format::disabled>>::value>>
: 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()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
}
};
FMT_BEGIN_EXPORT
/**
* Returns an object that formats `std::tuple` with elements separated by `sep`.
*
* **Example**:
*
* auto t = std::tuple<int, char>(1, 'a');
* auto t = std::tuple<int, char>{1, 'a'};
* fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a
*/
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep)
-> tuple_join_view<Tuple, char> {
template <typename... T>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
return {tuple, sep};
}

View File

@ -15,18 +15,18 @@
# include <atomic>
# include <bitset>
# include <complex>
# include <cstdlib>
# include <exception>
# include <functional> // std::reference_wrapper
# include <memory>
# include <thread>
# include <type_traits>
# include <typeinfo> // std::type_info
# include <utility> // std::make_index_sequence
# include <typeinfo>
# include <utility>
# include <vector>
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L
# if FMT_HAS_INCLUDE(<filesystem>) && \
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
# if FMT_HAS_INCLUDE(<filesystem>)
# include <filesystem>
# endif
# if FMT_HAS_INCLUDE(<variant>)
@ -60,36 +60,35 @@
# endif
#endif
#ifdef FMT_CPP_LIB_FILESYSTEM
// Use the provided definition.
#elif defined(__cpp_lib_filesystem)
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
#ifndef FMT_CPP_LIB_FILESYSTEM
# ifdef __cpp_lib_filesystem
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
#else
# else
# define FMT_CPP_LIB_FILESYSTEM 0
# endif
#endif
#ifdef FMT_CPP_LIB_VARIANT
// Use the provided definition.
#elif defined(__cpp_lib_variant)
#ifndef FMT_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
#else
# else
# define FMT_CPP_LIB_VARIANT 0
# endif
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
#if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> &&
std::is_same_v<PathChar, wchar_t>) {
return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf);
} else {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
else
return p.string<Char>();
}
}
template <typename Char, typename PathChar>
@ -110,180 +109,9 @@ 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, typename FormatContext>
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
-> 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);
formatter<std::remove_cv_t<T>, Char> underlying;
maybe_set_debug_format(underlying, true);
return underlying.format(v, ctx);
}
#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 auto check(std::index_sequence<Is...>) -> std::conjunction<
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
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
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
char* begin) -> string_view {
// 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* to = begin + 5; // std::
for (const 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] == '_') {
const 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)};
}
return demangled_name_view;
}
template <class OutputIt>
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
-> OutputIt {
const string_view demangled_name(abi_name_view);
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;
}
template <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), &free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = normalize_libcxx_inline_namespaces(
demangled_name_ptr.get(), demangled_name_ptr.get());
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
return normalize_msvc_abi_name(ti.name(), out);
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
const string_view demangled_name = ti.name();
std::string name_copy(demangled_name.size(), '\0');
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
// front of types
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
name_copy.end());
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
// environments, we need to eliminate both of them.
const string_view normalized_name =
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
return detail::write_bytes<char>(out, normalized_name);
# 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.
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
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
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
FMT_EXPORT
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs specs_;
@ -294,16 +122,14 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
auto it = ctx.begin(), end = ctx.end();
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 == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
@ -319,7 +145,7 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
!path_type_ ? p.native()
: p.generic_string<std::filesystem::path::value_type>();
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string);
@ -348,20 +174,24 @@ class path : public std::filesystem::path {
auto generic_system_string() const -> std::string { return generic_string(); }
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM
template <size_t N, typename Char>
struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> {
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
private:
// This is a functor because C++11 doesn't support generic lambdas.
// 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;
}
};
@ -370,28 +200,41 @@ struct formatter<std::bitset<N>, Char>
template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) {
return this->write_padded(ctx, writer{bs});
return write_padded(ctx, writer{bs});
}
};
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>> {
private:
formatter<std::remove_cv_t<T>, Char> underlying_;
formatter<T, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
detail::maybe_set_debug_format(underlying_, true);
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
@ -407,15 +250,37 @@ 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 ||
is_formattable<T, Char>::value) &&
std::enable_if_t<is_formattable<T, Char>::value &&
is_formattable<E, Char>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
@ -426,21 +291,25 @@ struct formatter<std::expected<T, E>, Char,
if (value.has_value()) {
out = detail::write<Char>(out, "expected(");
if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
out = detail::write_escaped_alternative<Char>(out, *value);
} else {
out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error(), ctx);
out = detail::write_escaped_alternative<Char>(out, value.error());
}
*out++ = ')';
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(); }
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::source_location& loc, FormatContext& ctx) const
@ -456,16 +325,48 @@ 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> struct is_variant_like {
static constexpr bool value = detail::is_variant_like_<T>::value;
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;
};
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* {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
@ -476,12 +377,14 @@ template <typename Char> struct formatter<std::monostate, Char> {
}
};
FMT_EXPORT
template <typename Variant, typename Char>
struct formatter<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* {
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
@ -494,7 +397,7 @@ struct formatter<Variant, Char,
FMT_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_escaped_alternative<Char>(out, v, ctx);
out = detail::write_escaped_alternative<Char>(out, v);
},
value);
}
@ -505,87 +408,128 @@ struct formatter<Variant, Char,
return out;
}
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT
template <> struct formatter<std::error_code> {
private:
format_specs specs_;
detail::arg_ref<char> width_ref_;
bool debug_ = false;
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
char c = *it;
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;
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR20 auto format(const std::error_code& ec,
FormatContext& ctx) const -> decltype(ctx.out()) {
auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
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);
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes<Char>(out, ec.category().name(), format_specs());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
#if FMT_USE_RTTI
template <> struct formatter<std::type_info> {
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.
> {
public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename Context>
auto format(const std::type_info& ti, Context& ctx) const
-> decltype(ctx.out()) {
return detail::write_demangled_name(ctx.out(), ti);
return detail::write_demangled_name<Char>(ctx.out(), ti);
}
};
#endif // FMT_USE_RTTI
#endif
template <typename T>
FMT_EXPORT
template <typename T, typename Char>
struct formatter<
T, char,
T, Char, // DEPRECATED! Mixing code unit types.
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
@ -602,18 +546,47 @@ struct formatter<
auto out = ctx.out();
#if FMT_USE_RTTI
if (with_typename_) {
out = detail::write_demangled_name(out, typeid(ex));
out = detail::write_demangled_name<Char>(out, typeid(ex));
*out++ = ':';
*out++ = ' ';
}
#endif
return detail::write_bytes<char>(out, string_view(ex.what()));
return detail::write_bytes<Char>(out, string_view(ex.what()));
}
};
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>>
@ -625,6 +598,15 @@ 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>>
@ -637,6 +619,7 @@ 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,11 +630,7 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
};
#endif // __cpp_lib_atomic_flag_test
template <typename T> struct is_tuple_like;
template <typename T>
struct is_tuple_like<std::complex<T>> : std::false_type {};
FMT_EXPORT
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private:
detail::dynamic_format_specs<Char> specs_;
@ -664,7 +643,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
if (c.real() != 0) {
*out++ = Char('(');
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
specs.set_sign(sign::plus);
specs.sign = sign::plus;
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i');
@ -678,7 +657,8 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type_constant<T, Char>::value);
@ -688,11 +668,12 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto format(const std::complex<T>& c, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs = specs_;
if (specs.dynamic()) {
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,
if (specs.width_ref.kind != detail::arg_id_kind::none ||
specs.precision_ref.kind != detail::arg_id_kind::none) {
detail::handle_dynamic_spec<detail::width_checker>(specs.width,
specs.width_ref, ctx);
detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,
specs.precision_ref, ctx);
detail::handle_dynamic_spec<detail::precision_checker>(
specs.precision, specs.precision_ref, ctx);
}
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
@ -700,12 +681,12 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto outer_specs = format_specs();
outer_specs.width = specs.width;
outer_specs.copy_fill_from(specs);
outer_specs.set_align(specs.align());
outer_specs.fill = specs.fill;
outer_specs.align = specs.align;
specs.width = 0;
specs.set_fill({});
specs.set_align(align::none);
specs.fill = {};
specs.align = align::none;
do_format(c, specs, ctx, basic_appender<Char>(buf));
return detail::write<Char>(ctx.out(),
@ -714,21 +695,5 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
}
};
template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
// 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
-> decltype(ctx.out()) {
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
}
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_

View File

@ -10,12 +10,11 @@
#include "color.h"
#include "format.h"
#include "ostream.h"
#include "ranges.h"
#ifndef FMT_MODULE
# include <cwchar>
# if FMT_USE_LOCALE
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
# include <locale>
# endif
#endif
@ -35,8 +34,7 @@ struct format_string_char<
};
template <typename S>
struct format_string_char<
S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> {
using type = typename S::char_type;
};
@ -45,7 +43,7 @@ using format_string_char_t = typename format_string_char<S>::type;
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool {
#if FMT_USE_LOCALE
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
@ -55,72 +53,36 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
#endif
return false;
}
template <typename Char>
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args,
locale_ref loc = {}) {
static_assert(!std::is_same<Char, char>::value, "");
auto out = basic_appender<Char>(buf);
parse_format_string(
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
}
} // namespace detail
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = parse_context<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
using wformat_context = buffered_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>;
template <typename Char, typename... T> struct basic_fstring {
private:
basic_string_view<Char> str_;
static constexpr int num_static_named_args =
detail::count_static_named_args<T...>();
using checker = detail::format_string_checker<
Char, static_cast<int>(sizeof...(T)), num_static_named_args,
num_static_named_args != detail::count_named_args<T...>()>;
using arg_pack = detail::arg_pack<T...>;
public:
using t = basic_fstring;
template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {
if (FMT_USE_CONSTEVAL)
detail::parse_format_string<Char>(s, checker(s, arg_pack()));
}
template <typename S,
FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&
std::is_same<typename S::char_type, Char>::value)>
FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {
FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());
FMT_CONSTEXPR int ignore =
(parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore);
}
basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}
operator basic_string_view<Char>() const { return str_; }
auto get() const -> basic_string_view<Char> { return str_; }
};
template <typename Char, typename... T>
using basic_format_string = basic_fstring<Char, T...>;
template <typename... T>
using wformat_string = typename basic_format_string<wchar_t, T...>::t;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
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
template <typename... T>
constexpr auto make_wformat_args(T&... args)
@ -128,13 +90,14 @@ constexpr auto make_wformat_args(T&... args)
return fmt::make_format_args<wformat_context>(args...);
}
#if !FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
constexpr auto operator""_a(const wchar_t* s, size_t)
-> detail::udl_arg<wchar_t> {
return {s};
}
} // namespace literals
#endif
} // namespace literals
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep)
@ -142,9 +105,9 @@ auto join(It begin, Sentinel end, wstring_view sep)
return {begin, end, sep};
}
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
template <typename Range>
auto join(Range&& range, wstring_view sep)
-> join_view<decltype(std::begin(range)), decltype(std::end(range)),
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
wchar_t> {
return join(std::begin(range), std::end(range), sep);
}
@ -155,19 +118,19 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), 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<Tuple, wchar_t> {
template <typename... T>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
return {tuple, sep};
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args)
auto vformat(basic_string_view<Char> format_str,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, fmt, args);
return {buf.data(), buf.size()};
detail::vformat_to(buf, format_str, args);
return to_string(buf);
}
template <typename... T>
@ -188,27 +151,29 @@ template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(fmt),
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str),
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(locale_ref loc, const S& fmt,
basic_format_args<buffered_context<Char>> args)
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& format_str,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
return {buf.data(), buf.size()};
return detail::vformat(loc, detail::to_string_view(format_str), args);
}
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(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& format_str, T&&... args)
-> std::basic_string<Char> {
return vformat(loc, detail::to_string_view(fmt),
return detail::vformat(
loc, detail::to_string_view(format_str),
fmt::make_format_args<buffered_context<Char>>(args...));
}
@ -216,10 +181,10 @@ template <typename OutputIt, typename S,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& fmt,
basic_format_args<buffered_context<Char>> args) -> OutputIt {
auto vformat_to(OutputIt out, const S& format_str,
typename detail::vformat_args<Char>::type args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(fmt), args);
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf, out);
}
@ -233,37 +198,42 @@ 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_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
basic_format_args<buffered_context<Char>> args)
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str,
typename detail::vformat_args<Char>::type args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(fmt), args, loc);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... T,
template <typename OutputIt, typename Locale, 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, locale_ref loc, const S& fmt, T&&... args)
-> typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(fmt),
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args)
inline auto vformat_to_n(OutputIt out, size_t n,
basic_string_view<Char> format_str,
typename detail::vformat_args<Char>::type args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, fmt, args);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
@ -317,33 +287,29 @@ 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(text_style ts, wstring_view fmt, wformat_args args)
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
-> std::wstring {
auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args);
return {buf.data(), buf.size()};
return fmt::to_string(buf);
}
template <typename... T>
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
-> std::wstring {
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
}
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args);
detail::write_buffer(os, buffer);
template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
wformat_string<T...> fmt, const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
void print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
}
template <typename... T>
void println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
const T&... args) {
return print(stdout, ts, fmt, args...);
}
/// Converts `value` to `std::wstring` using the default format for type `T`.

View File

@ -1,13 +1,5 @@
module;
#define FMT_MODULE
#ifdef _MSVC_LANG
# define FMT_CPLUSPLUS _MSVC_LANG
#else
# define FMT_CPLUSPLUS __cplusplus
#endif
// Put all implementation-provided headers into the global module fragment
// to prevent attachment to this module.
#ifndef FMT_IMPORT_STD
@ -23,9 +15,7 @@ module;
# include <cstring>
# include <ctime>
# include <exception>
# if FMT_CPLUSPLUS > 202002L
# include <expected>
# endif
# include <filesystem>
# include <fstream>
# include <functional>
@ -50,8 +40,6 @@ module;
# include <limits.h>
# include <stdint.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
#endif
#include <cerrno>
@ -139,17 +127,9 @@ extern "C++" {
module :private;
#endif
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
extern "C++" {
#endif
#if FMT_HAS_INCLUDE("format.cc")
# include "format.cc"
#endif
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
# include "os.cc"
#endif
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif

View File

@ -8,12 +8,6 @@
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
#if FMT_USE_LOCALE
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED!
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept
@ -21,22 +15,28 @@ template FMT_API auto dragonbox::to_decimal(float x) noexcept
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
// Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*);
template FMT_API void vformat_to(buffer<char>&, string_view,
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
// DEPRECATED!
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail

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(size_t count) {
inline unsigned convert_rwcount(std::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 auto convert_rwcount(size_t count) -> size_t { return count; }
inline std::size_t convert_rwcount(std::size_t count) { return count; }
#endif
} // namespace
@ -160,7 +160,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
}
void report_windows_error(int error_code, const char* message) noexcept {
do_report_error(detail::format_windows_error, error_code, message);
report_error(detail::format_windows_error, error_code, message);
}
#endif // _WIN32
@ -185,7 +185,7 @@ void buffered_file::close() {
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
auto buffered_file::descriptor() const -> int {
int buffered_file::descriptor() const {
#ifdef FMT_HAS_SYSTEM
// fileno is a macro on OpenBSD.
# ifdef fileno
@ -240,7 +240,7 @@ void file::close() {
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
auto file::size() const -> long long {
long long file::size() const {
# ifdef _WIN32
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
// is less than 0x0500 as is the case with some default MinGW builds.
@ -251,7 +251,7 @@ auto file::size() const -> long long {
if (size_lower == INVALID_FILE_SIZE) {
DWORD error = GetLastError();
if (error != NO_ERROR)
FMT_THROW(windows_error(error, "cannot get file size"));
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
}
unsigned long long long_size = size_upper;
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
@ -266,7 +266,7 @@ auto file::size() const -> long long {
# endif
}
auto file::read(void* buffer, size_t count) -> size_t {
std::size_t file::read(void* buffer, std::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 @@ auto file::read(void* buffer, size_t count) -> size_t {
return detail::to_unsigned(result);
}
auto file::write(const void* buffer, size_t count) -> size_t {
std::size_t file::write(const void* buffer, std::size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0)
@ -282,7 +282,7 @@ auto file::write(const void* buffer, size_t count) -> size_t {
return detail::to_unsigned(result);
}
auto file::dup(int fd) -> file {
file file::dup(int fd) {
// Don't retry as dup doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd));
@ -308,7 +308,7 @@ void file::dup2(int fd, std::error_code& ec) noexcept {
if (result == -1) ec = std::error_code(errno, std::generic_category());
}
auto file::fdopen(const char* mode) -> buffered_file {
buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR.
# if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode);
@ -355,7 +355,7 @@ pipe::pipe() {
}
# if !defined(__MSDOS__)
auto getpagesize() -> long {
long getpagesize() {
# ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
@ -374,25 +374,30 @@ auto getpagesize() -> long {
}
# endif
void ostream::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<ostream&>(buf).flush();
namespace detail {
void file_buffer::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<file_buffer&>(buf).flush();
}
ostream::ostream(cstring_view path, const detail::ostream_params& params)
file_buffer::file_buffer(cstring_view path, const ostream_params& params)
: buffer<char>(grow), file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
ostream::ostream(ostream&& other) noexcept
file_buffer::file_buffer(file_buffer&& other) noexcept
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
ostream::~ostream() {
file_buffer::~file_buffer() {
flush();
delete[] data();
}
} // namespace detail
ostream::~ostream() = default;
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

View File

@ -0,0 +1 @@
7.1.2

20
support/bazel/BUILD.bazel Normal file
View File

@ -0,0 +1,20 @@
cc_library(
name = "fmt",
srcs = [
#"src/fmt.cc", # No C++ module support, yet in Bazel (https://github.com/bazelbuild/bazel/pull/19940)
"src/format.cc",
"src/os.cc",
],
hdrs = glob([
"include/fmt/*.h",
]),
copts = select({
"@platforms//os:windows": ["-utf-8"],
"//conditions:default": [],
}),
includes = [
"include",
],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,6 @@
module(
name = "fmt",
compatibility_level = 10,
)
bazel_dep(name = "platforms", version = "0.0.10")

28
support/bazel/README.md Normal file
View File

@ -0,0 +1,28 @@
# Bazel support
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`,
`MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project.
This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
## Using {fmt} as a dependency
### Using Bzlmod
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}.
For instance, to use {fmt} add to your `MODULE.bazel` file:
```
bazel_dep(name = "fmt", version = "10.2.1")
```
### Live at head
For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.:
```
local_path_override(
module_name = "fmt",
path = "../third_party/fmt",
)
```

View File

@ -0,0 +1,2 @@
# WORKSPACE marker file needed by Bazel

View File

@ -1,43 +0,0 @@
#!/usr/bin/env python3
"""Compile source on a range of commits
Usage:
check-commits <start> <source>
"""
import docopt, os, sys, tempfile
from subprocess import check_call, check_output, run
args = docopt.docopt(__doc__)
start = args.get('<start>')
source = args.get('<source>')
cwd = os.getcwd()
with tempfile.TemporaryDirectory() as work_dir:
check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'],
cwd=work_dir)
repo_dir = os.path.join(work_dir, 'fmt')
commits = check_output(
['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit',
'--', 'include', 'src'],
text=True, cwd=repo_dir).rstrip().split('\n')
commits.reverse()
print('Time\tCommit')
for commit in commits:
check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit],
cwd=repo_dir)
returncode = run(
['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include',
'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode
if returncode != 0:
continue
times = []
for i in range(5):
output = check_output([os.path.join(repo_dir, 'a.out')], text=True)
times.append(float(output))
message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit],
cwd=repo_dir, text=True)
print(f'{min(times)}\t{commit} {message[:40]}')
sys.stdout.flush()

View File

@ -1,9 +1,10 @@
#!/usr/bin/env python3
"""Make a release.
"""Manage site and releases.
Usage:
release.py [<branch>]
manage.py release [<branch>]
manage.py site
For the release command $FMT_TOKEN should contain a GitHub personal access token
obtained from https://github.com/settings/tokens.
@ -11,9 +12,9 @@ obtained from https://github.com/settings/tokens.
from __future__ import print_function
import datetime, docopt, errno, fileinput, json, os
import re, shutil, sys
import re, requests, shutil, sys
from contextlib import contextmanager
from subprocess import check_call
import urllib.request
class Git:
@ -80,15 +81,46 @@ def create_build_env():
return env
if __name__ == '__main__':
args = docopt.docopt(__doc__)
fmt_repo_url = 'git@github.com:fmtlib/fmt'
def update_site(env):
env.fmt_repo.update(fmt_repo_url)
doc_repo = Git(os.path.join(env.build_dir, 'fmt.dev'))
doc_repo.update('git@github.com:fmtlib/fmt.dev')
version = '11.0.0'
clean_checkout(env.fmt_repo, version)
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
# Build the docs.
html_dir = os.path.join(env.build_dir, 'html')
if os.path.exists(html_dir):
shutil.rmtree(html_dir)
include_dir = env.fmt_repo.dir
import build
build.build_docs(version, doc_dir=target_doc_dir,
include_dir=include_dir, work_dir=env.build_dir)
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
# Copy docs to the website.
version_doc_dir = os.path.join(doc_repo.dir, version)
try:
shutil.rmtree(version_doc_dir)
except OSError as e:
if e.errno != errno.ENOENT:
raise
shutil.move(html_dir, version_doc_dir)
def release(args):
env = create_build_env()
fmt_repo = env.fmt_repo
branch = args.get('<branch>')
if branch is None:
branch = 'master'
if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
if not fmt_repo.update('-b', branch, fmt_repo_url):
clean_checkout(fmt_repo, branch)
# Update the date in the changelog and extract the version and the first
@ -159,30 +191,28 @@ if __name__ == '__main__':
# Create a release on GitHub.
fmt_repo.push('origin', 'release')
auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
req = urllib.request.Request(
'https://api.github.com/repos/fmtlib/fmt/releases',
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
headers=auth_headers,
data=json.dumps({'tag_name': version,
'target_commitish': 'release',
'body': changes, 'draft': True}).encode('utf-8'),
headers=auth_headers, method='POST')
with urllib.request.urlopen(req) as response:
if response.status != 201:
raise Exception(f'Failed to create a release ' +
'{response.status} {response.reason}')
response_data = json.loads(response.read().decode('utf-8'))
id = response_data['id']
# Upload the package.
'body': changes, 'draft': True}))
if r.status_code != 201:
raise Exception('Failed to create a release ' + str(r))
id = r.json()['id']
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
package = 'fmt-{}.zip'.format(version)
req = urllib.request.Request(
f'{uploads_url}/{id}/assets?name={package}',
r = requests.post(
'{}/{}/assets?name={}'.format(uploads_url, id, package),
headers={'Content-Type': 'application/zip'} | auth_headers,
data=open('build/fmt/' + package, 'rb').read(), method='POST')
with urllib.request.urlopen(req) as response:
if response.status != 201:
raise Exception(f'Failed to upload an asset '
'{response.status} {response.reason}')
data=open('build/fmt/' + package, 'rb'))
if r.status_code != 201:
raise Exception('Failed to upload an asset ' + str(r))
short_version = '.'.join(version.split('.')[:-1])
check_call(['./mkdocs', 'deploy', short_version])
update_site(env)
if __name__ == '__main__':
args = docopt.docopt(__doc__)
if args.get('release'):
release(args)
elif args.get('site'):
update_site(create_build_env())

View File

@ -2,10 +2,6 @@
# A script to invoke mkdocs with the correct environment.
# Additionally supports deploying via mike:
# ./mkdocs deploy [mike-deploy-options]
# For example:
# ./mkdocs deploy <version>
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
# <version> there.
import errno, os, shutil, sys
from subprocess import call
@ -19,36 +15,15 @@ path = env.get('PYTHONPATH')
env['PYTHONPATH'] = \
(path + ':' if path else '') + os.path.join(support_dir, 'python')
redirect_page = \
'''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redirecting</title>
<noscript>
<meta http-equiv="refresh" content="1; url=11.0/" />
</noscript>
<script>
window.location.replace(
"api/" + window.location.search + window.location.hash
);
</script>
</head>
<body>
Redirecting to <a href="api/">api</a>...
</body>
</html>
'''
config_path = os.path.join(support_dir, 'mkdocs.yml')
args = sys.argv[1:]
if len(args) > 0:
command = args[0]
if command == 'deploy' or command == 'set-default':
if command == 'deploy':
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
site_repo = git_url + 'fmtlib/fmt.dev.git'
site_dir = os.path.join(build_dir, 'fmt.dev')
site_dir = os. path.join(build_dir, 'fmt.dev')
try:
shutil.rmtree(site_dir)
except OSError as e:
@ -62,25 +37,8 @@ if len(args) > 0:
config_build_path = os.path.join(build_dir, 'mkdocs.yml')
shutil.copyfile(config_path, config_build_path)
version = args[1]
ret = call(['mike'] + args + ['--config-file', config_build_path,
'--branch', 'master'], cwd=site_dir, env=env)
if ret != 0 or version == 'dev':
sys.exit(ret)
current_doc_path = os.path.join(site_dir, version)
# mike stages files added by deploy for deletion for unclear reason,
# undo it.
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
if False:
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)
if ret != 0:
sys.exit(ret)
ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
sys.exit(ret)
sys.exit(call(['mike'] + args + ['--config-file', config_build_path,
'--branch', 'master'], cwd=site_dir, env=env))
elif not command.startswith('-'):
args += ['-f', config_path]
sys.exit(call(['mkdocs'] + args, env=env))

View File

@ -1,32 +1,24 @@
# A basic mkdocstrings handler for {fmt}.
# Copyright (c) 2012 - present, Victor Zverovich
# https://github.com/fmtlib/fmt/blob/master/LICENSE
import os
import xml.etree.ElementTree as ElementTree
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
from typing import Any, List, Mapping, Optional
from subprocess import CalledProcessError, PIPE, Popen, STDOUT
import xml.etree.ElementTree as et
from mkdocstrings.handlers.base import BaseHandler
class Definition:
"""A definition extracted by Doxygen."""
'''A definition extracted by Doxygen.'''
def __init__(self, name: str, kind: Optional[str] = None,
node: Optional[ElementTree.Element] = None,
node: Optional[et.Element] = None,
is_member: bool = False):
self.name = name
self.kind = kind if kind is not None else node.get('kind')
self.desc = None
self.id = name if not is_member else None
self.members = None
self.params = None
self.template_params = None
self.trailing_return_type = None
self.type = None
self.members = None
# A map from Doxygen to HTML tags.
tag_map = {
@ -34,8 +26,8 @@ tag_map = {
'emphasis': 'em',
'computeroutput': 'code',
'para': 'p',
'itemizedlist': 'ul',
'listitem': 'li'
'programlisting': 'pre',
'verbatim': 'pre'
}
# A map from Doxygen tags to text.
@ -45,81 +37,58 @@ tag_text_map = {
'sp': ' '
}
def escape_html(s: str) -> str:
return s.replace("<", "&lt;")
# Converts a node from doxygen to HTML format.
def convert_node(node: ElementTree.Element, tag: str, attrs: dict = {}):
out = '<' + tag
for key, value in attrs.items():
out += ' ' + key + '="' + value + '"'
out += '>'
if node.text:
out += escape_html(node.text)
out += doxyxml2html(list(node))
out += '</' + tag + '>'
if node.tail:
out += node.tail
return out
def doxyxml2html(nodes: List[ElementTree.Element]):
def doxyxml2html(nodes: List[et.Element]):
out = ''
for n in nodes:
tag = tag_map.get(n.tag)
if tag:
out += convert_node(n, tag)
continue
if n.tag == 'programlisting' or n.tag == 'verbatim':
out += '<pre>'
out += convert_node(n, 'code', {'class': 'language-cpp'})
out += '</pre>'
continue
if n.tag == 'ulink':
out += convert_node(n, 'a', {'href': n.attrib['url']})
continue
if not tag:
out += tag_text_map[n.tag]
out += '<' + tag + '>' if tag else ''
out += '<code class="language-cpp">' if tag == 'pre' else ''
if n.text:
out += escape_html(n.text)
out += doxyxml2html(n)
out += '</code>' if tag == 'pre' else ''
out += '</' + tag + '>' if tag else ''
if n.tail:
out += n.tail
return out
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
template_param_list = node.find('templateparamlist')
if template_param_list is None:
def convert_template_params(node: et.Element) -> Optional[List[Definition]]:
templateparamlist = node.find('templateparamlist')
if templateparamlist is None:
return None
params = []
for param_node in template_param_list.findall('param'):
for param_node in templateparamlist.findall('param'):
name = param_node.find('declname')
param = Definition(name.text if name is not None else '', 'param')
param.type = param_node.find('type').text
params.append(param)
return params
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
def get_description(node: et.Element) -> List[et.Element]:
return node.findall('briefdescription/para') + \
node.findall('detaileddescription/para')
def normalize_type(type: str) -> str:
type = type.replace('< ', '<').replace(' >', '>')
return type.replace(' &', '&').replace(' *', '*')
def normalize_type(type_: str) -> str:
type_ = type_.replace('< ', '<').replace(' >', '>')
return type_.replace(' &', '&').replace(' *', '*')
def convert_type(type_: ElementTree.Element) -> Optional[str]:
if type_ is None:
def convert_type(type: et.Element) -> str:
if type is None:
return None
result = type_.text if type_.text else ''
for ref in type_:
result = type.text if type.text else ''
for ref in type:
result += ref.text
if ref.tail:
result += ref.tail
result += type_.tail.strip()
result += type.tail.strip()
return normalize_type(result)
def convert_params(func: ElementTree.Element) -> List[Definition]:
def convert_params(func: et.Element) -> Definition:
params = []
for p in func.findall('param'):
d = Definition(p.find('declname').text, 'param')
@ -127,20 +96,17 @@ def convert_params(func: ElementTree.Element) -> List[Definition]:
params.append(d)
return params
def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
def convert_return_type(d: Definition, node: et.Element) -> None:
d.trailing_return_type = None
if d.type == 'auto' or d.type == 'constexpr auto':
parts = node.find('argsstring').text.split(' -> ')
if len(parts) > 1:
d.trailing_return_type = normalize_type(parts[1])
def render_param(param: Definition) -> str:
return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '')
def render_decl(d: Definition) -> str:
def render_decl(d: Definition) -> None:
text = ''
if d.id is not None:
text += f'<a id="{d.id}">\n'
@ -181,7 +147,6 @@ def render_decl(d: Definition) -> str:
text += f'</a>\n'
return text
class CxxHandler(BaseHandler):
def __init__(self, **kwargs: Any) -> None:
super().__init__(handler='cxx', **kwargs)
@ -214,7 +179,7 @@ class CxxHandler(BaseHandler):
PREDEFINED = _WIN32=1 \
__linux__=1 \
FMT_ENABLE_IF(...)= \
FMT_USE_USER_LITERALS=1 \
FMT_USE_USER_DEFINED_LITERALS=1 \
FMT_USE_ALIAS_TEMPLATES=1 \
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
FMT_API= \
@ -232,7 +197,7 @@ class CxxHandler(BaseHandler):
for h in headers:
filename = h.replace(".h", "_8h.xml")
with open(os.path.join(self._doxyxml_dir, filename)) as f:
doxyxml = ElementTree.parse(f)
doxyxml = et.parse(f)
if self._file_doxyxml is None:
self._file_doxyxml = doxyxml
continue
@ -241,18 +206,17 @@ class CxxHandler(BaseHandler):
root.append(node)
def collect_compound(self, identifier: str,
cls: List[ElementTree.Element]) -> Definition:
"""Collect a compound definition such as a struct."""
cls: List[et.Element]) -> Definition:
'''Collect a compound definition such as a struct.'''
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
with open(path) as f:
xml = ElementTree.parse(f)
xml = et.parse(f)
node = xml.find('compounddef')
d = Definition(identifier, node=node)
d.template_params = convert_template_params(node)
d.desc = get_description(node)
d.members = []
for m in \
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
for m in node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
node.findall('sectiondef[@kind="public-func"]/memberdef'):
name = m.find('name').text
# Doxygen incorrectly classifies members of private unnamed unions as
@ -264,8 +228,8 @@ class CxxHandler(BaseHandler):
continue
kind = m.get('kind')
member = Definition(name if name else '', kind=kind, is_member=True)
type_text = m.find('type').text
member.type = type_text if type_text else ''
type = m.find('type').text
member.type = type if type else ''
if kind == 'function':
member.params = convert_params(m)
convert_return_type(member, m)
@ -274,7 +238,7 @@ class CxxHandler(BaseHandler):
d.members.append(member)
return d
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
def collect(self, identifier: str, config: Mapping[str, Any]) -> Definition:
qual_name = 'fmt::' + identifier
param_str = None
@ -290,7 +254,7 @@ class CxxHandler(BaseHandler):
if doxyxml is None:
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
with open(os.path.join(self._doxyxml_dir, path)) as f:
doxyxml = ElementTree.parse(f)
doxyxml = et.parse(f)
self._ns2doxyxml[namespace] = doxyxml
nodes = doxyxml.findall(
@ -341,14 +305,13 @@ class CxxHandler(BaseHandler):
text += '</div>\n'
return text
def get_handler(theme: str, custom_templates: Optional[str] = None,
**_config: Any) -> CxxHandler:
"""Return an instance of `CxxHandler`.
**config: Any) -> CxxHandler:
'''Return an instance of `CxxHandler`.
Arguments:
theme: The theme to use when rendering contents.
custom_templates: Directory containing custom templates.
**_config: Configuration passed to the handler.
"""
**config: Configuration passed to the handler.
'''
return CxxHandler(theme=theme, custom_templates=custom_templates)

View File

@ -8,6 +8,22 @@ target_include_directories(test-main PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_link_libraries(test-main gtest fmt)
function(add_fmt_executable name)
add_executable(${name} ${ARGN})
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
# Bogus -Wstringop-overflow warning
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100395
# [10 Regression] spurious -Wstringop-overflow writing to a trailing array plus offset
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
target_compile_options(${name} PRIVATE -Wno-stringop-overflow)
# The linker flag is needed for LTO.
target_link_libraries(${name} -Wno-stringop-overflow)
endif ()
endfunction()
# Adds a test.
# Usage: add_fmt_test(name srcs...)
function(add_fmt_test name)
@ -26,7 +42,7 @@ function(add_fmt_test name)
else ()
set(libs test-main fmt)
endif ()
add_executable(${name} ${sources})
add_fmt_executable(${name} ${sources})
target_link_libraries(${name} ${libs})
if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE)
@ -62,9 +78,13 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif ()
add_fmt_test(ostream-test)
add_fmt_test(compile-test)
add_fmt_test(compile-fp-test HEADER_ONLY)
if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
endif()
add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(no-builtin-types-test HEADER_ONLY)
add_fmt_test(scan-test HEADER_ONLY)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
@ -90,9 +110,6 @@ add_fmt_test(enforce-checks-test)
target_compile_definitions(enforce-checks-test PRIVATE
-DFMT_ENFORCE_COMPILE_STRING)
add_executable(perf-sanity perf-sanity.cc)
target_link_libraries(perf-sanity fmt::fmt)
if (FMT_MODULE)
# The tests need {fmt} to be compiled as traditional library
# because of visibility of implementation details.
@ -125,7 +142,7 @@ if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
endif()
if (NOT MSVC_STATIC_RUNTIME)
add_executable(posix-mock-test
add_fmt_executable(posix-mock-test
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
target_include_directories(
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
@ -216,7 +233,7 @@ if (FMT_PEDANTIC AND NOT WIN32 AND NOT (
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
endif ()
# This test is disabled on Windows because it is POSIX-specific.
# This test are disabled on Windows because it is only *NIX issue.
if (FMT_PEDANTIC AND NOT WIN32)
add_test(static-export-test ${CMAKE_CTEST_COMMAND}
-C ${CMAKE_BUILD_TYPE}

View File

@ -64,7 +64,7 @@ TEST(args_test, custom_format) {
}
struct to_stringable {
friend auto to_string_view(to_stringable) -> fmt::string_view { return {}; }
friend fmt::string_view to_string_view(to_stringable) { return {}; }
};
FMT_BEGIN_NAMESPACE
@ -186,17 +186,3 @@ TEST(args_test, move_constructor) {
store.reset();
EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo");
}
TEST(args_test, size) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
EXPECT_EQ(store.size(), 0);
store.push_back(42);
EXPECT_EQ(store.size(), 1);
store.push_back("Molybdenum");
EXPECT_EQ(store.size(), 2);
store.clear();
EXPECT_EQ(store.size(), 0);
}

View File

@ -5,16 +5,14 @@
//
// 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 <limits.h> // INT_MAX
#include <string.h> // strlen
#include <climits> // INT_MAX
#include <cstring> // std::strlen
#include <functional> // std::equal_to
#include <iterator> // std::back_insert_iterator, std::distance
#include <limits> // std::numeric_limits
@ -23,36 +21,39 @@
#include "gmock/gmock.h"
#ifdef FMT_FORMAT_H_
# error base-test includes format.h
#endif
using fmt::string_view;
using fmt::detail::buffer;
using testing::_;
using testing::Invoke;
using testing::Return;
auto copy(fmt::string_view s, fmt::appender out) -> fmt::appender {
#ifdef FMT_FORMAT_H_
# error core-test includes format.h
#endif
fmt::appender copy(fmt::string_view s, fmt::appender out) {
for (char c : s) *out++ = c;
return out;
}
TEST(string_view_test, value_type) {
static_assert(std::is_same<fmt::string_view::value_type, char>::value, "");
static_assert(std::is_same<string_view::value_type, char>::value, "");
}
TEST(string_view_test, ctor) {
EXPECT_STREQ(fmt::string_view("abc").data(), "abc");
EXPECT_EQ(fmt::string_view("abc").size(), 3u);
EXPECT_STREQ("abc", fmt::string_view("abc").data());
EXPECT_EQ(3u, fmt::string_view("abc").size());
EXPECT_STREQ(fmt::string_view(std::string("defg")).data(), "defg");
EXPECT_EQ(fmt::string_view(std::string("defg")).size(), 4u);
EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data());
EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size());
}
TEST(string_view_test, length) {
// Test that string_view::size() returns string length, not buffer size.
char str[100] = "some string";
EXPECT_EQ(fmt::string_view(str).size(), strlen(str));
EXPECT_LT(strlen(str), sizeof(str));
EXPECT_EQ(std::strlen(str), string_view(str).size());
EXPECT_LT(std::strlen(str), sizeof(str));
}
// Check string_view's comparison operator.
@ -61,16 +62,13 @@ 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) {
fmt::string_view lhs(inputs[i]), rhs(inputs[j]);
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0),
Op<fmt::string_view>()(lhs, rhs));
string_view lhs(inputs[i]), rhs(inputs[j]);
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<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);
@ -94,49 +92,64 @@ 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");
TEST(base_test, is_output_iterator) {
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>,
char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::string::iterator, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator,
char>::value));
}
#endif // FMT_USE_CONSTEVAL
TEST(base_test, is_back_insert_iterator) {
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<
std::back_insert_iterator<std::string>>::value);
EXPECT_FALSE(fmt::detail::is_back_insert_iterator<
std::front_insert_iterator<std::string>>::value);
}
TEST(base_test, buffer_appender) {
#ifdef __cpp_lib_ranges
static_assert(std::output_iterator<fmt::appender, char>);
#endif
}
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470
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);
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<fmt::detail::buffer<char>>::value);
EXPECT_FALSE(std::is_move_assignable<fmt::detail::buffer<char>>::value);
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 : fmt::detail::buffer<T> {
template <typename T> struct mock_buffer final : buffer<T> {
MOCK_METHOD(size_t, do_grow, (size_t));
static void grow(fmt::detail::buffer<T>& buf, size_t capacity) {
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)
: fmt::detail::buffer<T>(grow) {
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;
@ -147,24 +160,24 @@ template <typename T> struct mock_buffer final : fmt::detail::buffer<T> {
TEST(buffer_test, ctor) {
{
mock_buffer<int> buffer;
EXPECT_EQ(buffer.data(), nullptr);
EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(buffer.capacity(), 0u);
EXPECT_EQ(nullptr, buffer.data());
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
}
{
int data;
mock_buffer<int> buffer(&data);
EXPECT_EQ(&buffer[0], &data);
EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(buffer.capacity(), 0u);
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 data;
int dummy;
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);
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());
}
}
@ -172,26 +185,26 @@ TEST(buffer_test, access) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
buffer[0] = 11;
EXPECT_EQ(buffer[0], 11);
EXPECT_EQ(11, buffer[0]);
buffer[3] = 42;
EXPECT_EQ(*(&buffer[0] + 3), 42);
EXPECT_EQ(42, *(&buffer[0] + 3));
const fmt::detail::buffer<char>& const_buffer = buffer;
EXPECT_EQ(const_buffer[3], 42);
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(buffer[10], 42);
EXPECT_EQ(42, buffer[10]);
buffer.try_resize(20);
EXPECT_EQ(buffer.size(), 20u);
EXPECT_EQ(buffer.capacity(), 123u);
EXPECT_EQ(buffer[10], 42);
EXPECT_EQ(20u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
EXPECT_EQ(42, buffer[10]);
buffer.try_resize(5);
EXPECT_EQ(buffer.size(), 5u);
EXPECT_EQ(buffer.capacity(), 123u);
EXPECT_EQ(buffer[10], 42);
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);
@ -213,8 +226,8 @@ TEST(buffer_test, clear) {
EXPECT_CALL(buffer, do_grow(20));
buffer.try_resize(20);
buffer.try_resize(0);
EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(buffer.capacity(), 20u);
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(20u, buffer.capacity());
}
TEST(buffer_test, append) {
@ -222,14 +235,14 @@ TEST(buffer_test, append) {
mock_buffer<char> buffer(data, 10);
auto test = "test";
buffer.append(test, test + 5);
EXPECT_STREQ(&buffer[0], test);
EXPECT_EQ(buffer.size(), 5u);
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(buffer[10], 't');
EXPECT_EQ(buffer[11], 'e');
EXPECT_EQ(buffer.size(), 12u);
EXPECT_EQ('t', buffer[10]);
EXPECT_EQ('e', buffer[11]);
EXPECT_EQ(12u, buffer.size());
}
TEST(buffer_test, append_partial) {
@ -255,51 +268,27 @@ TEST(buffer_test, append_allocates_enough_storage) {
buffer.append(test, test + 9);
}
TEST(base_test, is_locking) {
EXPECT_FALSE(fmt::detail::is_locking<const char(&)[3]>());
}
struct custom_context {
using char_type = char;
using parse_context_type = fmt::format_parse_context;
TEST(base_test, is_output_iterator) {
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>,
char>::value));
EXPECT_TRUE(
(fmt::detail::is_output_iterator<std::string::iterator, char>::value));
EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator,
char>::value));
}
bool called = false;
TEST(base_test, is_back_insert_iterator) {
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<
std::back_insert_iterator<std::string>>::value);
EXPECT_FALSE(fmt::detail::is_back_insert_iterator<
std::front_insert_iterator<std::string>>::value);
}
template <typename T> struct formatter_type {
FMT_CONSTEXPR auto parse(fmt::format_parse_context& ctx)
-> decltype(ctx.begin()) {
return ctx.begin();
}
struct minimal_container {
using value_type = char;
void push_back(char) {}
const char* format(const T&, custom_context& ctx) const {
ctx.called = true;
return nullptr;
}
};
void advance_to(const char*) {}
};
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) {
mock_buffer<char> buffer;
void* buffer_ptr = &buffer;
auto&& appender_result = fmt::detail::get_buffer<char>(fmt::appender(buffer));
EXPECT_EQ(&appender_result, buffer_ptr);
auto&& back_inserter_result =
fmt::detail::get_buffer<char>(std::back_inserter(buffer));
EXPECT_EQ(&back_inserter_result, buffer_ptr);
}
struct test_struct {};
FMT_BEGIN_NAMESPACE
@ -314,6 +303,21 @@ 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));
}
TEST(arg_test, make_value_with_custom_context) {
auto t = test_struct();
auto arg = fmt::detail::value<custom_context>(
fmt::detail::arg_mapper<custom_context>().map(t));
auto ctx = custom_context();
auto parse_ctx = fmt::format_parse_context("");
arg.custom.format(&t, parse_ctx, ctx);
EXPECT_TRUE(ctx.called);
}
// Use a unique result type to make sure that there are no undesirable
// conversions.
struct test_result {};
@ -360,99 +364,29 @@ VISIT_TYPE(long, long long);
VISIT_TYPE(unsigned long, unsigned long long);
#endif
#if FMT_BUILTIN_TYPES
# define CHECK_ARG(expected, value) \
#define CHECK_ARG(Char, expected, value) \
{ \
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
EXPECT_CALL(visitor, visit(expected)); \
using iterator = fmt::basic_appender<Char>; \
auto var = value; \
fmt::basic_format_arg<fmt::format_context>(var).visit(visitor); \
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>(var) \
.visit(visitor); \
}
#else
# define CHECK_ARG(expected, value)
#endif
#define CHECK_ARG_SIMPLE(value) \
{ \
using value_type = decltype(value); \
typename visit_type<value_type>::type expected = value; \
CHECK_ARG(expected, value) \
CHECK_ARG(char, expected, value) \
}
TEST(arg_test, format_args) {
auto args = fmt::format_args();
EXPECT_FALSE(args.get(1));
}
TEST(arg_test, char_arg) { CHECK_ARG('a', 'a'); }
TEST(arg_test, string_arg) {
char str_data[] = "test";
char* str = str_data;
const char* cstr = str;
CHECK_ARG(cstr, str);
auto sv = fmt::string_view(str);
CHECK_ARG(sv, std::string(str));
}
TEST(arg_test, pointer_arg) {
void* p = nullptr;
const void* cp = nullptr;
CHECK_ARG(cp, p);
CHECK_ARG_SIMPLE(cp);
}
TEST(arg_test, volatile_pointer_arg) {
const void* p = nullptr;
volatile int* vip = nullptr;
const volatile int* cvip = nullptr;
CHECK_ARG(p, static_cast<volatile void*>(vip));
CHECK_ARG(p, static_cast<const volatile void*>(cvip));
}
struct check_custom {
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
-> test_result {
struct test_buffer final : fmt::detail::buffer<char> {
char data[10];
test_buffer()
: fmt::detail::buffer<char>([](buffer<char>&, size_t) {}, data, 0,
10) {}
} buffer;
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(std::string(buffer.data, buffer.size()), "test");
return test_result();
}
};
TEST(arg_test, custom_arg) {
auto test = test_struct();
using visitor =
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
auto&& v = testing::StrictMock<visitor>();
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
fmt::basic_format_arg<fmt::format_context>(test).visit(v);
}
TEST(arg_test, visit_invalid_arg) {
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
EXPECT_CALL(visitor, visit(_));
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>
@ -472,34 +406,88 @@ TYPED_TEST(numeric_arg_test, make_and_visit) {
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
}
TEST(arg_test, char_arg) { CHECK_ARG(char, 'a', 'a'); }
TEST(arg_test, string_arg) {
char str_data[] = "test";
char* str = str_data;
const char* cstr = str;
CHECK_ARG(char, cstr, str);
auto sv = fmt::string_view(str);
CHECK_ARG(char, sv, std::string(str));
}
TEST(arg_test, pointer_arg) {
void* p = nullptr;
const void* cp = nullptr;
CHECK_ARG(char, cp, p);
CHECK_ARG_SIMPLE(cp);
}
struct check_custom {
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
-> test_result {
struct test_buffer final : fmt::detail::buffer<char> {
char data[10];
test_buffer()
: fmt::detail::buffer<char>([](buffer<char>&, size_t) {}, data, 0,
10) {}
} buffer;
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()));
return test_result();
}
};
TEST(arg_test, custom_arg) {
auto test = test_struct();
using visitor =
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
auto&& v = testing::StrictMock<visitor>();
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
fmt::detail::make_arg<fmt::format_context>(test).visit(v);
}
TEST(arg_test, visit_invalid_arg) {
auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
EXPECT_CALL(visitor, visit(_));
fmt::basic_format_arg<fmt::format_context>().visit(visitor);
}
#if FMT_USE_CONSTEXPR
enum class arg_id_result { none, index, name };
enum class arg_id_result { none, empty, index, name };
struct test_arg_id_handler {
arg_id_result res = arg_id_result::none;
int index = 0;
fmt::string_view name;
string_view name;
constexpr void on_auto() { res = arg_id_result::empty; }
constexpr void on_index(int i) {
res = arg_id_result::index;
index = i;
}
constexpr void on_name(fmt::string_view n) {
constexpr void on_name(string_view n) {
res = arg_id_result::name;
name = n;
}
};
template <size_t N>
constexpr auto parse_arg_id(const char (&s)[N]) -> test_arg_id_handler {
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
auto h = test_arg_id_handler();
fmt::detail::parse_arg_id(s, s + N, h);
return h;
}
TEST(base_test, constexpr_parse_arg_id) {
static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
static_assert(parse_arg_id("42:").index == 42, "");
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
@ -516,19 +504,19 @@ template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
}
TEST(base_test, constexpr_parse_format_specs) {
static_assert(parse_test_specs("<").align() == fmt::align::left, "");
static_assert(parse_test_specs("*^").fill_unit<char>() == '*', "");
static_assert(parse_test_specs("+").sign() == fmt::sign::plus, "");
static_assert(parse_test_specs("-").sign() == fmt::sign::none, "");
static_assert(parse_test_specs(" ").sign() == fmt::sign::space, "");
static_assert(parse_test_specs("#").alt(), "");
static_assert(parse_test_specs("0").align() == fmt::align::numeric, "");
static_assert(parse_test_specs("L").localized(), "");
static_assert(parse_test_specs("<").align == fmt::align::left, "");
static_assert(parse_test_specs("*^").fill.get<char>() == '*', "");
static_assert(parse_test_specs("+").sign == fmt::sign::plus, "");
static_assert(parse_test_specs("-").sign == fmt::sign::minus, "");
static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
static_assert(parse_test_specs("#").alt, "");
static_assert(parse_test_specs("0").align == fmt::align::numeric, "");
static_assert(parse_test_specs("L").localized, "");
static_assert(parse_test_specs("42").width == 42, "");
static_assert(parse_test_specs("{42}").width_ref.index == 42, "");
static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
static_assert(parse_test_specs(".42").precision == 42, "");
static_assert(parse_test_specs(".{42}").precision_ref.index == 42, "");
static_assert(parse_test_specs("f").type() == fmt::presentation_type::fixed,
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(parse_test_specs("f").type == fmt::presentation_type::fixed,
"");
}
@ -551,9 +539,9 @@ struct test_format_string_handler {
bool error = false;
};
template <size_t N> constexpr auto parse_string(const char (&s)[N]) -> bool {
template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
auto h = test_format_string_handler();
fmt::detail::parse_format_string(fmt::string_view(s, N - 1), h);
fmt::detail::parse_format_string<true>(fmt::string_view(s, N - 1), h);
return !h.error;
}
@ -565,7 +553,6 @@ TEST(base_test, constexpr_parse_format_string) {
static_assert(parse_string("{foo}"), "");
static_assert(parse_string("{:}"), "");
}
#endif // FMT_USE_CONSTEXPR
struct enabled_formatter {};
@ -597,6 +584,15 @@ template <> struct formatter<enabled_ptr_formatter*> {
};
FMT_END_NAMESPACE
TEST(base_test, has_formatter) {
using fmt::has_formatter;
using context = fmt::format_context;
static_assert(has_formatter<enabled_formatter, context>::value, "");
static_assert(!has_formatter<disabled_formatter, context>::value, "");
static_assert(!has_formatter<disabled_formatter_convertible, context>::value,
"");
}
struct const_formattable {};
struct nonconst_formattable {};
@ -648,48 +644,51 @@ FMT_END_NAMESPACE
enum class unformattable_scoped_enum {};
TEST(base_test, is_formattable) {
EXPECT_FALSE(fmt::is_formattable<void>::value);
EXPECT_FALSE(fmt::is_formattable<wchar_t>::value);
static_assert(!fmt::is_formattable<wchar_t>::value, "");
#ifdef __cpp_char8_t
EXPECT_FALSE(fmt::is_formattable<char8_t>::value);
static_assert(!fmt::is_formattable<char8_t>::value, "");
#endif
EXPECT_FALSE(fmt::is_formattable<char16_t>::value);
EXPECT_FALSE(fmt::is_formattable<char32_t>::value);
EXPECT_FALSE(fmt::is_formattable<signed char*>::value);
EXPECT_FALSE(fmt::is_formattable<unsigned char*>::value);
EXPECT_FALSE(fmt::is_formattable<const signed char*>::value);
EXPECT_FALSE(fmt::is_formattable<const unsigned char*>::value);
EXPECT_FALSE(fmt::is_formattable<const wchar_t*>::value);
EXPECT_FALSE(fmt::is_formattable<const wchar_t[3]>::value);
EXPECT_FALSE(fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value);
EXPECT_FALSE(fmt::is_formattable<enabled_ptr_formatter*>::value);
EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
EXPECT_FALSE(fmt::is_formattable<disabled_formatter_convertible>::value);
static_assert(!fmt::is_formattable<char16_t>::value, "");
static_assert(!fmt::is_formattable<char32_t>::value, "");
static_assert(!fmt::is_formattable<signed char*>::value, "");
static_assert(!fmt::is_formattable<unsigned char*>::value, "");
static_assert(!fmt::is_formattable<const signed char*>::value, "");
static_assert(!fmt::is_formattable<const unsigned char*>::value, "");
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, "");
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value,
"");
static_assert(fmt::is_formattable<enabled_formatter>::value, "");
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, "");
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
"");
EXPECT_TRUE(fmt::is_formattable<enabled_formatter>::value);
EXPECT_TRUE(fmt::is_formattable<const_formattable&>::value);
EXPECT_TRUE(fmt::is_formattable<const const_formattable&>::value);
static_assert(fmt::is_formattable<const_formattable&>::value, "");
static_assert(fmt::is_formattable<const const_formattable&>::value, "");
EXPECT_TRUE(fmt::is_formattable<nonconst_formattable&>::value);
EXPECT_FALSE(fmt::is_formattable<const nonconst_formattable&>::value);
static_assert(fmt::is_formattable<nonconst_formattable&>::value, "");
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
static_assert(!fmt::is_formattable<const nonconst_formattable&>::value, "");
#endif
EXPECT_FALSE(fmt::is_formattable<convertible_to_pointer>::value);
static_assert(!fmt::is_formattable<convertible_to_pointer>::value, "");
const auto f = convertible_to_pointer_formattable();
auto str = std::string();
fmt::format_to(std::back_inserter(str), "{}", f);
EXPECT_EQ(str, "test");
EXPECT_FALSE(fmt::is_formattable<void (*)()>::value);
static_assert(!fmt::is_formattable<void (*)()>::value, "");
struct s;
EXPECT_FALSE(fmt::is_formattable<int(s::*)>::value);
EXPECT_FALSE(fmt::is_formattable<int (s::*)()>::value);
EXPECT_FALSE(fmt::is_formattable<unformattable_scoped_enum>::value);
EXPECT_FALSE(fmt::is_formattable<unformattable_scoped_enum>::value);
static_assert(!fmt::is_formattable<int(s::*)>::value, "");
static_assert(!fmt::is_formattable<int (s::*)()>::value, "");
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
}
#ifdef __cpp_concepts
TEST(base_test, formattable_concept) {
#if FMT_USE_CONCEPTS
TEST(base_test, formattable) {
static_assert(fmt::formattable<char>);
static_assert(fmt::formattable<char&>);
static_assert(fmt::formattable<char&&>);
@ -710,48 +709,56 @@ TEST(base_test, format_to) {
TEST(base_test, format_to_array) {
char buffer[4];
auto result = fmt::format_to(buffer, "{}", 12345);
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_TRUE(result.truncated);
EXPECT_EQ(result.out, buffer + 4);
EXPECT_EQ(fmt::string_view(buffer, 4), "1234");
EXPECT_EQ(buffer + 4, result.out);
EXPECT_EQ("1234", fmt::string_view(buffer, 4));
char* out = nullptr;
EXPECT_THROW(out = result, std::runtime_error);
(void)out;
result = fmt::format_to(buffer, "{:s}", "foobar");
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_TRUE(result.truncated);
EXPECT_EQ(result.out, buffer + 4);
EXPECT_EQ(fmt::string_view(buffer, 4), "foob");
EXPECT_EQ(buffer + 4, result.out);
EXPECT_EQ("foob", fmt::string_view(buffer, 4));
buffer[0] = 'x';
buffer[1] = 'x';
buffer[2] = 'x';
buffer[3] = 'x';
result = fmt::format_to(buffer, "{}", 'A');
EXPECT_EQ(std::distance(&buffer[0], result.out), 1);
EXPECT_EQ(1, std::distance(&buffer[0], result.out));
EXPECT_FALSE(result.truncated);
EXPECT_EQ(result.out, buffer + 1);
EXPECT_EQ(fmt::string_view(buffer, 4), "Axxx");
EXPECT_EQ(buffer + 1, result.out);
EXPECT_EQ("Axxx", fmt::string_view(buffer, 4));
result = fmt::format_to(buffer, "{}{} ", 'B', 'C');
EXPECT_EQ(std::distance(&buffer[0], result.out), 3);
EXPECT_EQ(3, std::distance(&buffer[0], result.out));
EXPECT_FALSE(result.truncated);
EXPECT_EQ(result.out, buffer + 3);
EXPECT_EQ(fmt::string_view(buffer, 4), "BC x");
EXPECT_EQ(buffer + 3, result.out);
EXPECT_EQ("BC x", fmt::string_view(buffer, 4));
result = fmt::format_to(buffer, "{}", "ABCDE");
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_TRUE(result.truncated);
EXPECT_EQ(fmt::string_view(buffer, 4), "ABCD");
EXPECT_EQ("ABCD", fmt::string_view(buffer, 4));
result = fmt::format_to(buffer, "{}", std::string(1000, '*').c_str());
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
result = fmt::format_to(buffer, "{}", std::string(1000, '*'));
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_TRUE(result.truncated);
EXPECT_EQ(fmt::string_view(buffer, 4), "****");
EXPECT_EQ("****", fmt::string_view(buffer, 4));
}
#ifdef __cpp_lib_byte
TEST(base_test, format_byte) {
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
EXPECT_EQ(s, "42");
}
#endif
// Test that check is not found by ADL.
template <typename T> void check(T);
TEST(base_test, adl_check) {
@ -769,6 +776,19 @@ TEST(base_test, no_implicit_conversion_to_string_view) {
fmt::is_formattable<implicitly_convertible_to_string_view>::value);
}
#ifdef FMT_USE_STRING_VIEW
struct implicitly_convertible_to_std_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(base_test, no_implicit_conversion_to_std_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
}
#endif
// std::is_constructible is broken in MSVC until version 2015.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
struct explicitly_convertible_to_string_view {
explicit operator fmt::string_view() const { return "foo"; }
};
@ -780,16 +800,7 @@ TEST(base_test, format_explicitly_convertible_to_string_view) {
!fmt::is_formattable<explicitly_convertible_to_string_view>::value, "");
}
#if FMT_CPLUSPLUS >= 201703L
struct implicitly_convertible_to_std_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(base_test, no_implicit_conversion_to_std_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
}
# ifdef FMT_USE_STRING_VIEW
struct explicitly_convertible_to_std_string_view {
explicit operator std::string_view() const { return "foo"; }
};
@ -801,12 +812,14 @@ TEST(base_test, format_explicitly_convertible_to_std_string_view) {
!fmt::is_formattable<explicitly_convertible_to_std_string_view>::value,
"");
}
#endif // FMT_CPLUSPLUS >= 201703L
# endif
#endif
TEST(base_test, has_formatter) {
EXPECT_TRUE((fmt::detail::has_formatter<const const_formattable, char>()));
EXPECT_FALSE(
(fmt::detail::has_formatter<const nonconst_formattable, char>()));
TEST(base_test, has_const_formatter) {
EXPECT_TRUE((fmt::detail::has_const_formatter<const_formattable,
fmt::format_context>()));
EXPECT_FALSE((fmt::detail::has_const_formatter<nonconst_formattable,
fmt::format_context>()));
}
TEST(base_test, format_nonconst) {
@ -816,7 +829,7 @@ TEST(base_test, format_nonconst) {
}
TEST(base_test, throw_in_buffer_dtor) {
constexpr int buffer_size = 256;
enum { buffer_size = 256 };
struct throwing_iterator {
int& count;
@ -838,7 +851,7 @@ TEST(base_test, throw_in_buffer_dtor) {
}
}
struct convertible_to_any_type_with_member_x {
struct its_a_trap {
template <typename T> operator T() const {
auto v = T();
v.x = 42;
@ -847,12 +860,12 @@ struct convertible_to_any_type_with_member_x {
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<convertible_to_any_type_with_member_x> {
template <> struct formatter<its_a_trap> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(convertible_to_any_type_with_member_x, format_context& ctx) const
auto format(its_a_trap, format_context& ctx) const
-> decltype(ctx.out()) const {
auto out = ctx.out();
*out++ = 'x';
@ -861,51 +874,8 @@ template <> struct formatter<convertible_to_any_type_with_member_x> {
};
FMT_END_NAMESPACE
TEST(base_test, promiscuous_conversions) {
TEST(base_test, trappy_conversion) {
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}",
convertible_to_any_type_with_member_x());
fmt::format_to(std::back_inserter(s), "{}", its_a_trap());
EXPECT_EQ(s, "x");
}
struct custom_container {
char data;
using value_type = char;
auto size() const -> size_t { return 0; }
void resize(size_t) {}
void push_back(char) {}
auto operator[](size_t) -> char& { return data; }
};
FMT_BEGIN_NAMESPACE
template <> struct is_contiguous<custom_container> : std::true_type {};
FMT_END_NAMESPACE
TEST(base_test, format_to_custom_container) {
auto c = custom_container();
fmt::format_to(std::back_inserter(c), "");
}
TEST(base_test, no_repeated_format_string_conversions) {
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,9 +15,11 @@
#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
@ -55,8 +57,8 @@ auto make_second(int s) -> std::tm {
return time;
}
auto system_strftime(const std::string& format, const std::tm* timeptr,
std::locale* locptr = nullptr) -> std::string {
std::string system_strftime(const std::string& format, const std::tm* timeptr,
std::locale* locptr = nullptr) {
auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<char>>(loc);
std::ostringstream os;
@ -73,8 +75,8 @@ auto system_strftime(const std::string& format, const std::tm* timeptr,
#endif
}
FMT_CONSTEXPR auto make_tm(int year, int mon, int mday, int hour, int min,
int sec) -> std::tm {
FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min,
int sec) {
auto tm = std::tm();
tm.tm_sec = sec;
tm.tm_min = min;
@ -238,145 +240,196 @@ 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 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);
auto tm = *std::gmtime(&t);
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
}
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");
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);
}
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");
}
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, 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");
}
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));
template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)>
auto set_tm_gmtoff(T& time, long offset) -> bool {
time.tm_gmtoff = offset;
return true;
}
template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)>
auto set_tm_gmtoff(T&, long) -> bool {
return false;
}
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");
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");
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));
}
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");
// 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");
}
}
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");
#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)));
}
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)),
@ -486,7 +539,6 @@ TEST(chrono_test, format_specs) {
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(24)), "12");
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(4)), "04");
EXPECT_EQ(fmt::format("{:%I}", std::chrono::hours(14)), "02");
EXPECT_EQ(fmt::format("{:%j}", days(12)), "12");
EXPECT_EQ(fmt::format("{:%j}", days(12345)), "12345");
EXPECT_EQ(fmt::format("{:%j}", std::chrono::hours(12345 * 24 + 12)), "12345");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)),
@ -553,7 +605,7 @@ 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) \
# define EXPECT_TIME(spec, time, duration) \
{ \
auto jp_loc = std::locale("ja_JP.utf8"); \
EXPECT_EQ(format_tm(time, spec, jp_loc), \
@ -711,10 +763,10 @@ TEST(chrono_test, weekday) {
EXPECT_EQ(fmt::format("{:%a}", tm), "Sat");
if (loc != std::locale::classic()) {
auto saturdays = std::vector<std::string>{"sáb", "sá.", "sáb."};
auto saturdays = std::vector<std::string>{"sáb", "sá."};
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", tm)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm)));
}
}
@ -743,14 +795,20 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
"01.234000");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
"-01.234000");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}), "12.34");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}), "12.37");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12345}),
"12.34");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{12375}),
"12.37");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{-12375}),
"-12.37");
EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}), "12");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}), "39.99");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}), "01.00");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}), "00.001");
EXPECT_EQ(fmt::format("{:.0%S}", std::chrono::milliseconds{12054}),
"12");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{99999}),
"39.99");
EXPECT_EQ(fmt::format("{:.2%S}", std::chrono::milliseconds{1000}),
"01.00");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::milliseconds{1}),
"00.001");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::seconds{1234}), "34.000");
EXPECT_EQ(fmt::format("{:.3%S}", std::chrono::hours{1234}), "00.000");
EXPECT_EQ(fmt::format("{:.5%S}", dms(1.234)), "00.00123");
@ -764,11 +822,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};
@ -779,7 +837,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)),
@ -799,12 +857,14 @@ 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)
@ -877,11 +937,19 @@ 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 =
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 =
sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000));
EXPECT_EQ(fmt::format("{:%S}", t9), "02.000");
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
auto epoch = sys_time<std::chrono::milliseconds>();
auto d = std::chrono::milliseconds(250);
@ -943,10 +1011,6 @@ TEST(chrono_test, glibc_extensions) {
EXPECT_EQ(fmt::format("{:%U,%W,%V}", t), "02,01,01");
EXPECT_EQ(fmt::format("{:%_U,%_W,%_V}", t), " 2, 1, 1");
EXPECT_EQ(fmt::format("{:%-U,%-W,%-V}", t), "2,1,1");
EXPECT_EQ(fmt::format("{:%j}", t), "008");
EXPECT_EQ(fmt::format("{:%_j}", t), " 8");
EXPECT_EQ(fmt::format("{:%-j}", t), "8");
}
{
@ -958,30 +1022,6 @@ TEST(chrono_test, glibc_extensions) {
EXPECT_EQ(fmt::format("{:%e}", t), " 7");
}
{
auto t = std::tm();
t.tm_year = 7 - 1900;
EXPECT_EQ(fmt::format("{:%Y}", t), "0007");
EXPECT_EQ(fmt::format("{:%_Y}", t), " 7");
EXPECT_EQ(fmt::format("{:%-Y}", t), "7");
}
{
auto t = std::tm();
t.tm_year = -5 - 1900;
EXPECT_EQ(fmt::format("{:%Y}", t), "-005");
EXPECT_EQ(fmt::format("{:%_Y}", t), " -5");
EXPECT_EQ(fmt::format("{:%-Y}", t), "-5");
}
{
auto t = std::tm();
t.tm_mon = 7 - 1;
EXPECT_EQ(fmt::format("{:%m}", t), "07");
EXPECT_EQ(fmt::format("{:%_m}", t), " 7");
EXPECT_EQ(fmt::format("{:%-m}", t), "7");
}
}
TEST(chrono_test, out_of_range) {
@ -1018,6 +1058,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, "{:L%b}", month)));
EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month)));
}
}

View File

@ -9,68 +9,11 @@
#include <iterator> // std::back_inserter
#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());
}
#include "gtest-extra.h" // EXPECT_WRITE
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(
@ -113,15 +56,6 @@ TEST(color_test, format) {
EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) |
fmt::emphasis::underline)),
"\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m");
EXPECT_EQ(
fmt::format(
"{}", fmt::styled(
"all", fmt::emphasis::bold | fmt::emphasis::faint |
fmt::emphasis::italic |
fmt::emphasis::underline | fmt::emphasis::blink |
fmt::emphasis::reverse | fmt::emphasis::conceal |
fmt::emphasis::strikethrough)),
"\x1b[1;2;3;4;5;7;8;9mall\x1b[0m");
}
TEST(color_test, format_to) {

View File

@ -115,7 +115,8 @@ function (run_tests)
endif ()
endfunction ()
# Check if the source file skeleton compiles.
# check if the source file skeleton compiles
expect_compile(check "")
expect_compile(check-error "compilation_error" ERROR)
@ -165,6 +166,31 @@ expect_compile(format-lots-of-arguments-with-function "
fmt::format(\"\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, f);
" ERROR)
# Check if user-defined literals are available
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
check_cxx_source_compiles("
void operator\"\" _udl(long double);
int main() {}"
SUPPORTS_USER_DEFINED_LITERALS)
set(CMAKE_REQUIRED_FLAGS )
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
endif ()
# Make sure that compiler features detected in the header
# match the features detected in CMake.
if (SUPPORTS_USER_DEFINED_LITERALS)
set(supports_udl 1)
else ()
set(supports_udl 0)
endif ()
expect_compile(udl-check "
#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
# error
#endif
")
if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
# Compile-time argument type check
expect_compile(format-string-number-spec "

61
test/compile-fp-test.cc Normal file
View File

@ -0,0 +1,61 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/compile.h"
#include "gmock/gmock.h"
#if FMT_USE_CONSTEVAL
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
};
template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer, format, args...);
return string;
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif // FMT_USE_CONSTEVAL

View File

@ -7,16 +7,21 @@
#include "fmt/compile.h"
#include <iterator>
#include <list>
#include <type_traits>
#include <vector>
#include "fmt/chrono.h"
#include "fmt/ranges.h"
#include "gmock/gmock.h"
#include "gtest-extra.h"
TEST(iterator_test, counting_iterator) {
auto it = fmt::detail::counting_iterator();
auto prev = it++;
EXPECT_EQ(prev.count(), 0);
EXPECT_EQ(it.count(), 1);
EXPECT_EQ((it + 41).count(), 42);
}
TEST(compile_test, compile_fallback) {
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
// not available.
@ -90,6 +95,9 @@ TEST(compile_test, format_escape) {
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
}
TEST(compile_test, format_wide_string) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(compile_test, format_specs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
@ -121,6 +129,7 @@ TEST(compile_test, manual_ordering) {
"true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
}
TEST(compile_test, named) {
@ -129,6 +138,10 @@ TEST(compile_test, named) {
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
@ -193,22 +206,7 @@ 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)
# ifdef __cpp_lib_bit_cast
TEST(compile_test, constexpr_formatted_size) {
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
EXPECT_EQ(size, 2);
@ -228,12 +226,6 @@ TEST(compile_test, constexpr_formatted_size) {
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
EXPECT_EQ(str_size, 3);
}
TEST(compile_test, static_format) {
constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
EXPECT_STREQ(result.c_str(), "42");
EXPECT_EQ(result.str(), "42");
}
# endif
TEST(compile_test, text_and_arg) {
@ -245,14 +237,10 @@ TEST(compile_test, unknown_format_fallback) {
EXPECT_EQ(" 42 ",
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
std::vector<char> v1;
fmt::format_to(std::back_inserter(v1), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", fmt::string_view(v1.data(), v1.size()));
std::vector<char> v2;
fmt::format_to(std::back_inserter(v2), FMT_COMPILE("{name:^4}"),
std::vector<char> v;
fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"),
fmt::arg("name", 42));
EXPECT_EQ(" 42 ", fmt::string_view(v2.data(), v2.size()));
EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size()));
char buffer[4];
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
@ -285,23 +273,11 @@ TEST(compile_test, to_string_and_formatter) {
fmt::format(FMT_COMPILE("{}"), to_stringable());
}
struct std_context_test {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<std_context_test> : formatter<int> {
auto format(std_context_test, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(compile_test, print) {
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!");
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!");
fmt::print(FMT_COMPILE("{}"), std_context_test());
}
#endif
@ -310,17 +286,7 @@ TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
}
#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("{}")));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
}
#endif
@ -335,7 +301,7 @@ TEST(compile_test, is_compiled_string) {
(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 auto operator==(const T& rhs) const -> bool {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
@ -376,7 +342,6 @@ TEST(compile_time_formatting_test, integer) {
EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42l));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
@ -417,53 +382,4 @@ TEST(compile_time_formatting_test, custom_type) {
TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif
#if FMT_USE_CONSTEXPR_STRING
TEST(compile_test, constexpr_string_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 big = []() {
return fmt::format(FMT_COMPILE("{:100}"), ' ') == std::string(100, ' ');
}();
EXPECT_TRUE(big);
}
#endif // FMT_USE_CONSTEXPR_STRING

View File

@ -283,7 +283,7 @@ struct double_double {
double a;
double b;
constexpr explicit double_double(double a_val = 0, double b_val = 0)
explicit constexpr double_double(double a_val = 0, double b_val = 0)
: a(a_val), b(b_val) {}
operator double() const { return a + b; }
@ -292,14 +292,14 @@ struct double_double {
auto format_as(double_double d) -> double { return d; }
auto operator>=(const double_double& lhs, const double_double& rhs) -> bool {
bool operator>=(const double_double& lhs, const double_double& rhs) {
return lhs.a + lhs.b >= rhs.a + rhs.b;
}
struct slow_float {
float value;
constexpr explicit slow_float(float val = 0) : value(val) {}
explicit constexpr slow_float(float val = 0) : value(val) {}
operator float() const { return value; }
auto operator-() const -> slow_float { return slow_float(-value); }
};
@ -307,20 +307,19 @@ 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> {
@ -342,7 +341,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, "3.355445e+07");
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
}
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
@ -356,11 +355,11 @@ TEST(format_impl_test, write_console_signature) {
// A public domain branchless UTF-8 decoder by Christopher Wellons:
// https://github.com/skeeto/branchless-utf8
constexpr auto unicode_is_surrogate(uint32_t c) -> bool {
constexpr bool unicode_is_surrogate(uint32_t c) {
return c >= 0xD800U && c <= 0xDFFFU;
}
FMT_CONSTEXPR auto utf8_encode(char* s, uint32_t c) -> char* {
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
if (c >= (1UL << 16)) {
s[0] = static_cast<char>(0xf0 | (c >> 18));
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));

View File

@ -26,17 +26,11 @@
#include <string> // std::string
#include <thread> // std::thread
#include <type_traits> // std::is_default_constructible
#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<version>)
# include <version>
#endif
#include <limits.h>
#include <limits>
#include "gtest-extra.h"
#include "mock-allocator.h"
#include "util.h"
using fmt::basic_memory_buffer;
using fmt::format_error;
using fmt::memory_buffer;
@ -48,10 +42,6 @@ using fmt::detail::uint128_fallback;
using testing::Return;
using testing::StrictMock;
#ifdef __cpp_lib_concepts
static_assert(std::output_iterator<fmt::appender, char>);
#endif
enum { buffer_size = 256 };
TEST(uint128_test, ctor) {
@ -206,6 +196,10 @@ TEST(util_test, parse_nonnegative_int) {
EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1);
}
TEST(format_impl_test, compute_width) {
EXPECT_EQ(fmt::detail::compute_width("вожык"), 5);
}
TEST(util_test, utf8_to_utf16) {
auto u = fmt::detail::utf8_to_utf16("лошадка");
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
@ -315,17 +309,18 @@ TEST(memory_buffer_test, move_ctor_inline_buffer) {
std::allocator<char>* alloc = buffer.get_allocator().get();
basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer));
// Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
EXPECT_EQ(5u, buffer2.capacity());
// Move should transfer allocator.
EXPECT_EQ(buffer.get_allocator().get(), nullptr);
EXPECT_EQ(buffer2.get_allocator().get(), alloc);
EXPECT_EQ(nullptr, buffer.get_allocator().get());
EXPECT_EQ(alloc, buffer2.get_allocator().get());
};
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc)));
buffer.append(string_view("test"));
const char test[] = "test";
buffer.append(string_view(test, 4));
check_move_buffer("test", buffer);
// Adding one more character fills the inline buffer, but doesn't cause
// dynamic allocation.
@ -350,63 +345,14 @@ TEST(memory_buffer_test, move_ctor_dynamic_buffer) {
EXPECT_GT(buffer2.capacity(), 4u);
}
using std_allocator_noprop = allocator_ref<std::allocator<char>, false>;
TEST(memory_buffer_test, move_ctor_inline_buffer_non_propagating) {
auto check_move_buffer =
[](const char* str,
basic_memory_buffer<char, 5, std_allocator_noprop>& buffer) {
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
const char* original_data_ptr = &buffer[0];
basic_memory_buffer<char, 5, std_allocator_noprop> buffer2(
std::move(buffer));
const char* new_data_ptr = &buffer2[0];
EXPECT_NE(new_data_ptr, original_data_ptr);
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
// Allocators should NOT be transferred; they remain distinct instances.
// The original buffer's allocator pointer should still be valid (not
// nullptr).
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
};
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
buffer.append(string_view("test", 4));
check_move_buffer("test", buffer);
buffer.push_back('a');
check_move_buffer("testa", buffer);
}
TEST(memory_buffer_test, move_ctor_dynamic_buffer_non_propagating) {
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
const char test[] = "test";
buffer.append(test, test + 4);
const char* inline_buffer_ptr = &buffer[0];
buffer.push_back('a');
EXPECT_NE(buffer.data(), inline_buffer_ptr);
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer2;
buffer2 = std::move(buffer);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testa");
EXPECT_GT(buffer2.capacity(), 4u);
EXPECT_NE(buffer2.data(), inline_buffer_ptr);
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
}
void check_move_assign_buffer(const char* str,
basic_memory_buffer<char, 5>& buffer) {
basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer);
// Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(std::string(&buffer[0], buffer.size()), str);
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
EXPECT_EQ(5u, buffer2.capacity());
}
TEST(memory_buffer_test, move_assignment) {
@ -425,8 +371,8 @@ TEST(memory_buffer_test, move_assignment) {
basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer);
// Move should rip the guts of the first buffer.
EXPECT_EQ(buffer.data(), inline_buffer_ptr);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testab");
EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size()));
EXPECT_GT(buffer2.capacity(), 5u);
}
@ -525,18 +471,6 @@ 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));
EXPECT_EQ(p % 2, 0);
}
TEST(format_test, exception_from_lib) {
EXPECT_THROW_MSG(fmt::report_error("test"), format_error, "test");
}
@ -602,10 +536,6 @@ 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 {
@ -636,9 +566,6 @@ 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");
@ -656,9 +583,6 @@ 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) {
@ -870,7 +794,7 @@ TEST(format_test, hash_flag) {
EXPECT_EQ(fmt::format("{:#.2g}", 0.5), "0.50");
EXPECT_EQ(fmt::format("{:#.0f}", 0.5), "0.");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#"), 'c'), format_error,
"invalid format specifier for char");
"missing '}' in format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), 'c'), format_error,
"invalid format specifier for char");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), "abc"), format_error,
@ -891,7 +815,7 @@ TEST(format_test, zero_flag) {
EXPECT_EQ(fmt::format("{0:07}", -42.0), "-000042");
EXPECT_EQ(fmt::format("{0:07}", -42.0l), "-000042");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:0"), 'c'), format_error,
"invalid format specifier for char");
"missing '}' in format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), 'c'), format_error,
"invalid format specifier for char");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), "abc"), format_error,
@ -931,7 +855,6 @@ TEST(format_test, width) {
" 0xcafe");
EXPECT_EQ(fmt::format("{:11}", 'x'), "x ");
EXPECT_EQ(fmt::format("{:12}", "str"), "str ");
EXPECT_EQ(fmt::format("{:*^5}", "🤡"), "*🤡**");
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
@ -939,35 +862,6 @@ TEST(format_test, width) {
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
}
TEST(format_test, debug_presentation) {
EXPECT_EQ(fmt::format("{:?}", ""), R"("")");
EXPECT_EQ(fmt::format("{:*<5.0?}", "\n"), R"(*****)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "\n"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "\n"), R"("\***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "\n"), R"("\n**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", "\n"), R"("\n"*)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "Σ"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "Σ"), R"("Σ***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "Σ"), R"("Σ"**)");
EXPECT_EQ(fmt::format("{:*<5.1?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.3?}", ""), R"("**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", ""), R"(""*)");
EXPECT_EQ(fmt::format("{:*<8?}", "туда"), R"("туда"**)");
EXPECT_EQ(fmt::format("{:*>8?}", "сюда"), R"(**"сюда")");
EXPECT_EQ(fmt::format("{:*^8?}", "中心"), R"(*""*)");
EXPECT_EQ(fmt::format("{:*^14?}", "A\t👈🤯ы猫"), R"(*"A\t👈🤯ы"*)");
}
auto bad_dynamic_spec_msg = FMT_BUILTIN_TYPES
? "width/precision is out of range"
: "width/precision is not integer";
TEST(format_test, runtime_width) {
auto int_maxer = std::to_string(INT_MAX + 1u);
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{" + int_maxer), 0),
@ -990,23 +884,23 @@ TEST(format_test, runtime_width) {
"invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1), format_error,
"width/precision is out of range");
"negative width");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1u)),
format_error, bad_dynamic_spec_msg);
format_error, "number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, -1l), format_error,
bad_dynamic_spec_msg);
"negative width");
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX;
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (value + 1)),
format_error, bad_dynamic_spec_msg);
format_error, "number is too big");
}
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, (INT_MAX + 1ul)),
format_error, bad_dynamic_spec_msg);
format_error, "number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, '0'), format_error,
"width/precision is not integer");
"width is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:{1}}"), 0, 0.0), format_error,
"width/precision is not integer");
"width is not integer");
EXPECT_EQ(fmt::format("{0:{1}}", -42, 4), " -42");
EXPECT_EQ(fmt::format("{0:{1}}", 42u, 5), " 42");
@ -1023,10 +917,6 @@ TEST(format_test, runtime_width) {
EXPECT_EQ(fmt::format("{:{}}", 42, short(4)), " 42");
}
TEST(format_test, exponent_range) {
for (int e = -1074; e <= 1023; ++e) (void)fmt::format("{}", std::ldexp(1, e));
}
TEST(format_test, precision) {
char format_str[buffer_size];
safe_sprintf(format_str, "{0:.%u", UINT_MAX);
@ -1049,7 +939,7 @@ TEST(format_test, precision) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:."), 0.0), format_error,
"invalid precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.}"), 0.0), format_error,
"invalid format string");
"invalid precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.2"), 0), format_error,
"invalid format specifier");
@ -1154,8 +1044,7 @@ 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("{}", 123456789.0f), "1.2345679e+08");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1.0196664e+09");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1019666400");
EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01");
EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34");
@ -1165,45 +1054,21 @@ 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");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.f}"), 42.0), format_error,
"invalid format string");
EXPECT_EQ(fmt::format("{0:.2}", "str"), "st");
EXPECT_EQ(fmt::format("{0:.5}", "вожыкі"), "вожык");
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) {
TEST(xchar_test, utf8_precision) {
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
EXPECT_EQ(fmt::detail::compute_width(result), 4);
EXPECT_EQ(result, "caf\u00e9");
}
@ -1238,23 +1103,23 @@ TEST(format_test, runtime_precision) {
"invalid format string");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1),
format_error, "width/precision is out of range");
format_error, "negative precision");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1u)),
format_error, bad_dynamic_spec_msg);
format_error, "number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, -1l),
format_error, bad_dynamic_spec_msg);
format_error, "negative precision");
if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
long value = INT_MAX;
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (value + 1)),
format_error, bad_dynamic_spec_msg);
format_error, "number is too big");
}
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, (INT_MAX + 1ul)),
format_error, bad_dynamic_spec_msg);
format_error, "number is too big");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, '0'),
format_error, "width/precision is not integer");
format_error, "precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 0.0, 0.0),
format_error, "width/precision is not integer");
format_error, "precision is not integer");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:.{1}}"), 42, 2), format_error,
"invalid format specifier");
@ -1785,20 +1650,6 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) {
EXPECT_EQ("'foo'",
fmt::format("{}", explicitly_convertible_to_std_string_view()));
}
struct convertible_to_std_string_view {
operator std::string_view() const noexcept { return "Hi there"; }
};
FMT_BEGIN_NAMESPACE
template <>
class formatter<convertible_to_std_string_view>
: public formatter<std::string_view> {};
FMT_END_NAMESPACE
TEST(format_test, format_implicitly_convertible_and_inherits_string_view) {
static_assert(fmt::is_formattable<convertible_to_std_string_view>{}, "");
EXPECT_EQ("Hi there", fmt::format("{}", convertible_to_std_string_view{}));
}
#endif
class Answer {};
@ -1857,15 +1708,19 @@ TEST(format_test, format_examples) {
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
EXPECT_EQ("The answer is 42.", to_string(out));
EXPECT_THROW(
const char* filename = "nonexistent";
FILE* ftest = safe_fopen(filename, "r");
if (ftest) fclose(ftest);
int error_code = errno;
EXPECT_TRUE(ftest == nullptr);
EXPECT_SYSTEM_ERROR(
{
const char* filename = "madeup";
FILE* file = fopen(filename, "r");
if (!file)
throw fmt::system_error(errno, "cannot open file '{}'", filename);
fclose(file);
FILE* f = safe_fopen(filename, "r");
if (!f)
throw fmt::system_error(errno, "Cannot open file '{}'", filename);
fclose(f);
},
std::system_error);
error_code, "Cannot open file 'nonexistent'");
EXPECT_EQ("First, thou shalt count to three",
fmt::format("First, thou shalt count to {0}", "three"));
@ -1925,6 +1780,33 @@ 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 && !defined(_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
struct deadlockable {
int value = 0;
mutable std::mutex mutex;
@ -2036,6 +1918,11 @@ TEST(format_test, unpacked_args) {
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
}
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] = {'{', '}'};
TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
@ -2050,12 +1937,19 @@ TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
#endif
static constexpr char format_str[3] = {'{', '}', '\0'};
(void)format_str;
(void)static_with_null;
(void)static_no_null;
#ifndef _MSC_VER
EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
#endif
(void)with_null;
(void)no_null;
#if FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
#endif
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
#endif
@ -2071,6 +1965,7 @@ TEST(format_test, custom_format_compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{}"), const_answer), "42");
}
#if FMT_USE_USER_DEFINED_LITERALS
TEST(format_test, named_arg_udl) {
using namespace fmt::literals;
auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra",
@ -2082,11 +1977,12 @@ TEST(format_test, named_arg_udl) {
EXPECT_EQ(fmt::format("{answer}", "answer"_a = Answer()), "42");
}
#endif // FMT_USE_USER_DEFINED_LITERALS
TEST(format_test, enum) { EXPECT_EQ(fmt::format("{}", foo), "0"); }
TEST(format_test, formatter_not_specialized) {
static_assert(!fmt::is_formattable<fmt::formatter<test_enum>,
static_assert(!fmt::has_formatter<fmt::formatter<test_enum>,
fmt::format_context>::value,
"");
}
@ -2096,8 +1992,6 @@ enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
auto format_as(big_enum e) -> unsigned long long { return e; }
TEST(format_test, strong_enum) {
auto arg = fmt::basic_format_arg<fmt::context>(big_enum_value);
EXPECT_EQ(arg.type(), fmt::detail::type::ulong_long_type);
EXPECT_EQ(fmt::format("{}", big_enum_value), "5000000000");
}
#endif
@ -2147,17 +2041,6 @@ 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) {
fmt::memory_buffer buf;
auto it = fmt::appender(buf);
std::fill_n(it, 3, '~');
EXPECT_EQ(fmt::to_string(buf), "~~~");
}
TEST(format_test, formatted_size) {
@ -2273,7 +2156,7 @@ TEST(format_test, vformat_to) {
fmt::vformat_to(std::back_inserter(s), "{}", args);
EXPECT_EQ(s, "42");
s.clear();
fmt::vformat_to(std::back_inserter(s), "{}", args);
fmt::vformat_to(std::back_inserter(s), FMT_STRING("{}"), args);
EXPECT_EQ(s, "42");
}
@ -2482,7 +2365,6 @@ namespace adl_test {
template <typename... T> void make_format_args(const T&...) = delete;
struct string : std::string {};
auto format_as(const string& s) -> std::string { return s; }
} // namespace adl_test
// Test that formatting functions compile when make_format_args is found by ADL.
@ -2538,130 +2420,3 @@ TEST(format_test, formatter_overrides_implicit_conversion) {
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "x");
EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "y");
}
struct ustring {
using value_type = unsigned;
auto find_first_of(value_type, size_t) const -> size_t;
auto data() const -> const char*;
auto size() const -> size_t;
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<ustring> : formatter<std::string> {
auto format(const ustring&, format_context& ctx) const
-> decltype(ctx.out()) {
return formatter<std::string>::format("ustring", ctx);
}
};
FMT_END_NAMESPACE
TEST(format_test, ustring) {
EXPECT_EQ(fmt::format("{}", ustring()), "ustring");
}
TEST(format_test, writer) {
auto write_to_stdout = []() {
auto w = fmt::writer(stdout);
w.print("{}", 42);
};
EXPECT_WRITE(stdout, write_to_stdout(), "42");
#if FMT_USE_FCNTL
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
fmt::writer(write_end.get()).print("42");
write_end.close();
auto read_end = pipe.read_end.fdopen("r");
int n = 0;
int result = fscanf(read_end.get(), "%d", &n);
(void)result;
EXPECT_EQ(n, 42);
#endif
auto s = fmt::string_buffer();
fmt::writer(s).print("foo");
EXPECT_EQ(s.str(), "foo");
}
#if FMT_USE_FCNTL && !defined(_WIN32)
TEST(format_test, invalid_glibc_buffer) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
auto file = write_end.get();
// This results in _IO_write_ptr < _IO_write_end.
fprintf(file, "111\n");
setvbuf(file, nullptr, _IOLBF, 0);
fmt::print(file, "------\n");
}
#endif // FMT_USE_FCNTL
#if FMT_USE_BITINT
FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension")
TEST(format_test, bitint) {
using fmt::detail::bitint;
using fmt::detail::ubitint;
EXPECT_EQ(fmt::format("{}", ubitint<3>(7)), "7");
EXPECT_EQ(fmt::format("{}", bitint<7>()), "0");
EXPECT_EQ(fmt::format("{}", ubitint<15>(31000)), "31000");
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MIN)), "-32768");
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MAX)), "32767");
EXPECT_EQ(fmt::format("{}", ubitint<32>(4294967295)), "4294967295");
EXPECT_EQ(fmt::format("{}", ubitint<47>(140737488355327ULL)),
"140737488355327");
EXPECT_EQ(fmt::format("{}", bitint<47>(-40737488355327LL)),
"-40737488355327");
// Check lvalues and const
auto a = bitint<8>(0);
auto b = ubitint<32>(4294967295);
const auto c = bitint<7>(0);
const auto d = ubitint<32>(4294967295);
EXPECT_EQ(fmt::format("{}", a), "0");
EXPECT_EQ(fmt::format("{}", b), "4294967295");
EXPECT_EQ(fmt::format("{}", c), "0");
EXPECT_EQ(fmt::format("{}", d), "4294967295");
static_assert(fmt::is_formattable<bitint<64>, char>{}, "");
static_assert(fmt::is_formattable<ubitint<64>, char>{}, "");
}
#endif
#ifdef __cpp_lib_byte
TEST(base_test, format_byte) {
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
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

@ -12,10 +12,10 @@ void invoke_inner(fmt::string_view format_str, Rep rep) {
auto value = std::chrono::duration<Rep, Period>(rep);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(fmt::runtime(format_str), value);
std::string message = fmt::format(format_str, value);
#else
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf), fmt::runtime(format_str), value);
fmt::format_to(std::back_inserter(buf), format_str, value);
#endif
} catch (std::exception&) {
}

View File

@ -22,7 +22,7 @@
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
// The size of the largest possible type in use.
// To let the fuzzer mutation be efficient at cross pollinating between
// To let the the fuzzer mutation be efficient at cross pollinating between
// different types, use a fixed size format. The same bit pattern, interpreted
// as another type, is likely interesting.
constexpr auto fixed_size = 16;

View File

@ -15,10 +15,6 @@
#include "fmt/os.h"
#include "gmock/gmock.h"
#ifdef _MSC_VER
# include <crtdbg.h>
#endif
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \

View File

@ -13360,7 +13360,6 @@ bool UnorderedElementsAreMatcherImplBase::FindPairing(
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
@ -13984,50 +13983,46 @@ MockObjectRegistry g_mock_object_registry;
// Maps a mock object to the reaction Google Mock should have when an
// uninteresting method is called. Protected by g_gmock_mutex.
std::unordered_map<uintptr_t, internal::CallReaction>&
UninterestingCallReactionMap() {
static auto* map = new std::unordered_map<uintptr_t, internal::CallReaction>;
return *map;
}
std::map<const void*, internal::CallReaction> g_uninteresting_call_reaction;
// Sets the reaction Google Mock should have when an uninteresting
// method of the given mock object is called.
void SetReactionOnUninterestingCalls(uintptr_t mock_obj,
void SetReactionOnUninterestingCalls(const void* mock_obj,
internal::CallReaction reaction)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
internal::MutexLock l(&internal::g_gmock_mutex);
UninterestingCallReactionMap()[mock_obj] = reaction;
g_uninteresting_call_reaction[mock_obj] = reaction;
}
} // namespace
// Tells Google Mock to allow uninteresting calls on the given mock
// object.
void Mock::AllowUninterestingCalls(uintptr_t mock_obj)
void Mock::AllowUninterestingCalls(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
SetReactionOnUninterestingCalls(mock_obj, internal::kAllow);
}
// Tells Google Mock to warn the user about uninteresting calls on the
// given mock object.
void Mock::WarnUninterestingCalls(uintptr_t mock_obj)
void Mock::WarnUninterestingCalls(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
SetReactionOnUninterestingCalls(mock_obj, internal::kWarn);
}
// Tells Google Mock to fail uninteresting calls on the given mock
// object.
void Mock::FailUninterestingCalls(uintptr_t mock_obj)
void Mock::FailUninterestingCalls(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
SetReactionOnUninterestingCalls(mock_obj, internal::kFail);
}
// Tells Google Mock the given mock object is being destroyed and its
// entry in the call-reaction table should be removed.
void Mock::UnregisterCallReaction(uintptr_t mock_obj)
void Mock::UnregisterCallReaction(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
internal::MutexLock l(&internal::g_gmock_mutex);
UninterestingCallReactionMap().erase(static_cast<uintptr_t>(mock_obj));
g_uninteresting_call_reaction.erase(mock_obj);
}
// Returns the reaction Google Mock will have on uninteresting calls
@ -14036,12 +14031,9 @@ internal::CallReaction Mock::GetReactionOnUninterestingCalls(
const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
internal::MutexLock l(&internal::g_gmock_mutex);
return (UninterestingCallReactionMap().count(
reinterpret_cast<uintptr_t>(mock_obj)) == 0)
? internal::intToCallReaction(
GMOCK_FLAG(default_mock_behavior))
: UninterestingCallReactionMap()[reinterpret_cast<uintptr_t>(
mock_obj)];
return (g_uninteresting_call_reaction.count(mock_obj) == 0) ?
internal::intToCallReaction(GMOCK_FLAG(default_mock_behavior)) :
g_uninteresting_call_reaction[mock_obj];
}
// Tells Google Mock to ignore mock_obj when checking for leaked mock

View File

@ -2860,7 +2860,6 @@ GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
@ -8647,22 +8646,22 @@ class GTEST_API_ Mock {
// Tells Google Mock to allow uninteresting calls on the given mock
// object.
static void AllowUninterestingCalls(uintptr_t mock_obj)
static void AllowUninterestingCalls(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Tells Google Mock to warn the user about uninteresting calls on
// the given mock object.
static void WarnUninterestingCalls(uintptr_t mock_obj)
static void WarnUninterestingCalls(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Tells Google Mock to fail uninteresting calls on the given mock
// object.
static void FailUninterestingCalls(uintptr_t mock_obj)
static void FailUninterestingCalls(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Tells Google Mock the given mock object is being destroyed and
// its entry in the call-reaction table should be removed.
static void UnregisterCallReaction(uintptr_t mock_obj)
static void UnregisterCallReaction(const void* mock_obj)
GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
// Returns the reaction Google Mock will have on uninteresting calls
@ -11418,7 +11417,6 @@ MATCHER(IsFalse, negation ? "is true" : "is false") {
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
#include <cstdint>
#include <type_traits>
@ -11463,37 +11461,25 @@ constexpr bool HasStrictnessModifier() {
template <typename Base>
class NiceMockImpl {
public:
NiceMockImpl() {
::testing::Mock::AllowUninterestingCalls(reinterpret_cast<uintptr_t>(this));
}
NiceMockImpl() { ::testing::Mock::AllowUninterestingCalls(this); }
~NiceMockImpl() {
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
}
~NiceMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
};
template <typename Base>
class NaggyMockImpl {
public:
NaggyMockImpl() {
::testing::Mock::WarnUninterestingCalls(reinterpret_cast<uintptr_t>(this));
}
NaggyMockImpl() { ::testing::Mock::WarnUninterestingCalls(this); }
~NaggyMockImpl() {
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
}
~NaggyMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
};
template <typename Base>
class StrictMockImpl {
public:
StrictMockImpl() {
::testing::Mock::FailUninterestingCalls(reinterpret_cast<uintptr_t>(this));
}
StrictMockImpl() { ::testing::Mock::FailUninterestingCalls(this); }
~StrictMockImpl() {
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
}
~StrictMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
};
} // namespace internal

View File

@ -6390,18 +6390,17 @@ class MatcherBase : private MatcherDescriberInterface {
}
protected:
MatcherBase() : vtable_(nullptr), buffer_() {}
MatcherBase() : vtable_(nullptr) {}
// Constructs a matcher from its implementation.
template <typename U>
explicit MatcherBase(const MatcherInterface<U>* impl)
: vtable_(nullptr), buffer_() {
explicit MatcherBase(const MatcherInterface<U>* impl) {
Init(impl);
}
template <typename M, typename = typename std::remove_reference<
M>::type::is_gtest_matcher>
MatcherBase(M&& m) : vtable_(nullptr), buffer_() { // NOLINT
MatcherBase(M&& m) { // NOLINT
Init(std::forward<M>(m));
}

View File

@ -37,7 +37,7 @@ template <typename T> class mock_allocator {
MOCK_METHOD(void, deallocate, (T*, size_t));
};
template <typename Allocator, bool PropagateOnMove = true> class allocator_ref {
template <typename Allocator> class allocator_ref {
private:
Allocator* alloc_;
@ -48,8 +48,6 @@ template <typename Allocator, bool PropagateOnMove = true> class allocator_ref {
public:
using value_type = typename Allocator::value_type;
using propagate_on_container_move_assignment =
fmt::bool_constant<PropagateOnMove>;
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
@ -68,21 +66,12 @@ template <typename Allocator, bool PropagateOnMove = true> class allocator_ref {
}
public:
auto get() const -> Allocator* { return alloc_; }
Allocator* get() const { return alloc_; }
auto allocate(size_t n) -> value_type* {
value_type* allocate(size_t n) {
return std::allocator_traits<Allocator>::allocate(*alloc_, n);
}
void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); }
friend auto operator==(allocator_ref a, allocator_ref b) noexcept -> bool {
if (a.alloc_ == b.alloc_) return true;
return a.alloc_ && b.alloc_ && *a.alloc_ == *b.alloc_;
}
friend auto operator!=(allocator_ref a, allocator_ref b) noexcept -> bool {
return !(a == b);
}
};
#endif // FMT_MOCK_ALLOCATOR_H_

View File

@ -1,25 +0,0 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "gtest/gtest.h"
#if !defined(__GNUC__) || __GNUC__ >= 5
# 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) {
double d = 42;
auto args = fmt::make_format_args(d);
EXPECT_EQ(fmt::format_args(args).get(0).type(),
fmt::detail::type::custom_type);
}
#endif

View File

@ -10,7 +10,6 @@
#include <cstdlib> // std::exit
#include <cstring>
#include <memory>
#include <thread>
#include "gtest-extra.h"
#include "util.h"
@ -19,21 +18,10 @@ using fmt::buffered_file;
using testing::HasSubstr;
using wstring_view = fmt::basic_string_view<wchar_t>;
static auto uniq_file_name(unsigned line_number) -> std::string {
static std::string uniq_file_name(unsigned line_number) {
return "test-file" + std::to_string(line_number);
}
auto safe_fopen(const char* filename, const char* mode) -> FILE* {
#if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen.
FILE* f = nullptr;
errno = fopen_s(&f, filename, mode);
return f;
#else
return std::fopen(filename, mode);
#endif
}
#ifdef _WIN32
# include <windows.h>
@ -248,7 +236,8 @@ TEST(buffered_file_test, descriptor) {
}
TEST(ostream_test, move) {
fmt::ostream out = fmt::output_file(uniq_file_name(__LINE__));
auto test_file = uniq_file_name(__LINE__);
fmt::ostream out = fmt::output_file(test_file);
fmt::ostream moved(std::move(out));
moved.print("hello");
}
@ -440,7 +429,8 @@ TEST(file_test, read) {
}
TEST(file_test, read_error) {
file f(uniq_file_name(__LINE__), file::WRONLY | file::CREATE);
auto test_file = uniq_file_name(__LINE__);
file f(test_file, file::WRONLY | file::CREATE);
char buf;
// We intentionally read from a file opened in the write-only mode to
// cause error.
@ -449,13 +439,15 @@ TEST(file_test, read_error) {
TEST(file_test, write) {
auto pipe = fmt::pipe();
write(pipe.write_end, "test");
auto test_file = uniq_file_name(__LINE__);
write(pipe.write_end, test_file);
pipe.write_end.close();
EXPECT_READ(pipe.read_end, "test");
EXPECT_READ(pipe.read_end, test_file);
}
TEST(file_test, write_error) {
file f(uniq_file_name(__LINE__), file::RDONLY | file::CREATE);
auto test_file = uniq_file_name(__LINE__);
file f(test_file, file::RDONLY | file::CREATE);
// We intentionally write to a file opened in the read-only mode to
// cause error.
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
@ -521,79 +513,4 @@ 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

@ -265,7 +265,7 @@ template <> struct formatter<abstract> : ostream_formatter {};
} // namespace fmt
void format_abstract_compiles(const abstract& a) {
(void)fmt::format(FMT_COMPILE("{}"), a);
fmt::format(FMT_COMPILE("{}"), a);
}
TEST(ostream_test, is_formattable) {

View File

@ -1,25 +0,0 @@
// A quick and dirty performance test.
// For actual benchmarks see https://github.com/fmtlib/format-benchmark.
#include <atomic>
#include <chrono>
#include <iterator>
#include "fmt/format.h"
int main() {
const int n = 10000000;
auto start = std::chrono::steady_clock::now();
for (int iteration = 0; iteration < n; ++iteration) {
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf),
"Hello, {}. The answer is {} and {}.", 1, 2345, 6789);
}
std::atomic_signal_fence(std::memory_order_acq_rel); // Clobber memory.
auto end = std::chrono::steady_clock::now();
// Print time in milliseconds.
std::chrono::duration<double> duration = end - start;
fmt::print("{:.1f}\n", duration.count() * 1000);
}

View File

@ -6,12 +6,16 @@
// For the license information refer to format.h.
#include "fmt/printf.h"
// include <format> if possible for https://github.com/fmtlib/fmt/pull/4042
#if FMT_HAS_INCLUDE(<format>) && FMT_CPLUSPLUS > 201703L
# include <format>
#endif
#include <cctype>
#include <climits>
#include <cstring>
#include "fmt/xchar.h" // DEPRECATED!
#include "fmt/xchar.h"
#include "gtest-extra.h"
#include "util.h"
@ -22,21 +26,27 @@ using fmt::detail::max_value;
const unsigned big_num = INT_MAX + 1u;
// Makes format string argument positional.
static auto make_positional(fmt::string_view format) -> std::string {
static std::string make_positional(fmt::string_view format) {
std::string s(format.data(), format.size());
s.replace(s.find('%'), 1, "%1$");
return s;
}
static std::wstring make_positional(fmt::basic_string_view<wchar_t> format) {
std::wstring s(format.data(), format.size());
s.replace(s.find(L'%'), 1, L"%1$");
return s;
}
// A wrapper around fmt::sprintf to workaround bogus warnings about invalid
// format strings in MSVC.
template <typename... Args>
auto test_sprintf(fmt::string_view format, const Args&... args) -> std::string {
std::string test_sprintf(fmt::string_view format, const Args&... args) {
return fmt::sprintf(format, args...);
}
template <typename... Args>
auto test_sprintf(fmt::basic_string_view<wchar_t> format, const Args&... args)
-> std::wstring {
std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
const Args&... args) {
return fmt::sprintf(format, args...);
}
@ -45,7 +55,10 @@ auto test_sprintf(fmt::basic_string_view<wchar_t> format, const Args&... args)
<< "format: " << format; \
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
TEST(printf_test, no_args) { EXPECT_EQ("test", test_sprintf("test")); }
TEST(printf_test, no_args) {
EXPECT_EQ("test", test_sprintf("test"));
EXPECT_EQ(L"test", fmt::sprintf(L"test"));
}
TEST(printf_test, escape) {
EXPECT_EQ("%", test_sprintf("%%"));
@ -53,6 +66,11 @@ TEST(printf_test, escape) {
EXPECT_EQ("% after", test_sprintf("%% after"));
EXPECT_EQ("before % after", test_sprintf("before %% after"));
EXPECT_EQ("%s", test_sprintf("%%s"));
EXPECT_EQ(L"%", fmt::sprintf(L"%%"));
EXPECT_EQ(L"before %", fmt::sprintf(L"before %%"));
EXPECT_EQ(L"% after", fmt::sprintf(L"%% after"));
EXPECT_EQ(L"before % after", fmt::sprintf(L"before %% after"));
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
}
TEST(printf_test, positional_args) {
@ -449,6 +467,9 @@ TEST(printf_test, char) {
EXPECT_PRINTF("x", "%c", 'x');
int max = max_value<int>();
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
// EXPECT_PRINTF("x", "%lc", L'x');
EXPECT_PRINTF(L"x", L"%c", L'x');
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
}
TEST(printf_test, string) {
@ -456,6 +477,10 @@ TEST(printf_test, string) {
const char* null_str = nullptr;
EXPECT_PRINTF("(null)", "%s", null_str);
EXPECT_PRINTF(" (null)", "%10s", null_str);
EXPECT_PRINTF(L"abc", L"%s", L"abc");
const wchar_t* null_wstr = nullptr;
EXPECT_PRINTF(L"(null)", L"%s", null_wstr);
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
}
TEST(printf_test, pointer) {
@ -469,6 +494,16 @@ TEST(printf_test, pointer) {
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
const char* null_str = nullptr;
EXPECT_PRINTF("(nil)", "%p", null_str);
p = &n;
EXPECT_PRINTF(fmt::format(L"{}", p), L"%p", p);
p = nullptr;
EXPECT_PRINTF(L"(nil)", L"%p", p);
EXPECT_PRINTF(L" (nil)", L"%10p", p);
const wchar_t* w = L"test";
EXPECT_PRINTF(fmt::format(L"{:p}", w), L"%p", w);
const wchar_t* null_wstr = nullptr;
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
}
enum test_enum { answer = 42 };
@ -496,6 +531,10 @@ TEST(printf_test, printf_error) {
}
#endif
TEST(printf_test, wide_string) {
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
}
TEST(printf_test, vprintf) {
int n = 42;
auto store = fmt::make_format_args<fmt::printf_context>(n);

View File

@ -28,6 +28,11 @@
# define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
#endif
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1910
# define FMT_RANGES_TEST_ENABLE_JOIN
# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
#endif
#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
TEST(ranges_test, format_array) {
int arr[] = {1, 2, 3, 5, 7, 11};
@ -47,8 +52,6 @@ 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]");
@ -67,9 +70,6 @@ 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) {
@ -88,8 +88,6 @@ 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 {};
@ -153,7 +151,6 @@ 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 {
@ -173,19 +170,19 @@ TEST(ranges_test, format_adl_begin_end) {
TEST(ranges_test, format_pair) {
auto p = std::pair<int, float>(42, 1.5f);
EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
EXPECT_EQ(fmt::format("{:}", p), "(42, 1.5)");
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');
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
EXPECT_EQ(fmt::format("{:n}", t), "421.5\"this is tuple\"'i'");
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));
@ -213,6 +210,7 @@ TEST(ranges_test, tuple_parse_calls_element_parse) {
EXPECT_THROW(f.parse(ctx), bad_format);
}
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
struct tuple_like {
int i;
std::string str;
@ -232,12 +230,9 @@ 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> : public std::integral_constant<size_t, 2> {};
struct tuple_size<tuple_like> : 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>());
@ -248,6 +243,7 @@ TEST(ranges_test, format_struct) {
auto t = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
}
#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
TEST(ranges_test, format_to) {
char buf[10];
@ -330,7 +326,7 @@ template <typename T> class noncopyable_range {
explicit noncopyable_range(Args&&... args)
: vec(std::forward<Args>(args)...) {}
noncopyable_range(const noncopyable_range&) = delete;
noncopyable_range(noncopyable_range const&) = delete;
noncopyable_range(noncopyable_range&) = delete;
auto begin() -> iterator { return vec.begin(); }
@ -364,7 +360,8 @@ TEST(ranges_test, enum_range) {
#if !FMT_MSC_VERSION
TEST(ranges_test, unformattable_range) {
EXPECT_FALSE((fmt::is_formattable<std::vector<unformattable>, char>::value));
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
fmt::format_context>::value));
}
#endif
@ -402,6 +399,7 @@ TEST(ranges_test, join_bytes) {
}
#endif
#ifdef FMT_RANGES_TEST_ENABLE_JOIN
TEST(ranges_test, join_tuple) {
// Value tuple args.
auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
@ -420,11 +418,7 @@ TEST(ranges_test, join_tuple) {
auto t4 = std::tuple<float>(4.0f);
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
// Tuple-like.
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");
@ -437,7 +431,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) {
@ -457,7 +451,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;
@ -487,7 +481,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"};
@ -515,13 +509,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 {
@ -537,6 +531,8 @@ TEST(ranges_test, format_join_adl_begin_end) {
EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43");
}
#endif // FMT_RANGES_TEST_ENABLE_JOIN
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L
TEST(ranges_test, nested_ranges) {
auto l = std::list{1, 2, 3};
@ -562,7 +558,7 @@ TEST(ranges_test, escape) {
EXPECT_EQ(fmt::format("{}", vec{"\x7f"}), "[\"\\x7f\"]");
EXPECT_EQ(fmt::format("{}", vec{"n\xcc\x83"}), "[\"n\xcc\x83\"]");
if (fmt::detail::use_utf8) {
if (fmt::detail::use_utf8()) {
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
// Unassigned Unicode code points.
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
@ -580,11 +576,7 @@ TEST(ranges_test, escape) {
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
"[['x']]");
// Disabled due to a clang 17 bug: https://github.com/fmtlib/fmt/issues/4144.
#if FMT_CLANG_VERSION >= 1800
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
#endif
}
template <typename R> struct fmt_ref_view {
@ -663,8 +655,6 @@ 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 {
@ -762,17 +752,17 @@ TEST(ranges_test, std_istream_iterator_join) {
EXPECT_EQ("1, 2, 3, 4, 5", fmt::format("{}", fmt::join(first, last, ", ")));
}
// Mirrors C++20 std::ranges::basic_istream_view::iterator.
struct noncopyable_istream_iterator : std::istream_iterator<int> {
using base = std::istream_iterator<int>;
explicit noncopyable_istream_iterator(std::istringstream& iss) : base{iss} {}
TEST(ranges_test, movable_only_istream_iter_join) {
// Mirrors C++20 std::ranges::basic_istream_view::iterator.
struct noncopyable_istream_iterator : std::istream_iterator<int> {
explicit noncopyable_istream_iterator(std::istringstream& iss)
: std::istream_iterator<int>{iss} {}
noncopyable_istream_iterator(const noncopyable_istream_iterator&) = delete;
noncopyable_istream_iterator(noncopyable_istream_iterator&&) = default;
};
static_assert(!std::is_copy_constructible<noncopyable_istream_iterator>::value,
"");
};
static_assert(
!std::is_copy_constructible<noncopyable_istream_iterator>::value, "");
TEST(ranges_test, movable_only_istream_iter_join) {
auto&& iss = std::istringstream("1 2 3 4 5");
auto first = noncopyable_istream_iterator(iss);
auto last = std::istream_iterator<int>();
@ -780,18 +770,6 @@ TEST(ranges_test, movable_only_istream_iter_join) {
fmt::format("{}", fmt::join(std::move(first), last, ", ")));
}
struct movable_iter_range {
std::istringstream iss{"1 2 3 4 5"};
noncopyable_istream_iterator begin() {
return noncopyable_istream_iterator{iss};
}
std::istream_iterator<int> end() { return {}; }
};
TEST(ranges_test, movable_only_istream_iter_join2) {
EXPECT_EQ("[1, 2, 3, 4, 5]", fmt::format("{}", movable_iter_range{}));
}
struct not_range {
void begin() const {}
void end() const {}

View File

@ -111,9 +111,9 @@ TEST(scan_test, invalid_format) {
}
namespace std {
using fmt::scan;
using fmt::scan_error;
} // namespace std
using fmt::scan;
using fmt::scan_error;
}
TEST(scan_test, example) {
// Example from https://wg21.link/p1729r3.

View File

@ -192,10 +192,7 @@ class file_scan_buffer final : public scan_buffer {
flockfile(f);
fill();
}
~file_scan_buffer() {
FILE* f = file_;
funlockfile(f);
}
~file_scan_buffer() { funlockfile(file_); }
};
} // namespace detail
@ -211,7 +208,7 @@ class scan_parse_context {
public:
using iterator = string_view::iterator;
FMT_CONSTEXPR explicit scan_parse_context(string_view format)
explicit FMT_CONSTEXPR scan_parse_context(string_view format)
: format_(format) {}
FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); }
@ -229,8 +226,6 @@ enum class scan_type {
uint_type,
long_long_type,
ulong_long_type,
double_type,
float_type,
string_type,
string_view_type,
custom_type
@ -253,8 +248,6 @@ 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_;
@ -280,10 +273,6 @@ 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)
@ -313,10 +302,6 @@ 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:
@ -359,7 +344,7 @@ class scan_context {
using iterator = detail::scan_iterator;
using sentinel = detail::scan_sentinel;
FMT_CONSTEXPR explicit scan_context(detail::scan_buffer& buf, scan_args args)
explicit FMT_CONSTEXPR scan_context(detail::scan_buffer& buf, scan_args args)
: buf_(buf), args_(args) {}
FMT_CONSTEXPR auto arg(int id) const -> scan_arg {
@ -380,7 +365,7 @@ const char* parse_scan_specs(const char* begin, const char* end,
switch (to_ascii(*begin)) {
// TODO: parse more scan format specifiers
case 'x':
specs.set_type(presentation_type::hex);
specs.type = presentation_type::hex;
++begin;
break;
case '}':
@ -449,7 +434,7 @@ auto read_hex(scan_iterator it, T& value) -> scan_iterator {
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
auto read(scan_iterator it, T& value, const format_specs& specs)
-> scan_iterator {
if (specs.type() == presentation_type::hex) return read_hex(it, value);
if (specs.type == presentation_type::hex) return read_hex(it, value);
return read(it, value);
}
@ -469,47 +454,6 @@ 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++);
@ -606,12 +550,12 @@ struct scan_handler {
return begin;
}
FMT_NORETURN void on_error(const char* message) { report_error(message); }
void on_error(const char* message) { report_error(message); }
};
void vscan(detail::scan_buffer& buf, string_view fmt, scan_args args) {
auto h = detail::scan_handler(fmt, buf, args);
detail::parse_format_string(fmt, h);
detail::parse_format_string<false>(fmt, h);
}
template <size_t I, typename... T, FMT_ENABLE_IF(I == sizeof...(T))>

View File

@ -20,11 +20,6 @@
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 ");
@ -39,18 +34,11 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80");
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")),
"[\xED\xA0\xBD\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\"");
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
# 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");
@ -98,9 +86,6 @@ TEST(std_test, complex) {
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)");
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "(1+2i) ");
EXPECT_EQ(fmt::format("{:-<8}", std::complex<double>(1, 2)), "(1+2i)--");
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
" (1.00+2.20i)");
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)),
@ -145,13 +130,11 @@ TEST(std_test, optional) {
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
#endif
}
TEST(std_test, expected) {
#ifdef __cpp_lib_expected
EXPECT_EQ(fmt::format("{}", std::expected<void, int>{}), "expected()");
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{1}), "expected(1)");
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{std::unexpected(1)}),
"unexpected(1)");
@ -175,7 +158,6 @@ TEST(std_test, expected) {
EXPECT_FALSE(
(fmt::is_formattable<std::expected<int, unformattable2>>::value));
EXPECT_TRUE((fmt::is_formattable<std::expected<int, int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::expected<void, int>>::value));
#endif
}
@ -197,33 +179,7 @@ class my_class {
return fmt::to_string(elm.av);
}
};
class my_class_int {
public:
int av;
private:
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
};
} // namespace my_nso
TEST(std_test, expected_format_as) {
#ifdef __cpp_lib_expected
EXPECT_EQ(
fmt::format(
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
"expected(\"first\")");
EXPECT_EQ(
fmt::format("{}",
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
"expected(\"7\")");
EXPECT_EQ(fmt::format("{}",
std::expected<my_nso::my_class_int, int>{
my_nso::my_class_int{8}}),
"expected(8)");
#endif
}
TEST(std_test, optional_format_as) {
#ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
@ -232,8 +188,6 @@ TEST(std_test, optional_format_as) {
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
"optional(\"7\")");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
"optional(8)");
#endif
}
@ -303,42 +257,16 @@ TEST(std_test, variant) {
#endif
}
TEST(std_test, variant_format_as) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
"variant(\"first\")");
EXPECT_EQ(fmt::format(
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
"variant(\"first\")");
EXPECT_EQ(
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
"variant(\"7\")");
EXPECT_EQ(
fmt::format("{}",
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
"variant(8)");
#endif
}
TEST(std_test, error_code) {
auto& generic = std::generic_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\"");
EXPECT_EQ(fmt::format("{}",
std::map<std::error_code, int>{
{std::error_code(42, generic), 0}}),
"{\"generic:42\": 0}");
EXPECT_EQ("generic:42",
fmt::format(FMT_STRING("{0}"),
std::error_code(42, std::generic_category())));
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())));
}
template <typename Catch> void exception_test() {
@ -434,12 +362,11 @@ TEST(std_test, format_atomic) {
#ifdef __cpp_lib_atomic_flag_test
TEST(std_test, format_atomic_flag) {
std::atomic_flag f;
std::atomic_flag f = ATOMIC_FLAG_INIT;
(void)f.test_and_set();
EXPECT_EQ(fmt::format("{}", f), "true");
f.clear();
const std::atomic_flag& cf = f;
const std::atomic_flag cf = ATOMIC_FLAG_INIT;
EXPECT_EQ(fmt::format("{}", cf), "false");
}
#endif // __cpp_lib_atomic_flag_test
@ -461,34 +388,3 @@ TEST(std_test, format_shared_ptr) {
EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
fmt::format("{}", fmt::ptr(sp)));
}
TEST(std_test, format_reference_wrapper) {
int num = 35;
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

@ -12,7 +12,7 @@
void throw_assertion_failure(const char* message);
#define FMT_ASSERT(condition, message) \
((condition) ? (void)0 : throw_assertion_failure(message))
if (!(condition)) throw_assertion_failure(message);
#include "gtest/gtest.h"

View File

@ -15,7 +15,7 @@
using testing::Contains;
TEST(unicode_test, use_utf8) { EXPECT_TRUE(fmt::detail::use_utf8); }
TEST(unicode_test, use_utf8) { EXPECT_TRUE(fmt::detail::use_utf8()); }
TEST(unicode_test, legacy_locale) {
auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");

View File

@ -36,7 +36,8 @@ std::locale do_get_locale(const char* name) {
std::locale get_locale(const char* name, const char* alt_name) {
auto loc = do_get_locale(name);
if (loc == std::locale::classic() && alt_name) loc = do_get_locale(alt_name);
if (loc == std::locale::classic() && alt_name)
loc = do_get_locale(alt_name);
#ifdef __OpenBSD__
// Locales are not working in OpenBSD:
// https://github.com/fmtlib/fmt/issues/3670.

View File

@ -31,6 +31,17 @@ extern const char* const file_content;
// Opens a buffered file for reading.
auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file;
inline auto safe_fopen(const char* filename, const char* mode) -> FILE* {
#if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen.
FILE* f = nullptr;
errno = fopen_s(&f, filename, mode);
return f;
#else
return std::fopen(filename, mode);
#endif
}
template <typename Char> class basic_test_string {
private:
std::basic_string<Char> value_;

View File

@ -72,18 +72,17 @@ TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
#endif
TEST(xchar_test, format) {
EXPECT_EQ(fmt::format(L"{}", 42), L"42");
EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2");
EXPECT_EQ(fmt::format(L"{}", 1e100), L"1e+100");
EXPECT_EQ(fmt::format(L"{}", L"abc"), L"abc");
EXPECT_EQ(fmt::format(L"{}", L'z'), L"z");
EXPECT_EQ(L"42", fmt::format(L"{}", 42));
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
EXPECT_EQ(fmt::format(L"{}", true), L"true");
EXPECT_EQ(fmt::format(L"{0}", L'a'), L"a");
EXPECT_EQ(fmt::format(L"Letter {}", L'\x40e'), L"Letter \x40e"); // Ў
if (sizeof(wchar_t) == 4)
EXPECT_EQ(fmt::format(fmt::runtime(L"{:𓀨>3}"), 42), L"𓀨42");
EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), L"abc1");
EXPECT_EQ(L"true", fmt::format(L"{}", true));
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
EXPECT_EQ(L"Cyrillic letter \x42e",
fmt::format(L"Cyrillic letter {}", L'\x42e'));
EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
}
TEST(xchar_test, is_formattable) {
@ -97,6 +96,93 @@ TEST(xchar_test, compile_time_string) {
#endif
}
#if FMT_CPLUSPLUS > 201103L
struct custom_char {
int value;
custom_char() = default;
template <typename T>
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
constexpr operator char() const {
return value <= 0xff ? static_cast<char>(value) : '\0';
}
constexpr bool operator<(custom_char c) const { return value < c.value; }
};
namespace std {
template <> struct char_traits<custom_char> {
using char_type = custom_char;
using int_type = int;
using off_type = streamoff;
using pos_type = streampos;
using state_type = mbstate_t;
static constexpr void assign(char_type& r, const char_type& a) { r = a; }
static constexpr bool eq(char_type a, char_type b) { return a == b; }
static constexpr bool lt(char_type a, char_type b) { return a < b; }
static FMT_CONSTEXPR int compare(const char_type* s1, const char_type* s2,
size_t count) {
for (; count; count--, s1++, s2++) {
if (lt(*s1, *s2)) return -1;
if (lt(*s2, *s1)) return 1;
}
return 0;
}
static FMT_CONSTEXPR size_t length(const char_type* s) {
size_t count = 0;
while (!eq(*s++, custom_char(0))) count++;
return count;
}
static const char_type* find(const char_type*, size_t, const char_type&);
static FMT_CONSTEXPR char_type* move(char_type* dest, const char_type* src,
size_t count) {
if (count == 0) return dest;
char_type* ret = dest;
if (src < dest) {
dest += count;
src += count;
for (; count; count--) assign(*--dest, *--src);
} else if (src > dest)
copy(dest, src, count);
return ret;
}
static FMT_CONSTEXPR char_type* copy(char_type* dest, const char_type* src,
size_t count) {
char_type* ret = dest;
for (; count; count--) assign(*dest++, *src++);
return ret;
}
static FMT_CONSTEXPR char_type* assign(char_type* dest, std::size_t count,
char_type a) {
char_type* ret = dest;
for (; count; count--) assign(*dest++, a);
return ret;
}
static int_type not_eof(int_type);
static char_type to_char_type(int_type);
static int_type to_int_type(char_type);
static bool eq_int_type(int_type, int_type);
static int_type eof();
};
} // namespace std
auto to_ascii(custom_char c) -> char { return c; }
FMT_BEGIN_NAMESPACE
template <> struct is_char<custom_char> : std::true_type {};
FMT_END_NAMESPACE
TEST(xchar_test, format_custom_char) {
const custom_char format[] = {'{', '}', 0};
auto result = fmt::format(format, custom_char('x'));
EXPECT_EQ(result.size(), 1);
EXPECT_EQ(result[0], custom_char('x'));
}
#endif
TEST(xchar_test, format_to) {
auto buf = std::vector<wchar_t>();
fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
@ -146,6 +232,7 @@ TEST(format_test, wide_format_to_n) {
EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
}
#if FMT_USE_USER_DEFINED_LITERALS
TEST(xchar_test, named_arg_udl) {
using namespace fmt::literals;
auto udl_a =
@ -156,6 +243,7 @@ TEST(xchar_test, named_arg_udl) {
fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
udl_a);
}
#endif // FMT_USE_USER_DEFINED_LITERALS
TEST(xchar_test, print) {
// Check that the wide print overload compiles.
@ -172,13 +260,6 @@ 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) {
@ -232,9 +313,106 @@ 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");
}
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");
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));
}
}
TEST(xchar_test, color) {
@ -379,7 +557,7 @@ TEST(locale_test, int_formatter) {
f.parse(parse_ctx);
auto buf = fmt::memory_buffer();
fmt::basic_format_context<fmt::appender, char> format_ctx(
fmt::appender(buf), {}, fmt::locale_ref(loc));
fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
f.format(12345, format_ctx);
EXPECT_EQ(fmt::to_string(buf), "12,345");
}
@ -390,9 +568,11 @@ TEST(locale_test, chrono_weekday) {
auto sat = fmt::weekday(6);
EXPECT_EQ(fmt::format(L"{}", sat), L"Sat");
if (loc != std::locale::classic()) {
// L'\341' is 'á'.
auto saturdays =
std::vector<std::wstring>{L"s\341b", L"s\341.", L"s\341b."};
// L'\xE1' is 'á'.
auto saturdays = std::vector<std::wstring>{
L"s\xE1"
"b",
L"s\xE1."};
EXPECT_THAT(saturdays, Contains(fmt::format(loc, L"{:L}", sat)));
}
std::locale::global(loc_old);
@ -402,20 +582,11 @@ TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
}
TEST(std_test_xchar, format_bitset) {
auto bs = std::bitset<6>(42);
EXPECT_EQ(fmt::format(L"{}", bs), L"101010");
EXPECT_EQ(fmt::format(L"{:0>8}", bs), L"00101010");
EXPECT_EQ(fmt::format(L"{:-^12}", bs), L"---101010---");
}
TEST(std_test_xchar, complex) {
auto s = fmt::format(L"{}", std::complex<double>(1, 2));
EXPECT_EQ(s, L"(1+2i)");
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)),
L"(1.00+2.00i)");
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)), L"(1.00+2.00i)");
EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) ");
EXPECT_EQ(fmt::format(L"{:-<8}", std::complex<double>(1, 2)), L"(1+2i)--");
}
TEST(std_test_xchar, optional) {