externals: Update fmt to 10.1.1

Merge commit '091ca2aa033bf9bf9bfecdc55140d2cd15ed2ccb'
This commit is contained in:
Merry 2023-10-08 08:55:04 +01:00
commit 7a7557f70f
76 changed files with 6841 additions and 4838 deletions

8
externals/fmt/.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions" # Necessary to update action hashs
directory: "/"
schedule:
interval: "weekly"
# Allow up to 3 opened pull requests for github-actions versions
open-pull-requests-limit: 3

View file

@ -1,6 +1,7 @@
<!-- <!--
Please read the contribution guidelines before submitting a pull request: Please read the contribution guidelines before submitting a pull request:
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md. https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
By submitting this pull request, you agree that your contributions are licensed By submitting this pull request, you agree to license your contribution(s)
under the {fmt} license, and agree to future changes to the licensing. under the terms outlined in LICENSE.rst and represent that you have the right
to do so.
--> -->

View file

@ -0,0 +1,30 @@
name: CIFuzz
on: [pull_request]
permissions:
contents: read
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'fmt'
dry-run: false
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'fmt'
fuzz-seconds: 300
dry-run: false
language: c++
- name: Upload Crash
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

View file

@ -11,7 +11,15 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Add ubuntu mirrors
run: |
# Github Actions caching proxy is at times unreliable
# see https://github.com/actions/runner-images/issues/7048
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
- name: Create Build Environment - name: Create Build Environment
run: | run: |

View file

@ -7,66 +7,83 @@ permissions:
jobs: jobs:
build: build:
runs-on: ${{ matrix.os }} runs-on: ubuntu-20.04
strategy: strategy:
matrix: matrix:
cxx: [g++-4.8, g++-10, clang++-9] cxx: [g++-4.8, g++-10, clang++-9]
build_type: [Debug, Release] build_type: [Debug, Release]
std: [11] std: [11]
os: [ubuntu-18.04]
include: include:
- cxx: g++-4.8 - cxx: g++-4.8
install: sudo apt install g++-4.8 install: sudo apt install g++-4.8
os: ubuntu-18.04
- cxx: g++-8 - cxx: g++-8
build_type: Debug build_type: Debug
std: 14 std: 14
install: sudo apt install g++-8 install: sudo apt install g++-8
os: ubuntu-18.04
- cxx: g++-8 - cxx: g++-8
build_type: Debug build_type: Debug
std: 17 std: 17
install: sudo apt install g++-8 install: sudo apt install g++-8
os: ubuntu-18.04 - cxx: g++-9
build_type: Debug
std: 17
- cxx: g++-10 - cxx: g++-10
build_type: Debug build_type: Debug
std: 17 std: 17
os: ubuntu-18.04
- cxx: g++-11 - cxx: g++-11
build_type: Debug build_type: Debug
std: 20 std: 20
os: ubuntu-20.04
install: sudo apt install g++-11 install: sudo apt install g++-11
- cxx: clang++-8 - cxx: clang++-8
build_type: Debug build_type: Debug
std: 17 std: 17
cxxflags: -stdlib=libc++ cxxflags: -stdlib=libc++
os: ubuntu-18.04
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev 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 - cxx: clang++-9
build_type: Debug build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
std: 17 std: 17
os: ubuntu-18.04 install: sudo apt install clang-9
- cxx: clang++-11 - cxx: clang++-11
build_type: Debug build_type: Debug
std: 20 std: 20
os: ubuntu-20.04
- cxx: clang++-11 - cxx: clang++-11
build_type: Debug build_type: Debug
std: 20 std: 20
cxxflags: -stdlib=libc++ cxxflags: -stdlib=libc++
os: ubuntu-20.04
install: sudo apt install libc++-11-dev libc++abi-11-dev install: sudo apt install libc++-11-dev libc++abi-11-dev
- shared: -DBUILD_SHARED_LIBS=ON - shared: -DBUILD_SHARED_LIBS=ON
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set timezone
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
- name: Add repositories for older GCC
run: |
# 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 ubuntu mirrors
run: |
# Github Actions caching proxy is at times unreliable
# see https://github.com/actions/runner-images/issues/7048
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
- name: Create Build Environment - name: Create Build Environment
run: | run: |
${{matrix.install}}
sudo apt update sudo apt update
${{matrix.install}}
sudo apt install locales-all sudo apt install locales-all
cmake -E make_directory ${{runner.workspace}}/build cmake -E make_directory ${{runner.workspace}}/build

View file

@ -7,15 +7,29 @@ permissions:
jobs: jobs:
build: build:
runs-on: macos-10.15
strategy: strategy:
matrix: matrix:
os: [macos-11, macos-13]
build_type: [Debug, Release] build_type: [Debug, Release]
std: [11, 17, 20]
exclude:
- { os: macos-11, std: 20 }
- { os: macos-13, std: 11 }
- { os: macos-13, std: 17 }
include: include:
- shared: -DBUILD_SHARED_LIBS=ON - shared: -DBUILD_SHARED_LIBS=ON
runs-on: '${{ matrix.os }}'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set timezone
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
- name: Select Xcode 14.3 (macOS 13)
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"
if: ${{ matrix.os == 'macos-13' }}
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build run: cmake -E make_directory ${{runner.workspace}}/build
@ -24,6 +38,7 @@ jobs:
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
run: | run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE

View file

@ -0,0 +1,65 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '26 14 * * 5'
push:
branches: [ "master" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
steps:
- name: "Checkout code"
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
publish_results: true
# 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@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
with:
sarif_file: results.sarif

View file

@ -15,27 +15,36 @@ jobs:
# https://github.com/actions/virtual-environments. # https://github.com/actions/virtual-environments.
os: [windows-2019] os: [windows-2019]
platform: [Win32, x64] platform: [Win32, x64]
toolset: [v140, v141, v142]
standard: [14, 17, 20]
shared: ["", -DBUILD_SHARED_LIBS=ON]
build_type: [Debug, Release] build_type: [Debug, Release]
standard: [11, 17, 20] exclude:
- { toolset: v140, standard: 17 }
- { toolset: v140, standard: 20 }
- { toolset: v141, standard: 14 }
- { toolset: v141, standard: 20 }
- { toolset: v142, standard: 14 }
- { platform: Win32, toolset: v140 }
- { platform: Win32, toolset: v141 }
- { platform: Win32, standard: 14 }
- { platform: Win32, standard: 20 }
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
include: include:
- os: windows-2019
platform: Win32
build_type: Debug
shared: -DBUILD_SHARED_LIBS=ON
- os: windows-2022 - os: windows-2022
platform: x64 platform: x64
toolset: v143
build_type: Debug build_type: Debug
standard: 20 standard: 20
exclude:
- os: windows-2019
standard: 11
platform: Win32
- os: windows-2019
standard: 20
platform: Win32
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time"
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build run: cmake -E make_directory ${{runner.workspace}}/build
@ -45,9 +54,9 @@ jobs:
shell: bash shell: bash
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
run: | run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \ cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \
-A ${{matrix.platform}} \
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \ -DCMAKE_CXX_STANDARD=${{matrix.standard}} \
${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
$GITHUB_WORKSPACE $GITHUB_WORKSPACE
- name: Build - name: Build
@ -61,3 +70,31 @@ jobs:
run: ctest -C ${{matrix.build_type}} -V run: ctest -C ${{matrix.build_type}} -V
env: env:
CTEST_OUTPUT_ON_FAILURE: True CTEST_OUTPUT_ON_FAILURE: True
mingw:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
strategy:
matrix:
sys: [ mingw64, ucrt64 ]
steps:
- name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time"
shell: cmd
- uses: msys2/setup-msys2@7efe20baefed56359985e327d329042cde2434ff # v2
with:
release: false
msystem: ${{matrix.sys}}
pacboy: cc:p cmake:p ninja:p lld:p
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Configure
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
env: { LDFLAGS: -fuse-ld=lld }
- name: Build
run: cmake --build ../build
- name: Test
run: ctest -j `nproc` --test-dir ../build
env:
CTEST_OUTPUT_ON_FAILURE: True

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.8...3.26)
# Fallback for using newer policies on CMake <3.12. # Fallback for using newer policies on CMake <3.12.
if(${CMAKE_VERSION} VERSION_LESS 3.12) if(${CMAKE_VERSION} VERSION_LESS 3.12)
@ -24,15 +24,86 @@ function(join result_var)
set(${result_var} "${result}" PARENT_SCOPE) set(${result_var} "${result}" PARENT_SCOPE)
endfunction() endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target) function(enable_module target)
if (MSVC) if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
target_compile_options(${target} target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI} PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI}) INTERFACE /reference fmt=${BMI})
endif ()
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON) set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endif ()
endfunction()
# Adds a library compiled with C++20 module support.
# `enabled` is a CMake variables that specifies if modules are enabled.
# If modules are disabled `add_module_library` falls back to creating a
# non-modular library.
#
# Usage:
# add_module_library(<name> [sources...] FALLBACK [sources...] [IF enabled])
function(add_module_library name)
cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN})
set(sources ${AML_UNPARSED_ARGUMENTS})
add_library(${name})
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX)
if (NOT ${${AML_IF}})
# Create a non-modular library.
target_sources(${name} PRIVATE ${AML_FALLBACK})
return()
endif ()
# Modules require C++20.
target_compile_features(${name} PUBLIC cxx_std_20)
if (CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(${name} PUBLIC -fmodules-ts)
endif ()
# `std` is affected by CMake options and may be higher than C++20.
get_target_property(std ${name} CXX_STANDARD)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(pcms)
foreach (src ${sources})
get_filename_component(pcm ${src} NAME_WE)
set(pcm ${pcm}.pcm)
# Propagate -fmodule-file=*.pcm to targets that link with this library.
target_compile_options(
${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm})
# Use an absolute path to prevent target_link_libraries prepending -l
# to it.
set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm})
add_custom_command(
OUTPUT ${pcm}
COMMAND ${CMAKE_CXX_COMPILER}
-std=c++${std} -x c++-module --precompile -c
-o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src}
"-I$<JOIN:$<TARGET_PROPERTY:${name},INCLUDE_DIRECTORIES>,;-I>"
# Required by the -I generator expression above.
COMMAND_EXPAND_LISTS
DEPENDS ${src})
endforeach ()
# Add .pcm files as sources to make sure they are built before the library.
set(sources)
foreach (pcm ${pcms})
get_filename_component(pcm_we ${pcm} NAME_WE)
set(obj ${pcm_we}.o)
# Use an absolute path to prevent target_link_libraries prepending -l.
set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj})
add_custom_command(
OUTPUT ${obj}
COMMAND ${CMAKE_CXX_COMPILER} $<TARGET_PROPERTY:${name},COMPILE_OPTIONS>
-c -o ${obj} ${pcm}
DEPENDS ${pcm})
endforeach ()
endif ()
target_sources(${name} PRIVATE ${sources})
endfunction() endfunction()
include(CMakeParseArguments) include(CMakeParseArguments)
@ -75,7 +146,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
# Options that control generation of various targets. # Options that control generation of various targets.
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) 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_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
@ -83,16 +154,6 @@ option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
option(FMT_MODULE "Build a module instead of a traditional library." OFF) option(FMT_MODULE "Build a module instead of a traditional library." OFF)
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF) option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
set(FMT_CAN_MODULE OFF)
if (CMAKE_CXX_STANDARD GREATER 17 AND
# msvc 16.10-pre4
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
set(FMT_CAN_MODULE OFF)
endif ()
if (NOT FMT_CAN_MODULE)
set(FMT_MODULE OFF)
message(STATUS "Module support is disabled.")
endif ()
if (FMT_TEST AND FMT_MODULE) if (FMT_TEST AND FMT_MODULE)
# The tests require {fmt} to be compiled as traditional library # The tests require {fmt} to be compiled as traditional library
message(STATUS "Testing is incompatible with build mode 'module'.") message(STATUS "Testing is incompatible with build mode 'module'.")
@ -101,6 +162,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
if (FMT_SYSTEM_HEADERS) if (FMT_SYSTEM_HEADERS)
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
endif () endif ()
if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
set(FMT_TEST OFF)
message(STATUS "MSDOS is incompatible with gtest")
endif()
# Get version from core.h # Get version from core.h
file(READ include/fmt/core.h core_h) file(READ include/fmt/core.h core_h)
@ -118,23 +183,15 @@ message(STATUS "Version: ${FMT_VERSION}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
endif () endif ()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
include(cxx14) include(CheckCXXCompilerFlag)
include(JoinPaths) include(JoinPaths)
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
if (${index} GREATER -1)
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
# compatibility with older CMake versions.
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
endif ()
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
"Preset for the export of private symbols") "Preset for the export of private symbols")
@ -220,16 +277,18 @@ endfunction()
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
format-inl.h os.h ostream.h printf.h ranges.h std.h format-inl.h os.h ostream.h printf.h ranges.h std.h
xchar.h) xchar.h)
if (FMT_MODULE)
set(FMT_SOURCES src/fmt.cc)
elseif (FMT_OS)
set(FMT_SOURCES src/format.cc src/os.cc)
else()
set(FMT_SOURCES src/format.cc) set(FMT_SOURCES src/format.cc)
if (FMT_OS)
set(FMT_SOURCES ${FMT_SOURCES} src/os.cc)
endif () endif ()
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) add_module_library(fmt src/fmt.cc FALLBACK
${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst
IF FMT_MODULE)
add_library(fmt::fmt ALIAS fmt) add_library(fmt::fmt ALIAS fmt)
if (FMT_MODULE)
enable_module(fmt)
endif ()
if (FMT_WERROR) if (FMT_WERROR)
target_compile_options(fmt PRIVATE ${WERROR_FLAG}) target_compile_options(fmt PRIVATE ${WERROR_FLAG})
@ -237,11 +296,12 @@ endif ()
if (FMT_PEDANTIC) if (FMT_PEDANTIC)
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
endif () endif ()
if (FMT_MODULE)
enable_module(fmt)
endif ()
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES)
target_compile_features(fmt PUBLIC cxx_std_11)
else ()
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
endif ()
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
@ -262,13 +322,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
endif () endif ()
if (BUILD_SHARED_LIBS) if (BUILD_SHARED_LIBS)
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED)
NOT EMSCRIPTEN)
# Fix rpmlint warning:
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
target_link_libraries(fmt -Wl,--as-needed)
endif ()
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
endif () endif ()
if (FMT_SAFE_DURATION_CAST) if (FMT_SAFE_DURATION_CAST)
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST) target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
@ -278,7 +332,7 @@ add_library(fmt-header-only INTERFACE)
add_library(fmt::fmt-header-only ALIAS fmt-header-only) add_library(fmt::fmt-header-only ALIAS fmt-header-only)
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) target_compile_features(fmt-header-only INTERFACE cxx_std_11)
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
@ -300,7 +354,7 @@ if (FMT_INSTALL)
"Installation directory for libraries, a relative path that " "Installation directory for libraries, a relative path that "
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.") "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING
"Installation directory for pkgconfig (.pc) files, a relative " "Installation directory for pkgconfig (.pc) files, a relative "
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an " "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
"absolute path.") "absolute path.")
@ -330,7 +384,6 @@ if (FMT_INSTALL)
LIBRARY DESTINATION ${FMT_LIB_DIR} LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR}
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt" PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
FRAMEWORK DESTINATION "."
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# Use a namespace because CMake provides better diagnostics for namespaced # Use a namespace because CMake provides better diagnostics for namespaced
@ -345,8 +398,6 @@ if (FMT_INSTALL)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::) NAMESPACE fmt::)
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
endif () endif ()

View file

@ -1,3 +1,796 @@
10.1.1 - 2023-08-26
-------------------
* Added formatters for ``std::atomic`` and ``atomic_flag``
(`#3574 <https://github.com/fmtlib/fmt/pull/3574>`_,
`#3594 <https://github.com/fmtlib/fmt/pull/3594>`_).
Thanks `@wangzw (Zhanwei Wang) <https://github.com/wangzw>`_ and
`@AlexGuteniev (Alex Guteniev) <https://github.com/AlexGuteniev>`_.
* Fixed an error about partial specialization of ``formatter<string>``
after instantiation when compiled with gcc and C++20
(`#3584 <https://github.com/fmtlib/fmt/issues/3584>`_).
* Fixed compilation as a C++20 module with gcc and clang
(`#3587 <https://github.com/fmtlib/fmt/issues/3587>`_,
`#3597 <https://github.com/fmtlib/fmt/pull/3597>`_,
`#3605 <https://github.com/fmtlib/fmt/pull/3605>`_). Thanks
`@MathewBensonCode (Mathew Benson) <https://github.com/MathewBensonCode>`_.
* Made ``fmt::to_string`` work with types that have ``format_as`` overloads
(`#3575 <https://github.com/fmtlib/fmt/pull/3575>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Made ``formatted_size`` work with integral format specifiers at compile time
(`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_).
Thanks `@elbeno (Ben Deane) <https://github.com/elbeno>`_.
* Fixed a warning about the ``no_unique_address`` attribute on clang-cl
(`#3599 <https://github.com/fmtlib/fmt/pull/3599>`_).
Thanks `@lukester1975 <https://github.com/lukester1975>`_.
* Improved compatibility with the legacy GBK encoding
(`#3598 <https://github.com/fmtlib/fmt/issues/3598>`_,
`#3599 <https://github.com/fmtlib/fmt/pull/3599>`_).
Thanks `@YuHuanTin <https://github.com/YuHuanTin>`_.
* Added OpenSSF Scorecard analysis
(`#3530 <https://github.com/fmtlib/fmt/issues/3530>`_,
`#3571 <https://github.com/fmtlib/fmt/pull/3571>`_).
Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_.
* Updated CI dependencies
(`#3591 <https://github.com/fmtlib/fmt/pull/3591>`_,
`#3592 <https://github.com/fmtlib/fmt/pull/3592>`_,
`#3593 <https://github.com/fmtlib/fmt/pull/3593>`_,
`#3602 <https://github.com/fmtlib/fmt/pull/3602>`_).
10.1.0 - 2023-08-12
-------------------
* Optimized format string compilation resulting in up to 40% speed up in
compiled ``format_to`` and ~4x speed up in compiled ``format_to_n`` on a
concatenation benchmark (`#3133 <https://github.com/fmtlib/fmt/issues/3133>`_,
`#3484 <https://github.com/fmtlib/fmt/issues/3484>`_).
{fmt} 10.0::
---------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------
BM_format_to 78.9 ns 78.9 ns 8881746
BM_format_to_n 568 ns 568 ns 1232089
{fmt} 10.1::
---------------------------------------------------------
Benchmark Time CPU Iterations
---------------------------------------------------------
BM_format_to 54.9 ns 54.9 ns 12727944
BM_format_to_n 133 ns 133 ns 5257795
* Optimized storage of an empty allocator in ``basic_memory_buffer``
(`#3485 <https://github.com/fmtlib/fmt/pull/3485>`_).
Thanks `@Minty-Meeo <https://github.com/Minty-Meeo>`_.
* Added formatters for proxy references to elements of ``std::vector<bool>`` and
``std::bitset<N>`` (`#3567 <https://github.com/fmtlib/fmt/issues/3567>`_,
`#3570 <https://github.com/fmtlib/fmt/pull/3570>`_).
For example (`godbolt <https://godbolt.org/z/zYb79Pvn8>`__):
.. code:: c++
#include <vector>
#include <fmt/std.h>
int main() {
auto v = std::vector<bool>{true};
fmt::print("{}", v[0]);
}
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
`@felix642 (Félix-Antoine Constantin) <https://github.com/felix642>`_.
* Fixed an ambiguous formatter specialization for containers that look like
container adaptors such as ``boost::flat_set``
(`#3556 <https://github.com/fmtlib/fmt/issues/3556>`_,
`#3561 <https://github.com/fmtlib/fmt/pull/3561>`_).
Thanks `@5chmidti <https://github.com/5chmidti>`_.
* Fixed compilation when formatting durations not convertible from
``std::chrono::seconds`` (`#3430 <https://github.com/fmtlib/fmt/pull/3430>`_).
Thanks `@patlkli (Patrick Geltinger) <https://github.com/patlkli>`_.
* Made the ``formatter`` specialization for ``char*`` const-correct
(`#3432 <https://github.com/fmtlib/fmt/pull/3432>`_).
Thanks `@timsong-cpp <https://github.com/timsong-cpp>`_.
* Made ``{}`` and ``{:}`` handled consistently during compile-time checks
(`#3526 <https://github.com/fmtlib/fmt/issues/3526>`_).
* Disallowed passing temporaries to ``make_format_args`` to improve API safety
by preventing dangling references.
* Improved the compile-time error for unformattable types
(`#3478 <https://github.com/fmtlib/fmt/pull/3478>`_).
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Improved the floating-point formatter
(`#3448 <https://github.com/fmtlib/fmt/pull/3448>`_,
`#3450 <https://github.com/fmtlib/fmt/pull/3450>`_).
Thanks `@florimond-collette (Florimond Collette)
<https://github.com/florimond-collette>`_.
* Fixed handling of precision for ``long double`` larger than 64 bits.
(`#3539 <https://github.com/fmtlib/fmt/issues/3539>`_,
`#3564 <https://github.com/fmtlib/fmt/issues/3564>`_).
* Made floating-point and chrono tests less platform-dependent
(`#3337 <https://github.com/fmtlib/fmt/issues/3337>`_,
`#3433 <https://github.com/fmtlib/fmt/issues/3433>`_,
`#3434 <https://github.com/fmtlib/fmt/pull/3434>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Removed the remnants of the Grisu floating-point formatter that has been
replaced by Dragonbox in earlier versions.
* Added ``throw_format_error`` to the public API
(`#3551 <https://github.com/fmtlib/fmt/pull/3551>`_).
Thanks `@mjerabek (Martin Jeřábek) <https://github.com/mjerabek>`_.
* Made ``FMT_THROW`` assert even if assertions are disabled when compiling with
exceptions disabled (`#3418 <https://github.com/fmtlib/fmt/issues/3418>`_,
`#3439 <https://github.com/fmtlib/fmt/pull/3439>`_).
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Made ``format_as`` and ``std::filesystem::path`` formatter work with exotic
code unit types.
(`#3457 <https://github.com/fmtlib/fmt/pull/3457>`_,
`#3476 <https://github.com/fmtlib/fmt/pull/3476>`_).
Thanks `@gix (Nico Rieck) <https://github.com/gix>`_,
`@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_.
* Added support for the ``?`` format specifier to ``std::filesystem::path`` and
made the default unescaped for consistency with strings.
* Deprecated the wide stream overload of ``printf``.
* Removed unused ``basic_printf_parse_context``.
* Improved RTTI detection used when formatting exceptions
(`#3468 <https://github.com/fmtlib/fmt/pull/3468>`_).
Thanks `@danakj (Dana Jansens) <https://github.com/danakj>`_.
* Improved compatibility with VxWorks7
(`#3467 <https://github.com/fmtlib/fmt/pull/3467>`_).
Thanks `@wenshan1 (Bin Lan) <https://github.com/wenshan1>`_.
* Improved documentation
(`#3174 <https://github.com/fmtlib/fmt/issues/3174>`_,
`#3423 <https://github.com/fmtlib/fmt/issues/3423>`_,
`#3454 <https://github.com/fmtlib/fmt/pull/3454>`_,
`#3458 <https://github.com/fmtlib/fmt/issues/3458>`_,
`#3461 <https://github.com/fmtlib/fmt/pull/3461>`_,
`#3487 <https://github.com/fmtlib/fmt/issues/3487>`_,
`#3515 <https://github.com/fmtlib/fmt/pull/3515>`_).
Thanks `@zencatalyst (Kasra Hashemi) <https://github.com/zencatalyst>`_,
`@rlalik <https://github.com/rlalik>`_,
`@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_.
* Improved build and CI configurations
(`#3449 <https://github.com/fmtlib/fmt/issues/3449>`_,
`#3451 <https://github.com/fmtlib/fmt/pull/3451>`_,
`#3452 <https://github.com/fmtlib/fmt/pull/3452>`_,
`#3453 <https://github.com/fmtlib/fmt/pull/3453>`_,
`#3459 <https://github.com/fmtlib/fmt/pull/3459>`_,
`#3481 <https://github.com/fmtlib/fmt/issues/3481>`_,
`#3486 <https://github.com/fmtlib/fmt/pull/3486>`_,
`#3489 <https://github.com/fmtlib/fmt/issues/3489>`_,
`#3496 <https://github.com/fmtlib/fmt/pull/3496>`_,
`#3517 <https://github.com/fmtlib/fmt/issues/3517>`_,
`#3523 <https://github.com/fmtlib/fmt/pull/3523>`_,
`#3563 <https://github.com/fmtlib/fmt/pull/3563>`_).
Thanks `@joycebrum (Joyce) <https://github.com/joycebrum>`_,
`@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@petrmanek (Petr Mánek) <https://github.com/petrmanek>`_,
`@setoye (Alta) <https://github.com/setoye>`_,
`@abouvier (Alexandre Bouvier) <https://github.com/abouvier>`_.
* Fixed various warnings and compilation issues
(`#3408 <https://github.com/fmtlib/fmt/issues/3408>`_,
`#3424 <https://github.com/fmtlib/fmt/issues/3424>`_,
`#3444 <https://github.com/fmtlib/fmt/issues/3444>`_,
`#3446 <https://github.com/fmtlib/fmt/pull/3446>`_,
`#3475 <https://github.com/fmtlib/fmt/pull/3475>`_,
`#3482 <https://github.com/fmtlib/fmt/pull/3482>`_,
`#3492 <https://github.com/fmtlib/fmt/issues/3492>`_,
`#3493 <https://github.com/fmtlib/fmt/pull/3493>`_,
`#3508 <https://github.com/fmtlib/fmt/pull/3508>`_,
`#3509 <https://github.com/fmtlib/fmt/issues/3509>`_,
`#3533 <https://github.com/fmtlib/fmt/issues/3533>`_,
`#3542 <https://github.com/fmtlib/fmt/pull/3542>`_,
`#3543 <https://github.com/fmtlib/fmt/issues/3543>`_,
`#3540 <https://github.com/fmtlib/fmt/issues/3540>`_,
`#3544 <https://github.com/fmtlib/fmt/pull/3544>`_,
`#3548 <https://github.com/fmtlib/fmt/issues/3548>`_,
`#3549 <https://github.com/fmtlib/fmt/pull/3549>`_,
`#3550 <https://github.com/fmtlib/fmt/pull/3550>`_,
`#3552 <https://github.com/fmtlib/fmt/pull/3552>`_).
Thanks `@adesitter (Arnaud Desitter) <https://github.com/adesitter>`_,
`@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_,
`@Minty-Meeo <https://github.com/Minty-Meeo>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@TobiSchluter (Tobias Schlüter) <https://github.com/TobiSchluter>`_,
`@kieranclancy (Kieran Clancy) <https://github.com/kieranclancy>`_,
`@alexeedm (Dmitry Alexeev) <https://github.com/alexeedm>`_,
`@jurihock (Jürgen Hock) <https://github.com/jurihock>`_,
`@Ozomahtli <https://github.com/Ozomahtli>`_,
`@razaqq <https://github.com/razaqq>`_.
10.0.0 - 2023-05-09
-------------------
* Replaced Grisu with a new floating-point formatting algorithm for given
precision (`#3262 <https://github.com/fmtlib/fmt/issues/3262>`_,
`#2750 <https://github.com/fmtlib/fmt/issues/2750>`_,
`#3269 <https://github.com/fmtlib/fmt/pull/3269>`_,
`#3276 <https://github.com/fmtlib/fmt/pull/3276>`_).
The new algorithm is based on Dragonbox already used for the
shortest representation and gives substantial performance improvement:
.. image:: https://user-images.githubusercontent.com/33922675/
211956670-84891a09-6867-47d9-82fc-3230da7abe0f.png
* Red: new algorithm
* Green: new algorithm with ``FMT_USE_FULL_CACHE_DRAGONBOX`` defined to 1
* Blue: old algorithm
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
* Replaced ``snprintf``-based hex float formatter with an internal
implementation (`#3179 <https://github.com/fmtlib/fmt/pull/3179>`_,
`#3203 <https://github.com/fmtlib/fmt/pull/3203>`_).
This removes the last usage of ``s(n)printf`` in {fmt}.
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Fixed alignment of floating-point numbers with localization
(`#3263 <https://github.com/fmtlib/fmt/issues/3263>`_,
`#3272 <https://github.com/fmtlib/fmt/pull/3272>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Made handling of ``#`` consistent with ``std::format``.
* Improved C++20 module support
(`#3134 <https://github.com/fmtlib/fmt/pull/3134>`_,
`#3254 <https://github.com/fmtlib/fmt/pull/3254>`_,
`#3386 <https://github.com/fmtlib/fmt/pull/3386>`_,
`#3387 <https://github.com/fmtlib/fmt/pull/3387>`_,
`#3388 <https://github.com/fmtlib/fmt/pull/3388>`_,
`#3392 <https://github.com/fmtlib/fmt/pull/3392>`_,
`#3397 <https://github.com/fmtlib/fmt/pull/3397>`_,
`#3399 <https://github.com/fmtlib/fmt/pull/3399>`_,
`#3400 <https://github.com/fmtlib/fmt/pull/3400>`_).
Thanks `@laitingsheng (Tinson Lai) <https://github.com/laitingsheng>`_,
`@Orvid (Orvid King) <https://github.com/Orvid>`_,
`@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
Switched to the `modules CMake library <https://github.com/vitaut/modules>`_
which allows building {fmt} as a C++20 module with clang::
CXX=clang++ cmake -DFMT_MODULE=ON .
make
* Made ``format_as`` work with any user-defined type and not just enums.
For example (`godbolt <https://godbolt.org/z/b7rqhq5Kh>`__):
.. code:: c++
#include <fmt/format.h>
struct floaty_mc_floatface {
double value;
};
auto format_as(floaty_mc_floatface f) { return f.value; }
int main() {
fmt::print("{:8}\n", floaty_mc_floatface{0.42}); // prints " 0.42"
}
* Removed deprecated implicit conversions for enums and conversions to primitive
types for compatibility with ``std::format`` and to prevent potential ODR
violations. Use ``format_as`` instead.
* Added support for fill, align and width to the time point formatter
(`#3237 <https://github.com/fmtlib/fmt/issues/3237>`_,
`#3260 <https://github.com/fmtlib/fmt/pull/3260>`_,
`#3275 <https://github.com/fmtlib/fmt/pull/3275>`_).
For example (`godbolt <https://godbolt.org/z/rKP6MGz6c>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
// prints " 2023"
fmt::print("{:>8%Y}\n", std::chrono::system_clock::now());
}
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Implemented formatting of subseconds
(`#2207 <https://github.com/fmtlib/fmt/issues/2207>`_,
`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
`#3115 <https://github.com/fmtlib/fmt/pull/3115>`_,
`#3143 <https://github.com/fmtlib/fmt/pull/3143>`_,
`#3144 <https://github.com/fmtlib/fmt/pull/3144>`_,
`#3349 <https://github.com/fmtlib/fmt/pull/3349>`_).
For example (`godbolt <https://godbolt.org/z/45738oGEo>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
// prints 01.234567
fmt::print("{:%S}\n", std::chrono::microseconds(1234567));
}
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Added precision support to ``%S``
(`#3148 <https://github.com/fmtlib/fmt/pull/3148>`_).
Thanks `@SappyJoy (Stepan Ponomaryov) <https://github.com/SappyJoy>`_
* Added support for ``std::utc_time``
(`#3098 <https://github.com/fmtlib/fmt/issues/3098>`_,
`#3110 <https://github.com/fmtlib/fmt/pull/3110>`_).
Thanks `@patrickroocks (Patrick Roocks) <https://github.com/patrickroocks>`_.
* Switched formatting of ``std::chrono::system_clock`` from local time to UTC
for compatibility with the standard
(`#3199 <https://github.com/fmtlib/fmt/issues/3199>`_,
`#3230 <https://github.com/fmtlib/fmt/pull/3230>`_).
Thanks `@ned14 (Niall Douglas) <https://github.com/ned14>`_.
* Added support for ``%Ez`` and ``%Oz`` to chrono formatters.
(`#3220 <https://github.com/fmtlib/fmt/issues/3220>`_,
`#3222 <https://github.com/fmtlib/fmt/pull/3222>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Improved validation of format specifiers for ``std::chrono::duration``
(`#3219 <https://github.com/fmtlib/fmt/issues/3219>`_,
`#3232 <https://github.com/fmtlib/fmt/pull/3232>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Fixed formatting of time points before the epoch
(`#3117 <https://github.com/fmtlib/fmt/issues/3117>`_,
`#3261 <https://github.com/fmtlib/fmt/pull/3261>`_).
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
.. code:: c++
#include <fmt/chrono.h>
int main() {
auto t = std::chrono::system_clock::from_time_t(0) -
std::chrono::milliseconds(250);
fmt::print("{:%S}\n", t); // prints 59.750000000
}
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Experimental: implemented glibc extension for padding seconds, minutes and
hours (`#2959 <https://github.com/fmtlib/fmt/issues/2959>`_,
`#3271 <https://github.com/fmtlib/fmt/pull/3271>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Added a formatter for ``std::exception``
(`#2977 <https://github.com/fmtlib/fmt/issues/2977>`_,
`#3012 <https://github.com/fmtlib/fmt/issues/3012>`_,
`#3062 <https://github.com/fmtlib/fmt/pull/3062>`_,
`#3076 <https://github.com/fmtlib/fmt/pull/3076>`_,
`#3119 <https://github.com/fmtlib/fmt/pull/3119>`_).
For example (`godbolt <https://godbolt.org/z/8xoWGs9e4>`__):
.. code:: c++
#include <fmt/std.h>
#include <vector>
int main() {
try {
std::vector<bool>().at(0);
} catch(const std::exception& e) {
fmt::print("{}", e);
}
}
prints::
vector<bool>::_M_range_check: __n (which is 0) >= this->size() (which is 0)
on libstdc++.
Thanks `@zach2good (Zach Toogood) <https://github.com/zach2good>`_ and
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Moved ``std::error_code`` formatter from ``fmt/os.h`` to ``fmt/std.h``.
(`#3125 <https://github.com/fmtlib/fmt/pull/3125>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added formatters for standard container adapters: ``std::priority_queue``,
``std::queue`` and ``std::stack``
(`#3215 <https://github.com/fmtlib/fmt/issues/3215>`_,
`#3279 <https://github.com/fmtlib/fmt/pull/3279>`_).
For example (`godbolt <https://godbolt.org/z/74h1xY9qK>`__):
.. code:: c++
#include <fmt/ranges.h>
#include <stack>
#include <vector>
int main() {
auto s = std::stack<bool, std::vector<bool>>();
for (auto b: {true, false, true}) s.push(b);
fmt::print("{}\n", s); // prints [true, false, true]
}
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Added a formatter for ``std::optional`` to ``fmt/std.h``.
Thanks `@tom-huntington <https://github.com/tom-huntington>`_.
* Fixed formatting of valueless by exception variants
(`#3347 <https://github.com/fmtlib/fmt/pull/3347>`_).
Thanks `@TheOmegaCarrot <https://github.com/TheOmegaCarrot>`_.
* Made ``fmt::ptr`` accept ``unique_ptr`` with a custom deleter
(`#3177 <https://github.com/fmtlib/fmt/pull/3177>`_).
Thanks `@hmbj (Hans-Martin B. Jensen) <https://github.com/hmbj>`_.
* Fixed formatting of noncopyable ranges and nested ranges of chars
(`#3158 <https://github.com/fmtlib/fmt/pull/3158>`_
`#3286 <https://github.com/fmtlib/fmt/issues/3286>`_,
`#3290 <https://github.com/fmtlib/fmt/pull/3290>`_).
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Fixed issues with formatting of paths and ranges of paths
(`#3319 <https://github.com/fmtlib/fmt/issues/3319>`_,
`#3321 <https://github.com/fmtlib/fmt/pull/3321>`_
`#3322 <https://github.com/fmtlib/fmt/issues/3322>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Improved handling of invalid Unicode in paths.
* Enabled compile-time checks on Apple clang 14 and later
(`#3331 <https://github.com/fmtlib/fmt/pull/3331>`_).
Thanks `@cloyce (Cloyce D. Spradling) <https://github.com/cloyce>`_.
* Improved compile-time checks of named arguments
(`#3105 <https://github.com/fmtlib/fmt/issues/3105>`_,
`#3214 <https://github.com/fmtlib/fmt/pull/3214>`_).
Thanks `@rbrich (Radek Brich) <https://github.com/rbrich>`_.
* Fixed formatting when both alignment and ``0`` are given
(`#3236 <https://github.com/fmtlib/fmt/issues/3236>`_,
`#3248 <https://github.com/fmtlib/fmt/pull/3248>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Improved Unicode support in the experimental file API on Windows
(`#3234 <https://github.com/fmtlib/fmt/issues/3234>`_,
`#3293 <https://github.com/fmtlib/fmt/pull/3293>`_).
Thanks `@Fros1er (Froster) <https://github.com/Fros1er>`_.
* Unified UTF transcoding
(`#3416 <https://github.com/fmtlib/fmt/pull/3416>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added support for UTF-8 digit separators via an experimental locale facet
(`#1861 <https://github.com/fmtlib/fmt/issues/1861>`_).
For example (`godbolt <https://godbolt.org/z/f7bcznb3W>`__):
.. code:: c++
auto loc = std::locale(
std::locale(), new fmt::format_facet<std::locale>(""));
auto s = fmt::format(loc, "{:L}", 1000);
where ```` is U+2019 used as a digit separator in the de_CH locale.
* Added an overload of ``formatted_size`` that takes a locale
(`#3084 <https://github.com/fmtlib/fmt/issues/3084>`_,
`#3087 <https://github.com/fmtlib/fmt/pull/3087>`_).
Thanks `@gerboengels <https://github.com/gerboengels>`_.
* Removed the deprecated ``FMT_DEPRECATED_OSTREAM``.
* Fixed a UB when using a null ``std::string_view`` with ``fmt::to_string``
or format string compilation
(`#3241 <https://github.com/fmtlib/fmt/issues/3241>`_,
`#3244 <https://github.com/fmtlib/fmt/pull/3244>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added ``starts_with`` to the fallback ``string_view`` implementation
(`#3080 <https://github.com/fmtlib/fmt/pull/3080>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added ``fmt::basic_format_string::get()`` for compatibility with
``basic_format_string`` (`#3111 <https://github.com/fmtlib/fmt/pull/3111>`_).
Thanks `@huangqinjin <https://github.com/huangqinjin>`_.
* Added ``println`` for compatibility with C++23
(`#3267 <https://github.com/fmtlib/fmt/pull/3267>`_).
Thanks `@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_.
* Renamed the ``FMT_EXPORT`` macro for shared library usage to
``FMT_LIB_EXPORT``.
* Improved documentation
(`#3108 <https://github.com/fmtlib/fmt/issues/3108>`_,
`#3169 <https://github.com/fmtlib/fmt/issues/3169>`_,
`#3243 <https://github.com/fmtlib/fmt/pull/3243>`_).
`#3404 <https://github.com/fmtlib/fmt/pull/3404>`_).
Thanks `@Cleroth <https://github.com/Cleroth>`_ and
`@Vertexwahn <https://github.com/Vertexwahn>`_.
* Improved build configuration and tests
(`#3118 <https://github.com/fmtlib/fmt/pull/3118>`_,
`#3120 <https://github.com/fmtlib/fmt/pull/3120>`_,
`#3188 <https://github.com/fmtlib/fmt/pull/3188>`_,
`#3189 <https://github.com/fmtlib/fmt/issues/3189>`_,
`#3198 <https://github.com/fmtlib/fmt/pull/3198>`_,
`#3205 <https://github.com/fmtlib/fmt/pull/3205>`_,
`#3207 <https://github.com/fmtlib/fmt/pull/3207>`_,
`#3210 <https://github.com/fmtlib/fmt/pull/3210>`_,
`#3240 <https://github.com/fmtlib/fmt/pull/3240>`_,
`#3256 <https://github.com/fmtlib/fmt/pull/3256>`_,
`#3264 <https://github.com/fmtlib/fmt/pull/3264>`_,
`#3299 <https://github.com/fmtlib/fmt/issues/3299>`_,
`#3302 <https://github.com/fmtlib/fmt/pull/3302>`_,
`#3312 <https://github.com/fmtlib/fmt/pull/3312>`_,
`#3317 <https://github.com/fmtlib/fmt/issues/3317>`_,
`#3328 <https://github.com/fmtlib/fmt/pull/3328>`_,
`#3333 <https://github.com/fmtlib/fmt/pull/3333>`_,
`#3369 <https://github.com/fmtlib/fmt/pull/3369>`_,
`#3373 <https://github.com/fmtlib/fmt/issues/3373>`_,
`#3395 <https://github.com/fmtlib/fmt/pull/3395>`_,
`#3406 <https://github.com/fmtlib/fmt/pull/3406>`_,
`#3411 <https://github.com/fmtlib/fmt/pull/3411>`_).
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@DavidKorczynski <https://github.com/DavidKorczynski>`_,
`@ChrisThrasher (Chris Thrasher) <https://github.com/ChrisThrasher>`_,
`@FrancoisCarouge (François Carouge) <https://github.com/FrancoisCarouge>`_,
`@kennyweiss (Kenny Weiss) <https://github.com/kennyweiss>`_,
`@luzpaz <https://github.com/luzpaz>`_,
`@codeinred (Alecto Irene Perez) <https://github.com/codeinred>`_,
`@Mixaill (Mikhail Paulyshka) <https://github.com/Mixaill>`_,
`@joycebrum (Joyce) <https://github.com/joycebrum>`_,
`@kevinhwang (Kevin Hwang) <https://github.com/kevinhwang>`_,
`@Vertexwahn <https://github.com/Vertexwahn>`_.
* Fixed a regression in handling empty format specifiers after a colon (``{:}``)
(`#3086 <https://github.com/fmtlib/fmt/pull/3086>`_).
Thanks `@oxidase (Michael Krasnyk) <https://github.com/oxidase>`_.
* Worked around a broken implementation of ``std::is_constant_evaluated`` in
some versions of libstdc++ on clang
(`#3247 <https://github.com/fmtlib/fmt/issues/3247>`_,
`#3281 <https://github.com/fmtlib/fmt/pull/3281>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Fixed formatting of volatile variables
(`#3068 <https://github.com/fmtlib/fmt/pull/3068>`_).
* Fixed various warnings and compilation issues
(`#3057 <https://github.com/fmtlib/fmt/pull/3057>`_,
`#3066 <https://github.com/fmtlib/fmt/pull/3066>`_,
`#3072 <https://github.com/fmtlib/fmt/pull/3072>`_,
`#3082 <https://github.com/fmtlib/fmt/pull/3082>`_,
`#3091 <https://github.com/fmtlib/fmt/pull/3091>`_,
`#3092 <https://github.com/fmtlib/fmt/issues/3092>`_,
`#3093 <https://github.com/fmtlib/fmt/pull/3093>`_,
`#3095 <https://github.com/fmtlib/fmt/pull/3095>`_,
`#3096 <https://github.com/fmtlib/fmt/issues/3096>`_,
`#3097 <https://github.com/fmtlib/fmt/pull/3097>`_,
`#3128 <https://github.com/fmtlib/fmt/issues/3128>`_,
`#3129 <https://github.com/fmtlib/fmt/pull/3129>`_,
`#3137 <https://github.com/fmtlib/fmt/pull/3137>`_,
`#3139 <https://github.com/fmtlib/fmt/pull/3139>`_,
`#3140 <https://github.com/fmtlib/fmt/issues/3140>`_,
`#3142 <https://github.com/fmtlib/fmt/pull/3142>`_,
`#3149 <https://github.com/fmtlib/fmt/issues/3149>`_,
`#3150 <https://github.com/fmtlib/fmt/pull/3150>`_,
`#3154 <https://github.com/fmtlib/fmt/issues/3154>`_,
`#3163 <https://github.com/fmtlib/fmt/issues/3163>`_,
`#3178 <https://github.com/fmtlib/fmt/issues/3178>`_,
`#3184 <https://github.com/fmtlib/fmt/pull/3184>`_,
`#3196 <https://github.com/fmtlib/fmt/pull/3196>`_,
`#3204 <https://github.com/fmtlib/fmt/issues/3204>`_,
`#3206 <https://github.com/fmtlib/fmt/pull/3206>`_,
`#3208 <https://github.com/fmtlib/fmt/pull/3208>`_,
`#3213 <https://github.com/fmtlib/fmt/issues/3213>`_,
`#3216 <https://github.com/fmtlib/fmt/pull/3216>`_,
`#3224 <https://github.com/fmtlib/fmt/issues/3224>`_,
`#3226 <https://github.com/fmtlib/fmt/issues/3226>`_,
`#3228 <https://github.com/fmtlib/fmt/issues/3228>`_,
`#3229 <https://github.com/fmtlib/fmt/pull/3229>`_,
`#3259 <https://github.com/fmtlib/fmt/pull/3259>`_,
`#3274 <https://github.com/fmtlib/fmt/issues/3274>`_,
`#3287 <https://github.com/fmtlib/fmt/issues/3287>`_,
`#3288 <https://github.com/fmtlib/fmt/pull/3288>`_,
`#3292 <https://github.com/fmtlib/fmt/issues/3292>`_,
`#3295 <https://github.com/fmtlib/fmt/pull/3295>`_,
`#3296 <https://github.com/fmtlib/fmt/pull/3296>`_,
`#3298 <https://github.com/fmtlib/fmt/issues/3298>`_,
`#3325 <https://github.com/fmtlib/fmt/issues/3325>`_,
`#3326 <https://github.com/fmtlib/fmt/pull/3326>`_,
`#3334 <https://github.com/fmtlib/fmt/issues/3334>`_,
`#3342 <https://github.com/fmtlib/fmt/issues/3342>`_,
`#3343 <https://github.com/fmtlib/fmt/pull/3343>`_,
`#3351 <https://github.com/fmtlib/fmt/issues/3351>`_,
`#3352 <https://github.com/fmtlib/fmt/pull/3352>`_,
`#3362 <https://github.com/fmtlib/fmt/pull/3362>`_,
`#3365 <https://github.com/fmtlib/fmt/issues/3365>`_,
`#3366 <https://github.com/fmtlib/fmt/pull/3366>`_,
`#3374 <https://github.com/fmtlib/fmt/pull/3374>`_,
`#3377 <https://github.com/fmtlib/fmt/issues/3377>`_,
`#3378 <https://github.com/fmtlib/fmt/pull/3378>`_,
`#3381 <https://github.com/fmtlib/fmt/issues/3381>`_,
`#3398 <https://github.com/fmtlib/fmt/pull/3398>`_,
`#3413 <https://github.com/fmtlib/fmt/pull/3413>`_,
`#3415 <https://github.com/fmtlib/fmt/issues/3415>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_,
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
`@NewbieOrange <https://github.com/NewbieOrange>`_,
`@EngineLessCC (VivyaCC) <https://github.com/EngineLessCC>`_,
`@asmaloney (Andy Maloney) <https://github.com/asmaloney>`_,
`@HazardyKnusperkeks (Björn Schäpers)
<https://github.com/HazardyKnusperkeks>`_,
`@sergiud (Sergiu Deitsch) <https://github.com/sergiud>`_,
`@Youw (Ihor Dutchak) <https://github.com/Youw>`_,
`@thesmurph <https://github.com/thesmurph>`_,
`@czudziakm (Maksymilian Czudziak) <https://github.com/czudziakm>`_,
`@Roman-Koshelev <https://github.com/Roman-Koshelev>`_,
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
`@ShawnZhong (Shawn Zhong) <https://github.com/ShawnZhong>`_,
`@russelltg (Russell Greene) <https://github.com/russelltg>`_,
`@glebm (Gleb Mazovetskiy) <https://github.com/glebm>`_,
`@tmartin-gh <https://github.com/tmartin-gh>`_,
`@Zhaojun-Liu (June Liu) <https://github.com/Zhaojun-Liu>`_,
`@louiswins (Louis Wilson) <https://github.com/louiswins>`_,
`@mogemimi <https://github.com/mogemimi>`_.
9.1.0 - 2022-08-27
------------------
* ``fmt::formatted_size`` now works at compile time
(`#3026 <https://github.com/fmtlib/fmt/pull/3026>`_). For example
(`godbolt <https://godbolt.org/z/1MW5rMdf8>`__):
.. code:: c++
#include <fmt/compile.h>
int main() {
using namespace fmt::literals;
constexpr size_t n = fmt::formatted_size("{}"_cf, 42);
fmt::print("{}\n", n); // prints 2
}
Thanks `@marksantaniello (Mark Santaniello)
<https://github.com/marksantaniello>`_.
* Fixed handling of invalid UTF-8
(`#3038 <https://github.com/fmtlib/fmt/pull/3038>`_,
`#3044 <https://github.com/fmtlib/fmt/pull/3044>`_,
`#3056 <https://github.com/fmtlib/fmt/pull/3056>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_ and
`@skeeto (Christopher Wellons) <https://github.com/skeeto>`_.
* Improved Unicode support in ``ostream`` overloads of ``print``
(`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_,
`#3001 <https://github.com/fmtlib/fmt/pull/3001>`_,
`#3025 <https://github.com/fmtlib/fmt/pull/3025>`_).
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_.
* Fixed handling of the sign specifier in localized formatting on systems with
32-bit ``wchar_t`` (`#3041 <https://github.com/fmtlib/fmt/issues/3041>`_).
* Added support for wide streams to ``fmt::streamed``
(`#2994 <https://github.com/fmtlib/fmt/pull/2994>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Added the ``n`` specifier that disables the output of delimiters when
formatting ranges (`#2981 <https://github.com/fmtlib/fmt/pull/2981>`_,
`#2983 <https://github.com/fmtlib/fmt/pull/2983>`_).
For example (`godbolt <https://godbolt.org/z/roKqGdj8c>`__):
.. code:: c++
#include <fmt/ranges.h>
#include <vector>
int main() {
auto v = std::vector{1, 2, 3};
fmt::print("{:n}\n", v); // prints 1, 2, 3
}
Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
* Worked around problematic ``std::string_view`` constructors introduced in
C++23 (`#3030 <https://github.com/fmtlib/fmt/issues/3030>`_,
`#3050 <https://github.com/fmtlib/fmt/issues/3050>`_).
Thanks `@strega-nil-ms (nicole mazzuca) <https://github.com/strega-nil-ms>`_.
* Improve handling (exclusion) of recursive ranges
(`#2968 <https://github.com/fmtlib/fmt/issues/2968>`_,
`#2974 <https://github.com/fmtlib/fmt/pull/2974>`_).
Thanks `@Dani-Hub (Daniel Krügler) <https://github.com/Dani-Hub>`_.
* Improved error reporting in format string compilation
(`#3055 <https://github.com/fmtlib/fmt/issues/3055>`_).
* Improved the implementation of
`Dragonbox <https://github.com/jk-jeon/dragonbox>`_, the algorithm used for
the default floating-point formatting
(`#2984 <https://github.com/fmtlib/fmt/pull/2984>`_).
Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
* Fixed issues with floating-point formatting on exotic platforms.
* Improved the implementation of chrono formatting
(`#3010 <https://github.com/fmtlib/fmt/pull/3010>`_).
Thanks `@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
* Improved documentation
(`#2966 <https://github.com/fmtlib/fmt/pull/2966>`_,
`#3009 <https://github.com/fmtlib/fmt/pull/3009>`_,
`#3020 <https://github.com/fmtlib/fmt/issues/3020>`_,
`#3037 <https://github.com/fmtlib/fmt/pull/3037>`_).
Thanks `@mwinterb <https://github.com/mwinterb>`_,
`@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_
and `@remiburtin (Rémi Burtin) <https://github.com/remiburtin>`_.
* Improved build configuration
(`#2991 <https://github.com/fmtlib/fmt/pull/2991>`_,
`#2995 <https://github.com/fmtlib/fmt/pull/2995>`_,
`#3004 <https://github.com/fmtlib/fmt/issues/3004>`_,
`#3007 <https://github.com/fmtlib/fmt/pull/3007>`_,
`#3040 <https://github.com/fmtlib/fmt/pull/3040>`_).
Thanks `@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_ and
`@hwhsu1231 (Haowei Hsu) <https://github.com/hwhsu1231>`_.
* Fixed various warnings and compilation issues
(`#2969 <https://github.com/fmtlib/fmt/issues/2969>`_,
`#2971 <https://github.com/fmtlib/fmt/pull/2971>`_,
`#2975 <https://github.com/fmtlib/fmt/issues/2975>`_,
`#2982 <https://github.com/fmtlib/fmt/pull/2982>`_,
`#2985 <https://github.com/fmtlib/fmt/pull/2985>`_,
`#2988 <https://github.com/fmtlib/fmt/issues/2988>`_,
`#2989 <https://github.com/fmtlib/fmt/issues/2989>`_,
`#3000 <https://github.com/fmtlib/fmt/issues/3000>`_,
`#3006 <https://github.com/fmtlib/fmt/issues/3006>`_,
`#3014 <https://github.com/fmtlib/fmt/issues/3014>`_,
`#3015 <https://github.com/fmtlib/fmt/issues/3015>`_,
`#3021 <https://github.com/fmtlib/fmt/pull/3021>`_,
`#3023 <https://github.com/fmtlib/fmt/issues/3023>`_,
`#3024 <https://github.com/fmtlib/fmt/pull/3024>`_,
`#3029 <https://github.com/fmtlib/fmt/pull/3029>`_,
`#3043 <https://github.com/fmtlib/fmt/pull/3043>`_,
`#3052 <https://github.com/fmtlib/fmt/issues/3052>`_,
`#3053 <https://github.com/fmtlib/fmt/pull/3053>`_,
`#3054 <https://github.com/fmtlib/fmt/pull/3054>`_).
Thanks `@h-friederich (Hannes Friederich) <https://github.com/h-friederich>`_,
`@dimztimz (Dimitrij Mijoski) <https://github.com/dimztimz>`_,
`@olupton (Olli Lupton) <https://github.com/olupton>`_,
`@bernhardmgruber (Bernhard Manfred Gruber)
<https://github.com/bernhardmgruber>`_,
`@phprus (Vladislav Shchapov) <https://github.com/phprus>`_.
9.0.0 - 2022-07-04 9.0.0 - 2022-07-04
------------------ ------------------
@ -19,7 +812,7 @@
return result; return result;
} }
constexpr auto answer = compile_time_itoa(0.42); constexpr auto answer = compile_time_dtoa(0.42);
works with the default settings. works with the default settings.
@ -2134,7 +2927,7 @@
<https://github.com/kwesolowski>`_. <https://github.com/kwesolowski>`_.
* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ`` * Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ``
macro to prevent interferring with fuzzing of projects using {fmt} macro to prevent interfering with fuzzing of projects using {fmt}
(`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_). (`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_).
Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_. Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_.

View file

@ -1,4 +1,4 @@
Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View file

@ -12,9 +12,6 @@
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg .. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true
:target: https://ci.appveyor.com/project/vitaut/fmt
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg .. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
:alt: fmt is continuously fuzzed at oss-fuzz :alt: fmt is continuously fuzzed at oss-fuzz
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\ :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
@ -25,6 +22,9 @@
:alt: Ask questions at StackOverflow with the tag fmt :alt: Ask questions at StackOverflow with the tag fmt
:target: https://stackoverflow.com/questions/tagged/fmt :target: https://stackoverflow.com/questions/tagged/fmt
.. image:: https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge
:target: https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt
**{fmt}** is an open-source formatting library providing a fast and safe **{fmt}** is an open-source formatting library providing a fast and safe
alternative to C stdio and C++ iostreams. alternative to C stdio and C++ iostreams.
@ -50,7 +50,9 @@ Features
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's * `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ `format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and * Fast IEEE 754 floating-point formatter with correct rounding, shortness and
round-trip guarantees round-trip guarantees using the `Dragonbox <https://github.com/jk-jeon/dragonbox>`_
algorithm
* Portable Unicode support
* Safe `printf implementation * Safe `printf implementation
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX <https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
extension for positional arguments extension for positional arguments
@ -67,7 +69,7 @@ Features
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed <https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20 <https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_ Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
* Safety: the library is fully type safe, errors in format strings can be * Safety: the library is fully type-safe, errors in format strings can be
reported at compile time, automatic memory management prevents buffer overflow reported at compile time, automatic memory management prevents buffer overflow
errors errors
* Ease of use: small self-contained code base, no external dependencies, * Ease of use: small self-contained code base, no external dependencies,
@ -77,7 +79,7 @@ Features
consistent output across platforms and support for older compilers consistent output across platforms and support for older compilers
* Clean warning-free codebase even on high warning levels such as * Clean warning-free codebase even on high warning levels such as
``-Wall -Wextra -pedantic`` ``-Wall -Wextra -pedantic``
* Locale-independence by default * Locale independence by default
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro * Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
See the `documentation <https://fmt.dev>`_ for more details. See the `documentation <https://fmt.dev>`_ for more details.
@ -194,24 +196,24 @@ Speed tests
================= ============= =========== ================= ============= ===========
Library Method Run Time, s Library Method Run Time, s
================= ============= =========== ================= ============= ===========
libc printf 1.04 libc printf 0.91
libc++ std::ostream 3.05 libc++ std::ostream 2.49
{fmt} 6.1.1 fmt::print 0.75 {fmt} 9.1 fmt::print 0.74
Boost Format 1.67 boost::format 7.24 Boost Format 1.80 boost::format 6.26
Folly Format folly::format 2.23 Folly Format folly::format 1.87
================= ============= =========== ================= ============= ===========
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``. {fmt} is the fastest of the benchmarked methods, ~20% faster than ``printf``.
The above results were generated by building ``tinyformat_test.cpp`` on macOS The above results were generated by building ``tinyformat_test.cpp`` on macOS
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the 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"`` 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 or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source further details refer to the `source
<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_. <https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on {fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_) IEEE754 ``float`` and ``double`` formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
`ryu <https://github.com/ulfjack/ryu>`_: `ryu <https://github.com/ulfjack/ryu>`_:
@ -227,7 +229,7 @@ The script `bloat-test.py
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_ from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
tests compile time and code bloat for nontrivial projects. tests compile time and code bloat for nontrivial projects.
It generates 100 translation units and uses ``printf()`` or its alternative It generates 100 translation units and uses ``printf()`` or its alternative
five times in each to simulate a medium sized project. The resulting five times in each to simulate a medium-sized project. The resulting
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42), executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
macOS Sierra, best of three) is shown in the following tables. macOS Sierra, best of three) is shown in the following tables.
@ -248,7 +250,7 @@ As you can see, {fmt} has 60% less overhead in terms of resulting binary code
size compared to iostreams and comes pretty close to ``printf``. Boost Format size compared to iostreams and comes pretty close to ``printf``. Boost Format
and Folly Format have the largest overheads. and Folly Format have the largest overheads.
``printf+string`` is the same as ``printf`` but with extra ``<string>`` ``printf+string`` is the same as ``printf`` but with an extra ``<string>``
include to measure the overhead of the latter. include to measure the overhead of the latter.
**Non-optimized build** **Non-optimized build**
@ -264,14 +266,14 @@ Boost Format 54.1 365 303
Folly Format 79.9 445 430 Folly Format 79.9 445 430
============= =============== ==================== ================== ============= =============== ==================== ==================
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to ``libc``, ``lib(std)c++``, and ``libfmt`` are all linked as shared libraries to
compare formatting function overhead only. Boost Format is a compare formatting function overhead only. Boost Format is a
header-only library so it doesn't provide any linkage options. header-only library so it doesn't provide any linkage options.
Running the tests Running the tests
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
Please refer to `Building the library`__ for the instructions on how to build Please refer to `Building the library`__ for instructions on how to build
the library and run the unit tests. the library and run the unit tests.
__ https://fmt.dev/latest/usage.html#building-the-library __ https://fmt.dev/latest/usage.html#building-the-library
@ -296,9 +298,12 @@ or the bloat test::
Migrating code Migrating code
-------------- --------------
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang `clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`_ v17 (not yet
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to released) provides the `modernize-use-std-print
``fmt::print``. <https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html>`_
check that is capable of converting occurrences of ``printf`` and
``fprintf`` to ``fmt::print`` if configured to do so. (By default it
converts to ``std::print``.)
Projects using this library Projects using this library
--------------------------- ---------------------------
@ -306,8 +311,6 @@ Projects using this library
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform * `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
real-time strategy game real-time strategy game
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
* `AMPL/MP <https://github.com/ampl/mp>`_: * `AMPL/MP <https://github.com/ampl/mp>`_:
an open-source library for mathematical programming an open-source library for mathematical programming
@ -325,9 +328,11 @@ Projects using this library
* `ccache <https://ccache.dev/>`_: a compiler cache * `ccache <https://ccache.dev/>`_: a compiler cache
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database * `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: an analytical database
management system management system
* `Contour <https://github.com/contour-terminal/contour/>`_: a modern terminal emulator
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater * `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle vehicle
@ -363,6 +368,10 @@ Projects using this library
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node * `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
* `libunicode <https://github.com/contour-terminal/libunicode/>`_: a modern C++17 Unicode library
* `MariaDB <https://mariadb.org/>`_: relational database management system
* `Microsoft Verona <https://github.com/microsoft/verona>`_: * `Microsoft Verona <https://github.com/microsoft/verona>`_:
research programming language for concurrent ownership research programming language for concurrent ownership
@ -392,7 +401,7 @@ Projects using this library
proxy proxy
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement * `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
for mission critical systems written in C++ for mission-critical systems written in C++
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client * `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
library library
@ -416,6 +425,9 @@ Projects using this library
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source * `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
MMORPG framework MMORPG framework
* `🐙 userver framework <https://userver.tech/>`_: open-source asynchronous
framework with a rich set of abstractions and database drivers
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows * `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
terminal terminal
@ -473,7 +485,7 @@ error handling is awkward.
Boost Format Boost Format
~~~~~~~~~~~~ ~~~~~~~~~~~~
This is a very powerful library which supports both ``printf``-like format This is a very powerful library that supports both ``printf``-like format
strings and positional arguments. Its main drawback is performance. According to strings and positional arguments. Its main drawback is performance. According to
various benchmarks, it is much slower than other methods considered here. Boost various benchmarks, it is much slower than other methods considered here. Boost
Format also has excessive build times and severe code bloat issues (see Format also has excessive build times and severe code bloat issues (see
@ -482,7 +494,7 @@ Format also has excessive build times and severe code bloat issues (see
FastFormat FastFormat
~~~~~~~~~~ ~~~~~~~~~~
This is an interesting library which is fast, safe and has positional arguments. This is an interesting library that is fast, safe, and has positional arguments.
However, it has significant limitations, citing its author: However, it has significant limitations, citing its author:
Three features that have no hope of being accommodated within the Three features that have no hope of being accommodated within the
@ -498,7 +510,7 @@ restrictive for using it in some projects.
Boost Spirit.Karma Boost Spirit.Karma
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
This is not really a formatting library but I decided to include it here for This is not a formatting library but I decided to include it here for
completeness. As iostreams, it suffers from the problem of mixing verbatim text completeness. As iostreams, it suffers from the problem of mixing verbatim text
with arguments. The library is pretty fast, but slower on integer formatting with arguments. The library is pretty fast, but slower on integer formatting
than ``fmt::format_to`` with format string compilation on Karma's own benchmark, than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
@ -517,7 +529,7 @@ Documentation License
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_ The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
section in the documentation is based on the one from Python `string module section in the documentation is based on the one from Python `string module
documentation <https://docs.python.org/3/library/string.html#module-string>`_. documentation <https://docs.python.org/3/library/string.html#module-string>`_.
For this reason the documentation is distributed under the Python Software For this reason, the documentation is distributed under the Python Software
Foundation license available in `doc/python-license.txt Foundation license available in `doc/python-license.txt
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_. <https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
It only applies if you distribute the documentation of {fmt}. It only applies if you distribute the documentation of {fmt}.
@ -526,8 +538,7 @@ Maintainers
----------- -----------
The {fmt} library is maintained by Victor Zverovich (`vitaut The {fmt} library is maintained by Victor Zverovich (`vitaut
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan <https://github.com/vitaut>`_) with contributions from many other people.
<https://github.com/foonathan>`_) with contributions from many other people.
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names. `Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
Let us know if your contribution is not listed or mentioned incorrectly and Let us know if your contribution is not listed or mentioned incorrectly and

View file

@ -1,10 +1,19 @@
find_program(DOXYGEN doxygen) find_program(DOXYGEN doxygen
PATHS "$ENV{ProgramFiles}/doxygen/bin"
"$ENV{ProgramFiles\(x86\)}/doxygen/bin")
if (NOT DOXYGEN) if (NOT DOXYGEN)
message(STATUS "Target 'doc' disabled (requires doxygen)") message(STATUS "Target 'doc' disabled (requires doxygen)")
return () return ()
endif () endif ()
# Find the Python interpreter and set the PYTHON_EXECUTABLE variable.
if (CMAKE_VERSION VERSION_LESS 3.12)
# This logic is deprecated in CMake after 3.12.
find_package(PythonInterp QUIET REQUIRED) find_package(PythonInterp QUIET REQUIRED)
else ()
find_package(Python QUIET REQUIRED)
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
endif ()
add_custom_target(doc add_custom_target(doc
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py

View file

@ -7,7 +7,7 @@ API Reference
The {fmt} library API consists of the following parts: The {fmt} library API consists of the following parts:
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions * :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
for ``char``/UTF-8 with compile-time checks and minimal dependencies for ``char``/UTF-8 with C++20 compile-time checks and minimal dependencies
* :ref:`fmt/format.h <format-api>`: the full format API providing additional * :ref:`fmt/format.h <format-api>`: the full format API providing additional
formatting functions and locale support formatting functions and locale support
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples * :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
@ -17,6 +17,7 @@ The {fmt} library API consists of the following parts:
* :ref:`fmt/color.h <color-api>`: terminal color and text style * :ref:`fmt/color.h <color-api>`: terminal color and text style
* :ref:`fmt/os.h <os-api>`: system APIs * :ref:`fmt/os.h <os-api>`: system APIs
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support * :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
* :ref:`fmt/args.h <args-api>`: dynamic argument lists
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting * :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support * :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
@ -28,10 +29,11 @@ macros have prefix ``FMT_``.
Core API Core API
======== ========
``fmt/core.h`` defines the core API which provides main formatting functions for ``fmt/core.h`` defines the core API which provides main formatting functions
``char``/UTF-8 with compile-time checks. It has minimal include dependencies for for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include
better compile times. This header is only beneficial when using {fmt} as a dependencies for better compile times. This header is only beneficial when
library and not in the header-only mode. using {fmt} as a library (the default) and not in the header-only mode.
It also provides ``formatter`` specializations for built-in and string types.
The following functions use :ref:`format string syntax <syntax>` The following functions use :ref:`format string syntax <syntax>`
similar to that of Python's `str.format similar to that of Python's `str.format
@ -70,113 +72,18 @@ checked at compile time in C++20. To pass a runtime format string wrap it in
Compile-Time Format String Checks Compile-Time Format String Checks
--------------------------------- ---------------------------------
Compile-time checks are enabled when using ``FMT_STRING``. They support built-in Compile-time format string checks are enabled by default on compilers
and string types as well as user-defined types with ``constexpr`` ``parse`` that support C++20 ``consteval``. On older compilers you can use the
functions in their ``formatter`` specializations. :ref:`FMT_STRING <legacy-checks>`: macro defined in ``fmt/format.h`` instead.
Requires C++14 and is a no-op in C++11.
.. doxygendefine:: FMT_STRING Unused arguments are allowed as in Python's `str.format` and ordinary functions.
To force the use of compile-time checks, define the preprocessor variable
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
will fail to compile with regular strings. Runtime-checked
formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc.
.. doxygenclass:: fmt::basic_format_string .. doxygenclass:: fmt::basic_format_string
:members: :members:
.. doxygentypedef:: fmt::format_string .. doxygentypedef:: fmt::format_string
.. doxygenfunction:: fmt::runtime(const S&) .. doxygenfunction:: fmt::runtime(string_view) -> runtime_format_string<>
Named Arguments
---------------
.. doxygenfunction:: fmt::arg(const S&, const T&)
Named arguments are not supported in compile-time checks at the moment.
Argument Lists
--------------
You can create your own formatting function with compile-time checks and small
binary footprint, for example (https://godbolt.org/z/oba4Mc):
.. code:: c++
#include <fmt/format.h>
void vlog(const char* file, int line, fmt::string_view format,
fmt::format_args args) {
fmt::print("{}: {}: ", file, line);
fmt::vprint(format, args);
}
template <typename S, typename... Args>
void log(const char* file, int line, const S& format, Args&&... args) {
vlog(file, line, format, fmt::make_format_args(args...));
}
#define MY_LOG(format, ...) \
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
MY_LOG("invalid squishiness: {}", 42);
Note that ``vlog`` is not parameterized on argument types which improves compile
times and reduces binary code size compared to a fully parameterized version.
.. doxygenfunction:: fmt::make_format_args(const Args&...)
.. doxygenclass:: fmt::format_arg_store
:members:
.. doxygenclass:: fmt::dynamic_format_arg_store
:members:
.. doxygenclass:: fmt::basic_format_args
:members:
.. doxygentypedef:: fmt::format_args
.. doxygenclass:: fmt::basic_format_arg
:members:
.. doxygenclass:: fmt::basic_format_parse_context
:members:
.. doxygenclass:: fmt::basic_format_context
:members:
.. doxygentypedef:: fmt::format_context
Compatibility
-------------
.. doxygenclass:: fmt::basic_string_view
:members:
.. doxygentypedef:: fmt::string_view
Locale
------
All formatting is locale-independent by default. Use the ``'L'`` format
specifier to insert the appropriate number separator characters from the
locale::
#include <fmt/core.h>
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
.. _format-api:
Format API
==========
``fmt/format.h`` defines the full format API providing additional formatting
functions and locale support.
.. _udt: .. _udt:
@ -186,14 +93,39 @@ Formatting User-Defined Types
The {fmt} library provides formatters for many standard C++ types. The {fmt} library provides formatters for many standard C++ types.
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
and time formatting and :ref:`fmt/std.h <std-api>` for path and variant and time formatting and :ref:`fmt/std.h <std-api>` for other standard library
formatting. types.
To make a user-defined type formattable, specialize the ``formatter<T>`` struct There are two ways to make a user-defined type formattable: providing a
template and implement ``parse`` and ``format`` methods:: ``format_as`` function or specializing the ``formatter`` struct template.
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 take an
object of your type and return an object of a formattable type. It should be
defined in the same namespace as your type.
Example (https://godbolt.org/z/r7vvGE1v7)::
#include <fmt/format.h> #include <fmt/format.h>
namespace kevin_namespacy {
enum class film {
house_of_cards, american_beauty, se7en = 7
};
auto format_as(film f) { return fmt::underlying(f); }
}
int main() {
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
}
Using the specialization API is more complex but gives you full control over
parsing and formatting. To use this method specialize the ``formatter`` struct
template for your type and implement ``parse`` and ``format`` methods.
For example::
#include <fmt/core.h>
struct point { struct point {
double x, y; double x, y;
}; };
@ -203,7 +135,7 @@ template and implement ``parse`` and ``format`` methods::
char presentation = 'f'; char presentation = 'f';
// Parses format specifications of the form ['f' | 'e']. // Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator {
// [ctx.begin(), ctx.end()) is a character range that contains a part of // [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed, // the format string starting from the format specifications to be parsed,
// e.g. in // e.g. in
@ -224,7 +156,7 @@ template and implement ``parse`` and ``format`` methods::
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++; if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
// Check if reached the end of the range: // Check if reached the end of the range:
if (it != end && *it != '}') throw format_error("invalid format"); if (it != end && *it != '}') throw_format_error("invalid format");
// Return an iterator past the end of the parsed range: // Return an iterator past the end of the parsed range:
return it; return it;
@ -232,8 +164,7 @@ template and implement ``parse`` and ``format`` methods::
// Formats the point p using the parsed format specification (presentation) // Formats the point p using the parsed format specification (presentation)
// stored in this formatter. // stored in this formatter.
template <typename FormatContext> auto format(const point& p, format_context& ctx) const -> format_context::iterator {
auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
// ctx.out() is an output iterator to write to. // ctx.out() is an output iterator to write to.
return presentation == 'f' return presentation == 'f'
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y) ? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
@ -250,12 +181,22 @@ Then you can pass objects of type ``point`` to any formatting function::
You can also reuse existing formatters via inheritance or composition, for You can also reuse existing formatters via inheritance or composition, for
example:: example::
// color.h:
#include <fmt/core.h>
enum class color {red, green, blue}; enum class color {red, green, blue};
template <> struct fmt::formatter<color>: formatter<string_view> { template <> struct fmt::formatter<color>: formatter<string_view> {
// parse is inherited from formatter<string_view>. // parse is inherited from formatter<string_view>.
template <typename FormatContext>
auto format(color c, FormatContext& ctx) const { auto format(color c, format_context& ctx) const;
};
// color.cc:
#include "color.h"
#include <fmt/format.h>
auto fmt::formatter<color>::format(color c, format_context& ctx) const {
string_view name = "unknown"; string_view name = "unknown";
switch (c) { switch (c) {
case color::red: name = "red"; break; case color::red: name = "red"; break;
@ -264,8 +205,9 @@ example::
} }
return formatter<string_view>::format(name, ctx); return formatter<string_view>::format(name, ctx);
} }
};
Note that ``formatter<string_view>::format`` is defined in ``fmt/format.h`` so
it has to be included in the source file.
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
all string format specifications, for example all string format specifications, for example
@ -277,8 +219,9 @@ will return ``" blue"``.
You can also write a formatter for a hierarchy of classes:: You can also write a formatter for a hierarchy of classes::
// demo.h:
#include <type_traits> #include <type_traits>
#include <fmt/format.h> #include <fmt/core.h>
struct A { struct A {
virtual ~A() {} virtual ~A() {}
@ -292,42 +235,107 @@ You can also write a formatter for a hierarchy of classes::
template <typename T> template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> : struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
fmt::formatter<std::string> { fmt::formatter<std::string> {
template <typename FormatCtx> auto format(const A& a, format_context& ctx) const {
auto format(const A& a, FormatCtx& ctx) const {
return fmt::formatter<std::string>::format(a.name(), ctx); return fmt::formatter<std::string>::format(a.name(), ctx);
} }
}; };
// demo.cc:
#include "demo.h"
#include <fmt/format.h>
int main() { int main() {
B b; B b;
A& a = b; A& a = b;
fmt::print("{}", a); // prints "B" fmt::print("{}", a); // prints "B"
} }
If a type provides both a ``formatter`` specialization and an implicit Providing both a ``formatter`` specialization and a ``format_as`` overload is
conversion to a formattable type, the specialization takes precedence over the disallowed.
conversion.
For enums {fmt} also provides the ``format_as`` extension API. To format an enum Named Arguments
via this API define ``format_as`` that takes this enum and converts it to the ---------------
underlying type. ``format_as`` should be defined in the same namespace as the
enum.
Example (https://godbolt.org/z/r7vvGE1v7):: .. doxygenfunction:: fmt::arg(const S&, const T&)
#include <fmt/format.h> Named arguments are not supported in compile-time checks at the moment.
namespace kevin_namespacy { Argument Lists
enum class film { --------------
house_of_cards, american_beauty, se7en = 7
}; You can create your own formatting function with compile-time checks and small
auto format_as(film f) { return fmt::underlying(f); } binary footprint, for example (https://godbolt.org/z/vajfWEG4b):
.. code:: c++
#include <fmt/core.h>
void vlog(const char* file, int line, fmt::string_view format,
fmt::format_args args) {
fmt::print("{}: {}: ", file, line);
fmt::vprint(format, args);
} }
int main() { template <typename... T>
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7" void log(const char* file, int line, fmt::format_string<T...> format, T&&... args) {
vlog(file, line, format, fmt::make_format_args(args...));
} }
#define MY_LOG(format, ...) log(__FILE__, __LINE__, format, __VA_ARGS__)
MY_LOG("invalid squishiness: {}", 42);
Note that ``vlog`` is not parameterized on argument types which improves compile
times and reduces binary code size compared to a fully parameterized version.
.. doxygenfunction:: fmt::make_format_args(const Args&...)
.. doxygenclass:: fmt::format_arg_store
:members:
.. doxygenclass:: fmt::basic_format_args
:members:
.. doxygentypedef:: fmt::format_args
.. doxygenclass:: fmt::basic_format_arg
:members:
.. doxygenclass:: fmt::basic_format_parse_context
:members:
.. doxygenclass:: fmt::basic_format_context
:members:
.. doxygentypedef:: fmt::format_context
.. _args-api:
Dynamic Argument Lists
----------------------
The header ``fmt/args.h`` provides ``dynamic_format_arg_store``, a builder-like
API that can be used to construct format argument lists dynamically.
.. doxygenclass:: fmt::dynamic_format_arg_store
:members:
Compatibility
-------------
.. doxygenclass:: fmt::basic_string_view
:members:
.. doxygentypedef:: fmt::string_view
.. _format-api:
Format API
==========
``fmt/format.h`` defines the full format API providing additional formatting
functions and locale support.
Literal-Based API Literal-Based API
----------------- -----------------
@ -339,7 +347,7 @@ Utilities
--------- ---------
.. doxygenfunction:: fmt::ptr(T p) -> const void* .. doxygenfunction:: fmt::ptr(T p) -> const void*
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void* .. doxygenfunction:: fmt::ptr(const std::unique_ptr<T, Deleter> &p) -> const void*
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void* .. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
.. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type .. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type
@ -405,6 +413,41 @@ normally don't do any allocations for built-in and string types except for
non-default floating-point formatting that occasionally falls back on non-default floating-point formatting that occasionally falls back on
``sprintf``. ``sprintf``.
Locale
------
All formatting is locale-independent by default. Use the ``'L'`` format
specifier to insert the appropriate number separator characters from the
locale::
#include <fmt/core.h>
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
``fmt/format.h`` provides the following overloads of formatting functions that
take ``std::locale`` as a parameter. The locale type is a template parameter to
avoid the expensive ``<locale>`` include.
.. doxygenfunction:: format(const Locale& loc, format_string<T...> fmt, T&&... args) -> std::string
.. doxygenfunction:: format_to(OutputIt out, const Locale& loc, format_string<T...> fmt, T&&... args) -> OutputIt
.. doxygenfunction:: formatted_size(const Locale& loc, format_string<T...> fmt, T&&... args) -> size_t
.. _legacy-checks:
Legacy Compile-Time Format String Checks
----------------------------------------
``FMT_STRING`` enables compile-time checks on older compilers. It requires C++14
or later and is a no-op in C++11.
.. doxygendefine:: FMT_STRING
To force the use of legacy compile-time checks, define the preprocessor variable
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
will fail to compile with regular strings.
.. _ranges-api: .. _ranges-api:
Range and Tuple Formatting Range and Tuple Formatting
@ -474,10 +517,11 @@ Standard Library Types Formatting
``fmt/std.h`` provides formatters for: ``fmt/std.h`` provides formatters for:
* `std::filesystem::path <std::filesystem::path>`_ * `std::filesystem::path <https://en.cppreference.com/w/cpp/filesystem/path>`_
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_ * `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_ * `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_ * `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
* `std::optional <https://en.cppreference.com/w/cpp/utility/optional>`_
Formatting Variants Formatting Variants
------------------- -------------------
@ -505,10 +549,20 @@ Format String Compilation
``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings ``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings
marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into
efficient formatting code at compile-time. This supports arguments of built-in efficient formatting code at compile-time. This supports arguments of built-in
and string types as well as user-defined types with ``constexpr`` ``parse`` and string types as well as user-defined types with ``format`` functions taking
functions in their ``formatter`` specializations. Format string compilation can the format context type as a template parameter in their ``formatter``
generate more binary code compared to the default API and is only recommended in specializations. For example::
places where formatting is a performance bottleneck.
template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx);
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const;
};
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.
.. doxygendefine:: FMT_COMPILE .. doxygendefine:: FMT_COMPILE
@ -581,7 +635,7 @@ the POSIX extension for positional arguments. Unlike their standard
counterparts, the ``fmt`` functions are type-safe and throw an exception if an counterparts, the ``fmt`` functions are type-safe and throw an exception if an
argument type doesn't match its format specification. argument type doesn't match its format specification.
.. doxygenfunction:: printf(const S &format_str, const T&... args) .. doxygenfunction:: printf(string_view fmt, const T&... args) -> int
.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int .. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int

View file

@ -4,7 +4,7 @@
import errno, os, re, sys import errno, os, re, sys
from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0'] versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3', '8.0.0', '8.0.1', '8.1.0', '8.1.1', '9.0.0', '9.1.0', '10.0.0', '10.1.0', '10.1.1']
class Pip: class Pip:
def __init__(self, venv_dir): def __init__(self, venv_dir):
@ -49,9 +49,9 @@ def build_docs(version='dev', **kwargs):
GENERATE_MAN = NO GENERATE_MAN = NO
GENERATE_RTF = NO GENERATE_RTF = NO
CASE_SENSE_NAMES = NO CASE_SENSE_NAMES = NO
INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \ INPUT = {0}/args.h {0}/chrono.h {0}/color.h {0}/core.h \
{0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \ {0}/compile.h {0}/format.h {0}/os.h {0}/ostream.h \
{0}/xchar.h {0}/printf.h {0}/xchar.h
QUIET = YES QUIET = YES
JAVADOC_AUTOBRIEF = YES JAVADOC_AUTOBRIEF = YES
AUTOLINK_SUPPORT = NO AUTOLINK_SUPPORT = NO
@ -83,8 +83,7 @@ def build_docs(version='dev', **kwargs):
internal_symbols = [ internal_symbols = [
'fmt::detail::.*', 'fmt::detail::.*',
'basic_data<>', 'basic_data<>',
'fmt::type_identity', 'fmt::type_identity'
'fmt::dynamic_formatter'
] ]
noisy_warnings = [ noisy_warnings = [
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \ 'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \

View file

@ -39,7 +39,7 @@ The ``fmt::format`` function returns a string "The answer is 42.". You can use
.. code:: c++ .. code:: c++
auto out = fmt::memory_buffer(); auto out = fmt::memory_buffer();
format_to(std::back_inserter(out), fmt::format_to(std::back_inserter(out),
"For a moment, {} happened.", "nothing"); "For a moment, {} happened.", "nothing");
auto data = out.data(); // pointer to the formatted data auto data = out.data(); // pointer to the formatted data
auto size = out.size(); // size of the formatted data auto size = out.size(); // size of the formatted data

View file

@ -109,8 +109,8 @@ Note that unless a minimum field width is defined, the field width will always
be the same size as the data to fill it, so that the alignment option has no be the same size as the data to fill it, so that the alignment option has no
meaning in this case. meaning in this case.
The *sign* option is only valid for number types, and can be one of the The *sign* option is only valid for floating point and signed integer types,
following: and can be one of the following:
+---------+------------------------------------------------------------+ +---------+------------------------------------------------------------+
| Option | Meaning | | Option | Meaning |
@ -304,8 +304,8 @@ The available presentation types for pointers are:
Chrono Format Specifications Chrono Format Specifications
============================ ============================
Format specifications for chrono types and ``std::tm`` have the following Format specifications for chrono duration and time point types as well as
syntax: ``std::tm`` have the following syntax:
.. productionlist:: sf .. productionlist:: sf
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`] chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
@ -321,20 +321,89 @@ syntax:
Literal chars are copied unchanged to the output. Precision is valid only for Literal chars are copied unchanged to the output. Precision is valid only for
``std::chrono::duration`` types with a floating-point representation type. ``std::chrono::duration`` types with a floating-point representation type.
The available presentation types (*chrono_type*) for chrono durations and time The available presentation types (*chrono_type*) are:
points are:
+---------+--------------------------------------------------------------------+ +---------+--------------------------------------------------------------------+
| Type | Meaning | | Type | Meaning |
+=========+====================================================================+ +=========+====================================================================+
| ``'a'`` | The abbreviated weekday name, e.g. "Sat". If the value does not |
| | contain a valid weekday, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'A'`` | The full weekday name, e.g. "Saturday". If the value does not |
| | contain a valid weekday, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'b'`` | The abbreviated month name, e.g. "Nov". If the value does not |
| | contain a valid month, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'B'`` | The full month name, e.g. "November". If the value does not |
| | contain a valid month, an exception of type ``format_error`` is |
| | thrown. |
+---------+--------------------------------------------------------------------+
| ``'c'`` | The date and time representation, e.g. "Sat Nov 12 22:04:00 1955". |
| | The modified command ``%Ec`` produces the locale's alternate date |
| | and time representation. |
+---------+--------------------------------------------------------------------+
| ``'C'`` | The year divided by 100 using floored division, e.g. "55". If the |
| | result is a single decimal digit, it is prefixed with 0. |
| | The modified command ``%EC`` produces the locale's alternative |
| | representation of the century. |
+---------+--------------------------------------------------------------------+
| ``'d'`` | The day of month as a decimal number. If the result is a single |
| | decimal digit, it is prefixed with 0. The modified command ``%Od`` |
| | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'D'`` | Equivalent to ``%m/%d/%y``, e.g. "11/12/55". |
+---------+--------------------------------------------------------------------+
| ``'e'`` | The day of month as a decimal number. If the result is a single |
| | decimal digit, it is prefixed with a space. The modified command |
| | ``%Oe`` produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'F'`` | Equivalent to ``%Y-%m-%d``, e.g. "1955-11-12". |
+---------+--------------------------------------------------------------------+
| ``'g'`` | The last two decimal digits of the ISO week-based year. If the |
| | result is a single digit it is prefixed by 0. |
+---------+--------------------------------------------------------------------+
| ``'G'`` | The ISO week-based year as a decimal number. If the result is less |
| | than four digits it is left-padded with 0 to four digits. |
+---------+--------------------------------------------------------------------+
| ``'h'`` | Equivalent to ``%b``, e.g. "Nov". |
+---------+--------------------------------------------------------------------+
| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a | | ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a |
| | single digit, it is prefixed with 0. The modified command ``%OH`` | | | single digit, it is prefixed with 0. The modified command ``%OH`` |
| | produces the locale's alternative representation. | | | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+ +---------+--------------------------------------------------------------------+
| ``'I'`` | The hour (12-hour clock) as a decimal number. If the result is a |
| | single digit, it is prefixed with 0. The modified command ``%OI`` |
| | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'j'`` | If the type being formatted is a specialization of duration, the |
| | decimal number of days without padding. Otherwise, the day of the |
| | year as a decimal number. Jan 1 is 001. If the result is less than |
| | three digits, it is left-padded with 0 to three digits. |
+---------+--------------------------------------------------------------------+
| ``'m'`` | The month as a decimal number. Jan is 01. If the result is a |
| | single digit, it is prefixed with 0. The modified command ``%Om`` |
| | produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'M'`` | The minute as a decimal number. If the result is a single digit, | | ``'M'`` | The minute as a decimal number. If the result is a single digit, |
| | it is prefixed with 0. The modified command ``%OM`` produces the | | | it is prefixed with 0. The modified command ``%OM`` produces the |
| | locale's alternative representation. | | | locale's alternative representation. |
+---------+--------------------------------------------------------------------+ +---------+--------------------------------------------------------------------+
| ``'n'`` | A new-line character. |
+---------+--------------------------------------------------------------------+
| ``'p'`` | The AM/PM designations associated with a 12-hour clock. |
+---------+--------------------------------------------------------------------+
| ``'q'`` | The duration's unit suffix. |
+---------+--------------------------------------------------------------------+
| ``'Q'`` | The duration's numeric value (as if extracted via ``.count()``). |
+---------+--------------------------------------------------------------------+
| ``'r'`` | The 12-hour clock time, e.g. "10:04:00 PM". |
+---------+--------------------------------------------------------------------+
| ``'R'`` | Equivalent to ``%H:%M``, e.g. "22:04". |
+---------+--------------------------------------------------------------------+
| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than | | ``'S'`` | Seconds as a decimal number. If the number of seconds is less than |
| | 10, the result is prefixed with 0. If the precision of the input | | | 10, the result is prefixed with 0. If the precision of the input |
| | cannot be exactly represented with seconds, then the format is a | | | cannot be exactly represented with seconds, then the format is a |
@ -345,9 +414,66 @@ points are:
| | decimal point is localized according to the locale. The modified | | | decimal point is localized according to the locale. The modified |
| | command ``%OS`` produces the locale's alternative representation. | | | command ``%OS`` produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+ +---------+--------------------------------------------------------------------+
| ``'t'`` | A horizontal-tab character. |
+---------+--------------------------------------------------------------------+
| ``'T'`` | Equivalent to ``%H:%M:%S``. |
+---------+--------------------------------------------------------------------+
| ``'u'`` | The ISO weekday as a decimal number (1-7), where Monday is 1. The |
| | modified command ``%Ou`` produces the locale's alternative |
| | representation. |
+---------+--------------------------------------------------------------------+
| ``'U'`` | The week number of the year as a decimal number. The first Sunday |
| | of the year is the first day of week 01. Days of the same year |
| | prior to that are in week 00. If the result is a single digit, it |
| | is prefixed with 0. The modified command ``%OU`` produces the |
| | locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'V'`` | The ISO week-based week number as a decimal number. If the result |
| | is a single digit, it is prefixed with 0. The modified command |
| | ``%OV`` produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'w'`` | The weekday as a decimal number (0-6), where Sunday is 0. |
| | The modified command ``%Ow`` produces the locale's alternative |
| | representation. |
+---------+--------------------------------------------------------------------+
| ``'W'`` | The week number of the year as a decimal number. The first Monday |
| | of the year is the first day of week 01. Days of the same year |
| | prior to that are in week 00. If the result is a single digit, it |
| | is prefixed with 0. The modified command ``%OW`` produces the |
| | locale's alternative representation. |
+---------+--------------------------------------------------------------------+
| ``'x'`` | The date representation, e.g. "11/12/55". The modified command |
| | ``%Ex`` produces the locale's alternate date representation. |
+---------+--------------------------------------------------------------------+
| ``'X'`` | The time representation, e.g. "10:04:00". The modified command |
| | ``%EX`` produces the locale's alternate time representation. |
+---------+--------------------------------------------------------------------+
| ``'y'`` | The last two decimal digits of the year. If the result is a single |
| | digit it is prefixed by 0. The modified command ``%Oy`` produces |
| | the locale's alternative representation. The modified command |
| | ``%Ey`` produces the locale's alternative representation of offset |
| | from ``%EC`` (year only). |
+---------+--------------------------------------------------------------------+
| ``'Y'`` | The year as a decimal number. If the result is less than four |
| | digits it is left-padded with 0 to four digits. The modified |
| | command ``%EY`` produces the locale's alternative full year |
| | representation. |
+---------+--------------------------------------------------------------------+
| ``'z'`` | The offset from UTC in the ISO 8601:2004 format. For example -0430 |
| | refers to 4 hours 30 minutes behind UTC. If the offset is zero, |
| | +0000 is used. The modified commands ``%Ez`` and ``%Oz`` insert a |
| | ``:`` between the hours and minutes: -04:30. If the offset |
| | information is not available, an exception of type |
| | ``format_error`` is thrown. |
+---------+--------------------------------------------------------------------+
| ``'Z'`` | The time zone abbreviation. If the time zone abbreviation is not |
| | available, an exception of type ``format_error`` is thrown. |
+---------+--------------------------------------------------------------------+
| ``'%'`` | A % character. |
+---------+--------------------------------------------------------------------+
Specifiers that have a calendaric component such as ``'d'`` (the day of month) Specifiers that have a calendaric component such as ``'d'`` (the day of month)
are valid only for ``std::tm`` and not durations or time points. are valid only for ``std::tm`` and time points but not durations.
.. range-specs: .. range-specs:
@ -366,12 +492,12 @@ By default, a range of characters or strings is printed escaped and quoted. But
if any `underlying_spec` is provided (even if it is empty), then the characters if any `underlying_spec` is provided (even if it is empty), then the characters
or strings are printed according to the provided specification. or strings are printed according to the provided specification.
Examples: Examples::
fmt::format("{}", std::vector{10, 20, 30}); fmt::format("{}", std::vector{10, 20, 30});
// Result: [10, 20, 30] // Result: [10, 20, 30]
fmt::format("{::#x}", std::vector{10, 20, 30}); fmt::format("{::#x}", std::vector{10, 20, 30});
// Result: [0xa, 0x14, 0x13] // Result: [0xa, 0x14, 0x1e]
fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'}); fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'});
// Result: ['h', 'e', 'l', 'l', 'o'] // Result: ['h', 'e', 'l', 'l', 'o']
fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'}); fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'});

View file

@ -1,4 +1,4 @@
// Formatting library for C++ - dynamic format arguments // Formatting library for C++ - dynamic argument lists
// //
// Copyright (c) 2012 - present, Victor Zverovich // Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved. // All rights reserved.

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
enum class color : uint32_t { enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255) alice_blue = 0xF0F8FF, // rgb(240,248,255)
@ -203,7 +203,7 @@ struct rgb {
uint8_t b; uint8_t b;
}; };
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
// color is a struct of either a rgb color or a terminal color. // color is a struct of either a rgb color or a terminal color.
struct color_type { struct color_type {
@ -225,8 +225,7 @@ struct color_type {
uint32_t rgb_color; uint32_t rgb_color;
} value; } value;
}; };
} // namespace detail
FMT_END_DETAIL_NAMESPACE
/** A text style consisting of foreground and background colors and emphasis. */ /** A text style consisting of foreground and background colors and emphasis. */
class text_style { class text_style {
@ -323,7 +322,7 @@ FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept {
return text_style(lhs) | rhs; return text_style(lhs) | rhs;
} }
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
template <typename Char> struct ansi_color_escape { template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
@ -423,26 +422,6 @@ FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
return ansi_color_escape<Char>(em); return ansi_color_escape<Char>(em);
} }
template <typename Char> inline void fputs(const Char* chars, FILE* stream) {
int result = std::fputs(chars, stream);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
template <> inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) {
int result = std::fputws(chars, stream);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
template <typename Char> inline void reset_color(FILE* stream) {
fputs("\x1b[0m", stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) {
fputs(L"\x1b[0m", stream);
}
template <typename Char> inline void reset_color(buffer<Char>& buffer) { template <typename Char> inline void reset_color(buffer<Char>& buffer) {
auto reset_color = string_view("\x1b[0m"); auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end()); buffer.append(reset_color.begin(), reset_color.end());
@ -477,19 +456,21 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
if (has_style) detail::reset_color<Char>(buf); if (has_style) detail::reset_color<Char>(buf);
} }
FMT_END_DETAIL_NAMESPACE } // namespace detail
template <typename S, typename Char = char_t<S>> inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
void vprint(std::FILE* f, const text_style& ts, const S& format, format_args args) {
basic_format_args<buffer_context<type_identity_t<Char>>> args) { // Legacy wide streams are not supported.
basic_memory_buffer<Char> buf; auto buf = memory_buffer();
detail::vformat_to(buf, ts, detail::to_string_view(format), args); detail::vformat_to(buf, ts, fmt, args);
if (detail::is_utf8()) { if (detail::is_utf8()) {
detail::print(f, basic_string_view<Char>(buf.begin(), buf.size())); detail::print(f, string_view(buf.begin(), buf.size()));
} else { return;
buf.push_back(Char(0));
detail::fputs(buf.data(), f);
} }
buf.push_back('\0');
int result = std::fputs(buf.data(), f);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
} }
/** /**
@ -566,7 +547,7 @@ OutputIt vformat_to(
basic_format_args<buffer_context<type_identity_t<Char>>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, ts, format_str, args); detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf); return detail::get_iterator(buf, out);
} }
/** /**
@ -634,7 +615,7 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
**Example**:: **Example**::
fmt::print("Elapsed time: {s:.2f} seconds", fmt::print("Elapsed time: {0:.2f} seconds",
fmt::styled(1.23, fmt::fg(fmt::color::green) | fmt::styled(1.23, fmt::fg(fmt::color::green) |
fmt::bg(fmt::color::blue))); fmt::bg(fmt::color::blue)));
\endrst \endrst
@ -645,7 +626,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts)
return detail::styled_arg<remove_cvref_t<T>>{value, ts}; return detail::styled_arg<remove_cvref_t<T>>{value, ts};
} }
FMT_MODULE_EXPORT_END FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_COLOR_H_ #endif // FMT_COLOR_H_

View file

@ -14,89 +14,11 @@ FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename Char, typename InputIt> template <typename Char, typename InputIt>
inline counting_iterator copy_str(InputIt begin, InputIt end, FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end,
counting_iterator it) { counting_iterator it) {
return it + (end - begin); return it + (end - begin);
} }
template <typename OutputIt> class truncating_iterator_base {
protected:
OutputIt out_;
size_t limit_;
size_t count_ = 0;
truncating_iterator_base() : out_(), limit_(0) {}
truncating_iterator_base(OutputIt out, size_t limit)
: out_(out), limit_(limit) {}
public:
using iterator_category = std::output_iterator_tag;
using value_type = typename std::iterator_traits<OutputIt>::value_type;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
FMT_UNCHECKED_ITERATOR(truncating_iterator_base);
OutputIt base() const { return out_; }
size_t count() const { return count_; }
};
// An output iterator that truncates the output and counts the number of objects
// written to it.
template <typename OutputIt,
typename Enable = typename std::is_void<
typename std::iterator_traits<OutputIt>::value_type>::type>
class truncating_iterator;
template <typename OutputIt>
class truncating_iterator<OutputIt, std::false_type>
: public truncating_iterator_base<OutputIt> {
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
public:
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
truncating_iterator& operator++() {
if (this->count_++ < this->limit_) ++this->out_;
return *this;
}
truncating_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
value_type& operator*() const {
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
}
};
template <typename OutputIt>
class truncating_iterator<OutputIt, std::true_type>
: public truncating_iterator_base<OutputIt> {
public:
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
template <typename T> truncating_iterator& operator=(T val) {
if (this->count_++ < this->limit_) *this->out_++ = val;
return *this;
}
truncating_iterator& operator++() { return *this; }
truncating_iterator& operator++(int) { return *this; }
truncating_iterator& operator*() { return *this; }
};
// A compile-time string which is compiled into fast formatting code. // A compile-time string which is compiled into fast formatting code.
class compiled_string {}; class compiled_string {};
@ -196,7 +118,8 @@ template <typename Char> struct code_unit {
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value); *out++ = value;
return out;
} }
}; };
@ -220,7 +143,12 @@ template <typename Char, typename T, int N> struct field {
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
return write<Char>(out, get_arg_checked<T, N>(args...)); const T& arg = get_arg_checked<T, N>(args...);
if constexpr (std::is_convertible_v<T, basic_string_view<Char>>) {
auto s = basic_string_view<Char>(arg);
return copy_str<Char>(s.begin(), s.end(), out);
}
return write<Char>(out, arg);
} }
}; };
@ -331,39 +259,35 @@ template <typename T, typename Char> struct parse_specs_result {
int next_arg_id; int next_arg_id;
}; };
constexpr int manual_indexing_id = -1; enum { manual_indexing_id = -1 };
template <typename T, typename Char> template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) { size_t pos, int next_arg_id) {
str.remove_prefix(pos); str.remove_prefix(pos);
auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {}, auto ctx =
next_arg_id); compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>(); auto f = formatter<T, Char>();
auto end = f.parse(ctx); auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, return {f, pos + fmt::detail::to_unsigned(end - str.data()),
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
} }
template <typename Char> struct arg_id_handler { template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
constexpr int operator()() { constexpr int on_auto() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing"); FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0; return 0;
} }
constexpr int operator()(int id) { constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
constexpr int operator()(basic_string_view<Char> id) { constexpr int on_name(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
constexpr void on_error(const char* message) {
FMT_THROW(format_error(message));
}
}; };
template <typename Char> struct parse_arg_id_result { template <typename Char> struct parse_arg_id_result {
@ -397,15 +321,22 @@ constexpr auto parse_replacement_field_then_tail(S format_str) {
return parse_tail<Args, END_POS + 1, NEXT_ID>( return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(), field<char_type, typename field_type<T>::type, ARG_INDEX>(),
format_str); format_str);
} else if constexpr (c == ':') { } else if constexpr (c != ':') {
FMT_THROW(format_error("expected ':'"));
} else {
constexpr auto result = parse_specs<typename field_type<T>::type>( constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
return parse_tail<Args, result.end, result.next_arg_id>( if constexpr (result.end >= str.size() || str[result.end] != '}') {
FMT_THROW(format_error("expected '}'"));
return 0;
} else {
return parse_tail<Args, result.end + 1, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{ spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt}, result.fmt},
format_str); format_str);
} }
} }
}
// Compiles a non-empty format string and returns the compiled representation // Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input. // or unknown_format() on unrecognized input.
@ -445,14 +376,13 @@ constexpr auto compile_format_string(S format_str) {
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
constexpr auto arg_index = constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
if constexpr (arg_index != invalid_arg_index) { if constexpr (arg_index >= 0) {
constexpr auto next_id = constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id; ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail< return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos, decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(format_str); arg_index, next_id>(format_str);
} else { } else if constexpr (c == '}') {
if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>( return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
format_str); format_str);
@ -461,7 +391,6 @@ constexpr auto compile_format_string(S format_str) {
} }
} }
} }
}
} else if constexpr (str[POS] == '}') { } else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
FMT_THROW(format_error("unmatched '}' in format string")); FMT_THROW(format_error("unmatched '}' in format string"));
@ -494,7 +423,7 @@ constexpr auto compile(S format_str) {
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail } // namespace detail
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
@ -561,14 +490,16 @@ template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const S& format_str, Args&&... args) { const S& format_str, Args&&... args) {
auto it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n), using traits = detail::fixed_buffer_traits;
format_str, std::forward<Args>(args)...); auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
return {it.base(), it.count()}; format_to(std::back_inserter(buf), format_str, std::forward<Args>(args)...);
return {buf.out(), buf.count()};
} }
template <typename S, typename... Args, template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
size_t formatted_size(const S& format_str, const Args&... args) { FMT_CONSTEXPR20 size_t formatted_size(const S& format_str,
const Args&... args) {
return fmt::format_to(detail::counting_iterator(), format_str, args...) return fmt::format_to(detail::counting_iterator(), format_str, args...)
.count(); .count();
} }
@ -597,7 +528,7 @@ template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
} // namespace literals } // namespace literals
#endif #endif
FMT_MODULE_EXPORT_END FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_ #endif // FMT_COMPILE_H_

File diff suppressed because it is too large Load diff

View file

@ -9,13 +9,9 @@
#define FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_
#include <algorithm> #include <algorithm>
#include <cctype>
#include <cerrno> // errno #include <cerrno> // errno
#include <climits> #include <climits>
#include <cmath> #include <cmath>
#include <cstdarg>
#include <cstring> // std::memmove
#include <cwchar>
#include <exception> #include <exception>
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
@ -115,16 +111,43 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.'; return '.';
} }
#endif #endif
FMT_FUNC auto write_loc(appender out, loc_value value,
const format_specs<>& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
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 std::use_facet<facet>(locale).put(out, value, specs);
return facet(locale).put(out, value, specs);
#endif
return false;
}
} // namespace detail } // namespace detail
#if !FMT_MSC_VERSION template <typename Locale> typename Locale::id format_facet<Locale>::id;
FMT_API FMT_FUNC format_error::~format_error() noexcept = default;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
grouping_ = numpunct.grouping();
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
}
template <>
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
appender out, loc_value val, const format_specs<>& specs) const -> bool {
return val.visit(
detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_});
}
#endif #endif
FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
format_args args) { format_args args) {
auto ec = std::error_code(error_code, std::generic_category()); auto ec = std::error_code(error_code, std::generic_category());
return std::system_error(ec, vformat(format_str, args)); return std::system_error(ec, vformat(fmt, args));
} }
namespace detail { namespace detail {
@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
return (n >> r) | (n << (64 - r)); return (n >> r) | (n << (64 - r));
} }
// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept {
#if FMT_USE_INT128
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)};
#elif defined(_MSC_VER) && defined(_M_X64)
auto result = uint128_fallback();
result.lo_ = _umul128(x, y, &result.hi_);
return result;
#else
const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>());
uint64_t a = x >> 32;
uint64_t b = x & mask;
uint64_t c = y >> 32;
uint64_t d = y & mask;
uint64_t ac = a * c;
uint64_t bc = b * c;
uint64_t ad = a * d;
uint64_t bd = b * d;
uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);
return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),
(intermediate << 32) + (bd & mask)};
#endif
}
// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
namespace dragonbox { namespace dragonbox {
// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept {
#if FMT_USE_INT128
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
return static_cast<uint64_t>(p >> 64);
#elif defined(_MSC_VER) && defined(_M_X64)
return __umulh(x, y);
#else
return umul128(x, y).high();
#endif
}
// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a
// 128-bit unsigned integer.
inline uint128_fallback umul192_upper128(uint64_t x,
uint128_fallback y) noexcept {
uint128_fallback r = umul128(x, y.high());
r += umul128_upper64(x, y.low());
return r;
}
// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
// 64-bit unsigned integer. // 64-bit unsigned integer.
inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept { inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
@ -216,25 +189,13 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
return x * y; return x * y;
} }
// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from
// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1.
inline int floor_log10_pow2(int e) noexcept {
FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent");
static_assert((-1 >> 1) == -1, "right shift is not arithmetic");
return (e * 315653) >> 20;
}
// Various fast log computations. // Various fast log computations.
inline int floor_log2_pow10(int e) noexcept {
FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
return (e * 1741647) >> 19;
}
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
return (e * 631305 - 261663) >> 21; return (e * 631305 - 261663) >> 21;
} }
static constexpr struct { FMT_INLINE_VARIABLE constexpr struct {
uint32_t divisor; uint32_t divisor;
int shift_amount; int shift_amount;
} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; } div_small_pow10_infos[] = {{10, 16}, {100, 16}};
@ -288,7 +249,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
} }
// Various subroutines using pow10 cache // Various subroutines using pow10 cache
template <class T> struct cache_accessor; template <typename T> struct cache_accessor;
template <> struct cache_accessor<float> { template <> struct cache_accessor<float> {
using carrier_uint = float_info<float>::carrier_uint; using carrier_uint = float_info<float>::carrier_uint;
@ -1009,8 +970,23 @@ template <> struct cache_accessor<double> {
{0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},
{0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},
{0xc5a05277621be293, 0xc7098b7305241886}, {0xc5a05277621be293, 0xc7098b7305241886},
{ 0xf70867153aa2db38, {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8},
0xb8cbee4fc66d1ea8 } {0x9a65406d44a5c903, 0x737f74f1dc043329},
{0xc0fe908895cf3b44, 0x505f522e53053ff3},
{0xf13e34aabb430a15, 0x647726b9e7c68ff0},
{0x96c6e0eab509e64d, 0x5eca783430dc19f6},
{0xbc789925624c5fe0, 0xb67d16413d132073},
{0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890},
{0x933e37a534cbaae7, 0x8e91b962f7b6f15a},
{0xb80dc58e81fe95a1, 0x723627bbb5a4adb1},
{0xe61136f2227e3b09, 0xcec3b1aaa30dd91d},
{0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2},
{0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e},
{0xe0accfa875af45a7, 0x93eb1b80a33b8606},
{0x8c6c01c9498d8b88, 0xbc72f130660533c4},
{0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
{ 0xdb68c2ca82ed2a05,
0xa67398db9f6820e2 }
#else #else
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
@ -1034,8 +1010,8 @@ template <> struct cache_accessor<double> {
{0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0x8da471a9de737e24, 0x5ceaecfed289e5d3},
{0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b},
{0xb8da1662e7b00a17, 0x3d6a751f3b936244}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244},
{ 0x95527a5202df0ccb, {0x95527a5202df0ccb, 0x0f37801e0c43ebc9},
0x0f37801e0c43ebc9 } {0xf13e34aabb430a15, 0x647726b9e7c68ff0}
#endif #endif
}; };
@ -1138,8 +1114,12 @@ template <> struct cache_accessor<double> {
} }
}; };
FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
return cache_accessor<double>::get_cached_power(k);
}
// Various integer checks // Various integer checks
template <class T> template <typename T>
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_lower_threshold = 2;
const int case_shorter_interval_left_endpoint_upper_threshold = 3; const int case_shorter_interval_left_endpoint_upper_threshold = 3;
@ -1148,12 +1128,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
} }
// Remove trailing zeros from n and return the number of zeros removed (float) // Remove trailing zeros from n and return the number of zeros removed (float)
FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
FMT_ASSERT(n != 0, ""); FMT_ASSERT(n != 0, "");
const uint32_t mod_inv_5 = 0xcccccccd; // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; constexpr uint32_t mod_inv_5 = 0xcccccccd;
constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5
int s = 0;
while (true) { while (true) {
auto q = rotr(n * mod_inv_25, 2); auto q = rotr(n * mod_inv_25, 2);
if (q > max_value<uint32_t>() / 100) break; if (q > max_value<uint32_t>() / 100) break;
@ -1165,7 +1145,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
n = q; n = q;
s |= 1; s |= 1;
} }
return s; return s;
} }
@ -1179,32 +1158,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
// Is n is divisible by 10^8? // Is n is divisible by 10^8?
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
// If yes, work with the quotient. // If yes, work with the quotient...
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64)); auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
// ... and use the 32 bit variant of the function
const uint32_t mod_inv_5 = 0xcccccccd; int s = remove_trailing_zeros(n32, 8);
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
int s = 8;
while (true) {
auto q = rotr(n32 * mod_inv_25, 2);
if (q > max_value<uint32_t>() / 100) break;
n32 = q;
s += 2;
}
auto q = rotr(n32 * mod_inv_5, 1);
if (q <= max_value<uint32_t>() / 10) {
n32 = q;
s |= 1;
}
n = n32; n = n32;
return s; return s;
} }
// If n is not divisible by 10^8, work with n itself. // If n is not divisible by 10^8, work with n itself.
const uint64_t mod_inv_5 = 0xcccccccccccccccd; constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd;
const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5; constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // = mod_inv_5 * mod_inv_5
int s = 0; int s = 0;
while (true) { while (true) {
@ -1223,7 +1187,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
} }
// The main algorithm for shorter interval case // The main algorithm for shorter interval case
template <class T> template <typename T>
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept { FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
decimal_fp<T> ret_value; decimal_fp<T> ret_value;
// Compute k and beta // Compute k and beta
@ -1337,7 +1301,7 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
if (r < deltai) { if (r < deltai) {
// Exclude the right endpoint if necessary. // Exclude the right endpoint if necessary.
if (r == 0 && z_mul.is_integer && !include_right_endpoint) { if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) {
--ret_value.significand; --ret_value.significand;
r = float_info<T>::big_divisor; r = float_info<T>::big_divisor;
goto small_divisor_case_label; goto small_divisor_case_label;
@ -1346,27 +1310,12 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
goto small_divisor_case_label; goto small_divisor_case_label;
} else { } else {
// r == deltai; compare fractional parts. // r == deltai; compare fractional parts.
const carrier_uint two_fl = two_fc - 1;
if (!include_left_endpoint ||
exponent < float_info<T>::case_fc_pm_half_lower_threshold ||
exponent > float_info<T>::divisibility_check_by_5_threshold) {
// If the left endpoint is not included, the condition for
// success is z^(f) < delta^(f) (odd parity).
// Otherwise, the inequalities on exponent ensure that
// x is not an integer, so if z^(f) >= delta^(f) (even parity), we in fact
// have strict inequality.
if (!cache_accessor<T>::compute_mul_parity(two_fl, cache, beta).parity) {
goto small_divisor_case_label;
}
} else {
const typename cache_accessor<T>::compute_mul_parity_result x_mul = const typename cache_accessor<T>::compute_mul_parity_result x_mul =
cache_accessor<T>::compute_mul_parity(two_fl, cache, beta); cache_accessor<T>::compute_mul_parity(two_fc - 1, cache, beta);
if (!x_mul.parity && !x_mul.is_integer) {
if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint)))
goto small_divisor_case_label; goto small_divisor_case_label;
} }
}
}
ret_value.exponent = minus_k + float_info<T>::kappa + 1; ret_value.exponent = minus_k + float_info<T>::kappa + 1;
// We may need to remove trailing zeros. // We may need to remove trailing zeros.
@ -1404,22 +1353,11 @@ small_divisor_case_label:
// or equivalently, when y is an integer. // or equivalently, when y is an integer.
if (y_mul.parity != approx_y_parity) if (y_mul.parity != approx_y_parity)
--ret_value.significand; --ret_value.significand;
else if (y_mul.is_integer && ret_value.significand % 2 != 0) else if (y_mul.is_integer & (ret_value.significand % 2 != 0))
--ret_value.significand; --ret_value.significand;
return ret_value; return ret_value;
} }
} // namespace dragonbox } // namespace dragonbox
#ifdef _MSC_VER
FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...)
-> int {
auto args = va_list();
va_start(args, fmt);
int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args);
va_end(args);
return result;
}
#endif
} // namespace detail } // namespace detail
template <> struct formatter<detail::bigint> { template <> struct formatter<detail::bigint> {
@ -1428,9 +1366,8 @@ template <> struct formatter<detail::bigint> {
return ctx.begin(); return ctx.begin();
} }
template <typename FormatContext> auto format(const detail::bigint& n, format_context& ctx) const
auto format(const detail::bigint& n, FormatContext& ctx) const -> -> format_context::iterator {
typename FormatContext::iterator {
auto out = ctx.out(); auto out = ctx.out();
bool first = true; bool first = true;
for (auto i = n.bigits_.size(); i > 0; --i) { for (auto i = n.bigits_.size(); i > 0; --i) {
@ -1488,53 +1425,45 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
return to_string(buffer); return to_string(buffer);
} }
#ifdef _WIN32
namespace detail { namespace detail {
#ifndef _WIN32
FMT_FUNC bool write_console(std::FILE*, string_view) { return false; }
#else
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>; using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
void*, const void*, dword, dword*, void*); void*, const void*, dword, dword*, void*);
} // namespace detail
#endif
namespace detail { FMT_FUNC bool write_console(std::FILE* f, string_view text) {
FMT_FUNC void print(std::FILE* f, string_view text) {
#ifdef _WIN32
auto fd = _fileno(f); auto fd = _fileno(f);
if (_isatty(fd)) { if (!_isatty(fd)) return false;
detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); auto u16 = utf8_to_utf16(text);
auto written = detail::dword(); auto written = dword();
if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),
u16.c_str(), static_cast<uint32_t>(u16.size()), static_cast<uint32_t>(u16.size()), &written, nullptr) != 0;
&written, nullptr)) {
return;
}
// Fallback to fwrite on failure. It can happen if the output has been
// redirected to NUL.
}
#endif
detail::fwrite_fully(text.data(), 1, text.size(), f);
}
} // namespace detail
FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
memory_buffer buffer;
detail::vformat_to(buffer, format_str, args);
detail::print(f, {buffer.data(), buffer.size()});
} }
#ifdef _WIN32
// Print assuming legacy (non-Unicode) encoding. // Print assuming legacy (non-Unicode) encoding.
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) {
format_args args) { auto buffer = memory_buffer();
memory_buffer buffer; detail::vformat_to(buffer, fmt,
detail::vformat_to(buffer, format_str,
basic_format_args<buffer_context<char>>(args)); basic_format_args<buffer_context<char>>(args));
fwrite_fully(buffer.data(), 1, buffer.size(), f); fwrite_fully(buffer.data(), 1, buffer.size(), f);
} }
#endif #endif
FMT_FUNC void vprint(string_view format_str, format_args args) { FMT_FUNC void print(std::FILE* f, string_view text) {
vprint(stdout, format_str, args); if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f);
}
} // namespace detail
FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args);
detail::print(f, {buffer.data(), buffer.size()});
}
FMT_FUNC void vprint(string_view fmt, format_args args) {
vprint(stdout, fmt, args);
} }
namespace detail { namespace detail {

File diff suppressed because it is too large Load diff

View file

@ -71,7 +71,7 @@
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
/** /**
\rst \rst
@ -120,51 +120,13 @@ template <typename Char> class basic_cstring_view {
using cstring_view = basic_cstring_view<char>; using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>; using wcstring_view = basic_cstring_view<wchar_t>;
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_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(),
basic_format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
#ifdef _WIN32 #ifdef _WIN32
FMT_API const std::error_category& system_category() noexcept; FMT_API const std::error_category& system_category() noexcept;
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
// A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 {
private:
memory_buffer buffer_;
public:
utf16_to_utf8() {}
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
operator string_view() const { return string_view(&buffer_[0], size()); }
size_t size() const { return buffer_.size() - 1; }
const char* c_str() const { return &buffer_[0]; }
std::string str() const { return std::string(&buffer_[0], size()); }
// Performs conversion returning a system error code instead of
// throwing exception on conversion error. This method may still throw
// in case of memory allocation error.
FMT_API int convert(basic_string_view<wchar_t> s);
};
FMT_API void format_windows_error(buffer<char>& out, int error_code, FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept; const char* message) noexcept;
FMT_END_DETAIL_NAMESPACE }
FMT_API std::system_error vwindows_error(int error_code, string_view format_str, FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
format_args args); format_args args);
@ -355,12 +317,18 @@ class FMT_API file {
// Creates a buffered_file object associated with this file and detaches // Creates a buffered_file object associated with this file and detaches
// this file object from the file. // this file object from the file.
buffered_file fdopen(const char* mode); buffered_file fdopen(const char* mode);
# if defined(_WIN32) && !defined(__MINGW32__)
// Opens a file and constructs a file object representing this file by
// wcstring_view filename. Windows only.
static file open_windows_file(wcstring_view path, int oflag);
# endif
}; };
// Returns the memory page size. // Returns the memory page size.
long getpagesize(); long getpagesize();
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
struct buffer_size { struct buffer_size {
buffer_size() = default; buffer_size() = default;
@ -397,56 +365,61 @@ struct ostream_params {
# endif # endif
}; };
FMT_END_DETAIL_NAMESPACE class file_buffer final : public buffer<char> {
file file_;
FMT_API void grow(size_t) override;
public:
FMT_API file_buffer(cstring_view path, const ostream_params& params);
FMT_API file_buffer(file_buffer&& other);
FMT_API ~file_buffer();
void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
void close() {
flush();
file_.close();
}
};
} // namespace detail
// Added {} below to work around default constructor error known to // Added {} below to work around default constructor error known to
// occur in Xcode versions 7.2.1 and 8.2.1. // occur in Xcode versions 7.2.1 and 8.2.1.
constexpr detail::buffer_size buffer_size{}; constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */ /** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> { class FMT_API ostream {
private: private:
file file_; FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
void grow(size_t) override;
ostream(cstring_view path, const detail::ostream_params& params) ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) { : buffer_(path, params) {}
set(new char[params.buffer_size], params.buffer_size);
}
public: public:
ostream(ostream&& other) ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
void flush() { ~ostream();
if (size() == 0) return;
file_.write(data(), size()); void flush() { buffer_.flush(); }
clear();
}
template <typename... T> template <typename... T>
friend ostream output_file(cstring_view path, T... params); friend ostream output_file(cstring_view path, T... params);
void close() { void close() { buffer_.close(); }
flush();
file_.close();
}
/** /**
Formats ``args`` according to specifications in ``fmt`` and writes the Formats ``args`` according to specifications in ``fmt`` and writes the
output to the file. output to the file.
*/ */
template <typename... T> void print(format_string<T...> fmt, T&&... args) { template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(detail::buffer_appender<char>(*this), fmt, vformat_to(detail::buffer_appender<char>(buffer_), fmt,
fmt::make_format_args(args...)); fmt::make_format_args(args...));
} }
}; };
@ -472,7 +445,7 @@ inline ostream output_file(cstring_view path, T... params) {
} }
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
FMT_MODULE_EXPORT_END FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_OS_H_ #endif // FMT_OS_H_

View file

@ -8,86 +8,65 @@
#ifndef FMT_OSTREAM_H_ #ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_ #define FMT_OSTREAM_H_
#include <fstream> #include <fstream> // std::filebuf
#include <ostream>
#if defined(_WIN32) && defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
# include <__std_stream>
#endif
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail { namespace detail {
// Checks if T has a user-defined operator<<.
template <typename T, typename Char, typename Enable = void>
class is_streamable {
private:
template <typename U>
static auto test(int)
-> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
<< std::declval<U>()) != 0>;
template <typename> static auto test(...) -> std::false_type;
using result = decltype(test<T>(0));
public:
is_streamable() = default;
static const bool value = result::value;
};
// Formatting of built-in types and arrays is intentionally disabled because
// it's handled by standard (non-ostream) formatters.
template <typename T, typename Char>
struct is_streamable<
T, Char,
enable_if_t<
std::is_arithmetic<T>::value || std::is_array<T>::value ||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
std::is_convertible<T, fmt::basic_string_view<Char>>::value ||
std::is_same<T, std_string_view<Char>>::value ||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
: std::false_type {};
template <typename Char> FILE* get_file(std::basic_filebuf<Char>&) {
return nullptr;
}
struct dummy_filebuf {
FILE* _Myfile;
};
template <typename T, typename U = int> struct ms_filebuf {
using type = dummy_filebuf;
};
template <typename T> struct ms_filebuf<T, decltype(T::_Myfile, 0)> {
using type = T;
};
using filebuf_type = ms_filebuf<std::filebuf>::type;
FILE* get_file(filebuf_type& buf);
// Generate a unique explicit instantion in every translation unit using a tag // Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace. // type in an anonymous namespace.
namespace { namespace {
struct filebuf_access_tag {}; struct file_access_tag {};
} // namespace } // namespace
template <typename Tag, typename FileMemberPtr, FileMemberPtr file> template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class filebuf_access { class file_access {
friend FILE* get_file(filebuf_type& buf) { return buf.*file; } friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
}; };
template class filebuf_access<filebuf_access_tag,
decltype(&filebuf_type::_Myfile),
&filebuf_type::_Myfile>;
inline bool write(std::filebuf& buf, fmt::string_view data) { #if FMT_MSC_VERSION
FILE* f = get_file(buf); template class file_access<file_access_tag, std::filebuf,
if (!f) return false; &std::filebuf::_Myfile>;
print(f, data); auto get_file(std::filebuf&) -> FILE*;
return true; #elif defined(_WIN32) && defined(_LIBCPP_VERSION)
template class file_access<file_access_tag, std::__stdoutbuf<char>,
&std::__stdoutbuf<char>::__file_>;
auto get_file(std::__stdoutbuf<char>&) -> FILE*;
#endif
inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
#if FMT_MSC_VERSION
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
if (FILE* f = get_file(*buf)) return write_console(f, data);
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
FILE* c_file;
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
c_file = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
c_file = fbuf->file();
else
return false;
if (c_file) return write_console(c_file, data);
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf()))
if (FILE* f = get_file(*buf)) return write_console(f, data);
#else
ignore_unused(os, data);
#endif
return false;
} }
inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) { inline bool write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) {
return false; return false;
} }
@ -95,10 +74,6 @@ inline bool write(std::wfilebuf&, fmt::basic_string_view<wchar_t>) {
// It is a separate function rather than a part of vprint to simplify testing. // It is a separate function rather than a part of vprint to simplify testing.
template <typename Char> template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
if (const_check(FMT_MSC_VERSION)) {
auto filebuf = dynamic_cast<std::basic_filebuf<Char>*>(os.rdbuf());
if (filebuf && write(*filebuf, {buf.data(), buf.size()})) return;
}
const Char* buf_data = buf.data(); const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size(); unsigned_streamsize size = buf.size();
@ -130,11 +105,13 @@ template <typename T> struct streamed_view { const T& value; };
// Formats an object of type T that has an overloaded ostream operator<<. // Formats an object of type T that has an overloaded ostream operator<<.
template <typename Char> template <typename Char>
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> { struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
void set_debug_format() = delete;
template <typename T, typename OutputIt> template <typename T, typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt { -> OutputIt {
auto buffer = basic_memory_buffer<Char>(); auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale()); detail::format_value(buffer, value, ctx.locale());
return formatter<basic_string_view<Char>, Char>::format( return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx); {buffer.data(), buffer.size()}, ctx);
} }
@ -142,12 +119,13 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
using ostream_formatter = basic_ostream_formatter<char>; using ostream_formatter = basic_ostream_formatter<char>;
template <typename T> template <typename T, typename Char>
struct formatter<detail::streamed_view<T>> : ostream_formatter { struct formatter<detail::streamed_view<T>, Char>
: basic_ostream_formatter<Char> {
template <typename OutputIt> template <typename OutputIt>
auto format(detail::streamed_view<T> view, auto format(detail::streamed_view<T> view,
basic_format_context<OutputIt, char>& ctx) const -> OutputIt { basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
return ostream_formatter::format(view.value, ctx); return basic_ostream_formatter<Char>::format(view.value, ctx);
} }
}; };
@ -168,21 +146,22 @@ auto streamed(const T& value) -> detail::streamed_view<T> {
namespace detail { namespace detail {
// Formats an object of type T that has an overloaded ostream operator<<. inline void vprint_directly(std::ostream& os, string_view format_str,
template <typename T, typename Char> format_args args) {
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> auto buffer = memory_buffer();
: basic_ostream_formatter<Char> { detail::vformat_to(buffer, format_str, args);
using basic_ostream_formatter<Char>::format; detail::write_buffer(os, buffer);
}; }
} // namespace detail } // namespace detail
FMT_MODULE_EXPORT template <typename Char> FMT_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os, void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str, basic_string_view<type_identity_t<Char>> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto buffer = basic_memory_buffer<Char>(); auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args); detail::vformat_to(buffer, format_str, args);
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
detail::write_buffer(os, buffer); detail::write_buffer(os, buffer);
} }
@ -195,12 +174,16 @@ void vprint(std::basic_ostream<Char>& os,
fmt::print(cerr, "Don't {}!", "panic"); fmt::print(cerr, "Don't {}!", "panic");
\endrst \endrst
*/ */
FMT_MODULE_EXPORT template <typename... T> FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) { void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
vprint(os, fmt, fmt::make_format_args(args...)); const auto& vargs = fmt::make_format_args(args...);
if (detail::is_utf8())
vprint(os, fmt, vargs);
else
detail::vprint_directly(os, fmt, vargs);
} }
FMT_MODULE_EXPORT FMT_EXPORT
template <typename... Args> template <typename... Args>
void print(std::wostream& os, void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt, basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
@ -208,6 +191,19 @@ void print(std::wostream& os,
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...)); vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
} }
FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_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 FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_ #endif // FMT_OSTREAM_H_

View file

@ -14,24 +14,18 @@
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter { printf_formatter() = delete; }; template <typename T> struct printf_formatter { printf_formatter() = delete; };
template <typename Char> template <typename Char> class basic_printf_context {
class basic_printf_parse_context : public basic_format_parse_context<Char> {
using basic_format_parse_context<Char>::basic_format_parse_context;
};
template <typename OutputIt, typename Char> class basic_printf_context {
private: private:
OutputIt out_; detail::buffer_appender<Char> out_;
basic_format_args<basic_printf_context> args_; basic_format_args<basic_printf_context> args_;
public: public:
using char_type = Char; using char_type = Char;
using format_arg = basic_format_arg<basic_printf_context>; using parse_context_type = basic_format_parse_context<Char>;
using parse_context_type = basic_printf_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>; template <typename T> using formatter_type = printf_formatter<T>;
/** /**
@ -40,68 +34,68 @@ template <typename OutputIt, typename Char> class basic_printf_context {
stored in the context object so make sure they have appropriate lifetimes. stored in the context object so make sure they have appropriate lifetimes.
\endrst \endrst
*/ */
basic_printf_context(OutputIt out, basic_printf_context(detail::buffer_appender<Char> out,
basic_format_args<basic_printf_context> args) basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {} : out_(out), args_(args) {}
OutputIt out() { return out_; } auto out() -> detail::buffer_appender<Char> { return out_; }
void advance_to(OutputIt it) { out_ = it; } void advance_to(detail::buffer_appender<Char>) {}
detail::locale_ref locale() { return {}; } auto locale() -> detail::locale_ref { return {}; }
format_arg arg(int id) const { return args_.get(id); } auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
}
FMT_CONSTEXPR void on_error(const char* message) { FMT_CONSTEXPR void on_error(const char* message) {
detail::error_handler().on_error(message); detail::error_handler().on_error(message);
} }
}; };
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
// Checks if a value fits in int - used to avoid warnings about comparing // Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers. // signed and unsigned integers.
template <bool IsSigned> struct int_checker { template <bool IsSigned> struct int_checker {
template <typename T> static bool fits_in_int(T value) { template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = max_value<int>(); unsigned max = max_value<int>();
return value <= max; return value <= max;
} }
static bool fits_in_int(bool) { return true; } static auto fits_in_int(bool) -> bool { return true; }
}; };
template <> struct int_checker<true> { template <> struct int_checker<true> {
template <typename T> static bool fits_in_int(T value) { template <typename T> static auto fits_in_int(T value) -> bool {
return value >= (std::numeric_limits<int>::min)() && return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>(); value <= max_value<int>();
} }
static bool fits_in_int(int) { return true; } static auto fits_in_int(int) -> bool { return true; }
}; };
class printf_precision_handler { struct printf_precision_handler {
public:
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
int operator()(T value) { auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
FMT_THROW(format_error("number is too big")); throw_format_error("number is too big");
return (std::max)(static_cast<int>(value), 0); return (std::max)(static_cast<int>(value), 0);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
int operator()(T) { auto operator()(T) -> int {
FMT_THROW(format_error("precision is not integer")); throw_format_error("precision is not integer");
return 0; return 0;
} }
}; };
// An argument visitor that returns true iff arg is a zero integer. // An argument visitor that returns true iff arg is a zero integer.
class is_zero_int { struct is_zero_int {
public:
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
bool operator()(T value) { auto operator()(T value) -> bool {
return value == 0; return value == 0;
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
bool operator()(T) { auto operator()(T) -> bool {
return false; return false;
} }
}; };
@ -132,22 +126,23 @@ template <typename T, typename Context> class arg_converter {
if (const_check(sizeof(target_type) <= sizeof(int))) { if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings. // Extra casts are used to silence warnings.
if (is_signed) { if (is_signed) {
arg_ = detail::make_arg<Context>( auto n = static_cast<int>(static_cast<target_type>(value));
static_cast<int>(static_cast<target_type>(value))); arg_ = detail::make_arg<Context>(n);
} else { } else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type; using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
arg_ = detail::make_arg<Context>( auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
static_cast<unsigned>(static_cast<unsigned_type>(value))); arg_ = detail::make_arg<Context>(n);
} }
} else { } else {
if (is_signed) { if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types: // glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254" // std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB. // but we don't have to do the same because it's a UB.
arg_ = detail::make_arg<Context>(static_cast<long long>(value)); auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else { } else {
arg_ = detail::make_arg<Context>( auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
static_cast<typename make_unsigned_or_bool<U>::type>(value)); arg_ = detail::make_arg<Context>(n);
} }
} }
} }
@ -175,8 +170,8 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) { void operator()(T value) {
arg_ = detail::make_arg<Context>( auto c = static_cast<typename Context::char_type>(value);
static_cast<typename Context::char_type>(value)); arg_ = detail::make_arg<Context>(c);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -186,68 +181,77 @@ template <typename Context> class char_converter {
// An argument visitor that return a pointer to a C string if argument is a // An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise. // string or null otherwise.
template <typename Char> struct get_cstring { template <typename Char> struct get_cstring {
template <typename T> const Char* operator()(T) { return nullptr; } template <typename T> auto operator()(T) -> const Char* { return nullptr; }
const Char* operator()(const Char* s) { return s; } auto operator()(const Char* s) -> const Char* { return s; }
}; };
// Checks if an argument is a valid printf width specifier and sets // Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative. // left alignment if it is negative.
template <typename Char> class printf_width_handler { template <typename Char> class printf_width_handler {
private: private:
using format_specs = basic_format_specs<Char>; format_specs<Char>& specs_;
format_specs& specs_;
public: public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {} explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) { auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value); auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) { if (detail::is_negative(value)) {
specs_.align = align::left; specs_.align = align::left;
width = 0 - width; width = 0 - width;
} }
unsigned int_max = max_value<int>(); unsigned int_max = max_value<int>();
if (width > int_max) FMT_THROW(format_error("number is too big")); if (width > int_max) throw_format_error("number is too big");
return static_cast<unsigned>(width); return static_cast<unsigned>(width);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
unsigned operator()(T) { auto operator()(T) -> unsigned {
FMT_THROW(format_error("width is not integer")); throw_format_error("width is not integer");
return 0; return 0;
} }
}; };
// Workaround for a bug with the XL compiler when initializing
// printf_arg_formatter's base class.
template <typename Char>
auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
-> arg_formatter<Char> {
return {iter, s, locale_ref()};
}
// The ``printf`` argument formatter. // The ``printf`` argument formatter.
template <typename OutputIt, typename Char> template <typename Char>
class printf_arg_formatter : public arg_formatter<Char> { class printf_arg_formatter : public arg_formatter<Char> {
private: private:
using base = arg_formatter<Char>; using base = arg_formatter<Char>;
using context_type = basic_printf_context<OutputIt, Char>; using context_type = basic_printf_context<Char>;
using format_specs = basic_format_specs<Char>;
context_type& context_; context_type& context_;
OutputIt write_null_pointer(bool is_string = false) { void write_null_pointer(bool is_string = false) {
auto s = this->specs; auto s = this->specs;
s.type = presentation_type::none; s.type = presentation_type::none;
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s); write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
} }
public: public:
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx) printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s,
: base{iter, s, locale_ref()}, context_(ctx) {} context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
OutputIt operator()(monostate value) { return base::operator()(value); } void operator()(monostate value) { base::operator()(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
OutputIt operator()(T value) { void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use // MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead. // std::is_same instead.
if (std::is_same<T, Char>::value) { if (!std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs; base::operator()(value);
return;
}
format_specs<Char> fmt_specs = this->specs;
if (fmt_specs.type != presentation_type::none && if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) { fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value)); return (*this)(static_cast<int>(value));
@ -259,49 +263,49 @@ class printf_arg_formatter : public arg_formatter<Char> {
// ignored for non-numeric types // ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right; fmt_specs.align = align::right;
return write<Char>(this->out, static_cast<Char>(value), fmt_specs); write<Char>(this->out, static_cast<Char>(value), fmt_specs);
}
return base::operator()(value);
} }
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
OutputIt operator()(T value) { void operator()(T value) {
return base::operator()(value); base::operator()(value);
} }
/** Formats a null-terminated C string. */ /** Formats a null-terminated C string. */
OutputIt operator()(const char* value) { void operator()(const char* value) {
if (value) return base::operator()(value); if (value)
return write_null_pointer(this->specs.type != presentation_type::pointer); base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
} }
/** Formats a null-terminated wide C string. */ /** Formats a null-terminated wide C string. */
OutputIt operator()(const wchar_t* value) { void operator()(const wchar_t* value) {
if (value) return base::operator()(value); if (value)
return write_null_pointer(this->specs.type != presentation_type::pointer); base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
} }
OutputIt operator()(basic_string_view<Char> value) { void operator()(basic_string_view<Char> value) { base::operator()(value); }
return base::operator()(value);
}
/** Formats a pointer. */ /** Formats a pointer. */
OutputIt operator()(const void* value) { void operator()(const void* value) {
return value ? base::operator()(value) : write_null_pointer(); if (value)
base::operator()(value);
else
write_null_pointer();
} }
/** Formats an argument of a custom (user-defined) type. */ /** Formats an argument of a custom (user-defined) type. */
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) { void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = auto parse_ctx = basic_format_parse_context<Char>({});
basic_printf_parse_context<Char>(basic_string_view<Char>());
handle.format(parse_ctx, context_); handle.format(parse_ctx, context_);
return this->out;
} }
}; };
template <typename Char> template <typename Char>
void parse_flags(basic_format_specs<Char>& specs, const Char*& it, void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
const Char* end) {
for (; it != end; ++it) { for (; it != end; ++it) {
switch (*it) { switch (*it) {
case '-': case '-':
@ -314,9 +318,7 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
specs.fill[0] = '0'; specs.fill[0] = '0';
break; break;
case ' ': case ' ':
if (specs.sign != sign::plus) { if (specs.sign != sign::plus) specs.sign = sign::space;
specs.sign = sign::space;
}
break; break;
case '#': case '#':
specs.alt = true; specs.alt = true;
@ -328,8 +330,8 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
} }
template <typename Char, typename GetArg> template <typename Char, typename GetArg>
int parse_header(const Char*& it, const Char* end, auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
basic_format_specs<Char>& specs, GetArg get_arg) { GetArg get_arg) -> int {
int arg_index = -1; int arg_index = -1;
Char c = *it; Char c = *it;
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
@ -344,7 +346,7 @@ int parse_header(const Char*& it, const Char* end,
if (value != 0) { if (value != 0) {
// Nonzero value means that we parsed width and don't need to // Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now. // parse it or flags again, so return now.
if (value == -1) FMT_THROW(format_error("number is too big")); if (value == -1) throw_format_error("number is too big");
specs.width = value; specs.width = value;
return arg_index; return arg_index;
} }
@ -355,7 +357,7 @@ int parse_header(const Char*& it, const Char* end,
if (it != end) { if (it != end) {
if (*it >= '0' && *it <= '9') { if (*it >= '0' && *it <= '9') {
specs.width = parse_nonnegative_int(it, end, -1); specs.width = parse_nonnegative_int(it, end, -1);
if (specs.width == -1) FMT_THROW(format_error("number is too big")); if (specs.width == -1) throw_format_error("number is too big");
} else if (*it == '*') { } else if (*it == '*') {
++it; ++it;
specs.width = static_cast<int>(visit_format_arg( specs.width = static_cast<int>(visit_format_arg(
@ -365,13 +367,53 @@ int parse_header(const Char*& it, const Char* end,
return arg_index; return arg_index;
} }
inline auto parse_printf_presentation_type(char c, type t)
-> presentation_type {
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':
return in(t, integral_set) ? pt::hex_lower : pt::none;
case 'X':
return in(t, integral_set) ? pt::hex_upper : pt::none;
case 'a':
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
case 'A':
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
case 'e':
return in(t, float_set) ? pt::exp_lower : pt::none;
case 'E':
return in(t, float_set) ? pt::exp_upper : pt::none;
case 'f':
return in(t, float_set) ? pt::fixed_lower : pt::none;
case 'F':
return in(t, float_set) ? pt::fixed_upper : pt::none;
case 'g':
return in(t, float_set) ? pt::general_lower : pt::none;
case 'G':
return in(t, float_set) ? pt::general_upper : 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;
}
}
template <typename Char, typename Context> template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format, void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) { basic_format_args<Context> args) {
using OutputIt = buffer_appender<Char>; using iterator = buffer_appender<Char>;
auto out = OutputIt(buf); auto out = iterator(buf);
auto context = basic_printf_context<OutputIt, Char>(out, args); auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_printf_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 // Returns the argument with specified index or, if arg_index is -1, the next
// argument. // argument.
@ -387,26 +429,24 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
const Char* end = parse_ctx.end(); const Char* end = parse_ctx.end();
auto it = start; auto it = start;
while (it != end) { while (it != end) {
if (!detail::find<false, Char>(it, end, '%', it)) { if (!find<false, Char>(it, end, '%', it)) {
it = end; // detail::find leaves it == nullptr if it doesn't find '%' it = end; // find leaves it == nullptr if it doesn't find '%'.
break; break;
} }
Char c = *it++; Char c = *it++;
if (it != end && *it == c) { if (it != end && *it == c) {
out = detail::write( write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
start = ++it; start = ++it;
continue; continue;
} }
out = detail::write(out, basic_string_view<Char>( write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
start, detail::to_unsigned(it - 1 - start)));
basic_format_specs<Char> specs; auto specs = format_specs<Char>();
specs.align = align::right; specs.align = align::right;
// Parse argument index, flags and width. // Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg); int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) parse_ctx.on_error("argument not found"); if (arg_index == 0) throw_format_error("argument not found");
// Parse precision. // Parse precision.
if (it != end && *it == '.') { if (it != end && *it == '.') {
@ -417,7 +457,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
} else if (c == '*') { } else if (c == '*') {
++it; ++it;
specs.precision = static_cast<int>( specs.precision = static_cast<int>(
visit_format_arg(detail::printf_precision_handler(), get_arg(-1))); visit_format_arg(printf_precision_handler(), get_arg(-1)));
} else { } else {
specs.precision = 0; specs.precision = 0;
} }
@ -426,20 +466,19 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto arg = get_arg(arg_index); auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is // For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored // specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) if (specs.precision >= 0 && arg.is_integral()) {
specs.fill[0] = // Ignore '0' for non-numeric types or if '-' present.
' '; // Ignore '0' flag for non-numeric types or if '-' present. specs.fill[0] = ' ';
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) { }
auto str = visit_format_arg(detail::get_cstring<Char>(), arg); if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = visit_format_arg(get_cstring<Char>(), arg);
auto str_end = str + specs.precision; auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char()); auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>( auto sv = basic_string_view<Char>(
basic_string_view<Char>( str, to_unsigned(nul != str_end ? nul - str : specs.precision));
str, detail::to_unsigned(nul != str_end ? nul - str arg = make_arg<basic_printf_context<Char>>(sv);
: specs.precision)));
} }
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg)) if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
specs.alt = false;
if (specs.fill[0] == '0') { if (specs.fill[0] == '0') {
if (arg.is_arithmetic() && specs.align != align::left) if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric; specs.align = align::numeric;
@ -451,7 +490,6 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
// Parse length and convert the argument to the required type. // Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0; c = it != end ? *it++ : 0;
Char t = it != end ? *it : 0; Char t = it != end ? *it : 0;
using detail::convert_arg;
switch (c) { switch (c) {
case 'h': case 'h':
if (t == 'h') { if (t == 'h') {
@ -490,7 +528,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
} }
// Parse type. // Parse type.
if (it == end) FMT_THROW(format_error("invalid format string")); if (it == end) throw_format_error("invalid format string");
char type = static_cast<char>(*it++); char type = static_cast<char>(*it++);
if (arg.is_integral()) { if (arg.is_integral()) {
// Normalize type. // Normalize type.
@ -500,32 +538,25 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
type = 'd'; type = 'd';
break; break;
case 'c': case 'c':
visit_format_arg( visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg);
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
arg);
break; break;
} }
} }
specs.type = parse_presentation_type(type); specs.type = parse_printf_presentation_type(type, arg.type());
if (specs.type == presentation_type::none) if (specs.type == presentation_type::none)
parse_ctx.on_error("invalid type specifier"); throw_format_error("invalid format specifier");
start = it; start = it;
// Format argument. // Format argument.
out = visit_format_arg( visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg);
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
} }
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start))); write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
} }
FMT_END_DETAIL_NAMESPACE } // namespace detail
template <typename Char> using printf_context = basic_printf_context<char>;
using basic_printf_context_t = using wprintf_context = basic_printf_context<wchar_t>;
basic_printf_context<detail::buffer_appender<Char>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;
using printf_args = basic_format_args<printf_context>; using printf_args = basic_format_args<printf_context>;
using wprintf_args = basic_format_args<wprintf_context>; using wprintf_args = basic_format_args<wprintf_context>;
@ -542,26 +573,21 @@ inline auto make_printf_args(const T&... args)
return {args...}; return {args...};
} }
/** // DEPRECATED!
\rst
Constructs an `~fmt::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmt::wprintf_args`.
\endrst
*/
template <typename... T> template <typename... T>
inline auto make_wprintf_args(const T&... args) inline auto make_wprintf_args(const T&... args)
-> format_arg_store<wprintf_context, T...> { -> format_arg_store<wprintf_context, T...> {
return {args...}; return {args...};
} }
template <typename S, typename Char = char_t<S>> template <typename Char>
inline auto vsprintf( inline auto vsprintf(
const S& fmt, basic_string_view<Char> fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
basic_memory_buffer<Char> buffer; auto buf = basic_memory_buffer<Char>();
vprintf(buffer, detail::to_string_view(fmt), args); detail::vprintf(buf, fmt, args);
return to_string(buffer); return to_string(buf);
} }
/** /**
@ -576,20 +602,19 @@ inline auto vsprintf(
template <typename S, typename... T, template <typename S, typename... T,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> { inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
using context = basic_printf_context_t<Char>;
return vsprintf(detail::to_string_view(fmt), return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<context>(args...)); fmt::make_format_args<basic_printf_context<Char>>(args...));
} }
template <typename S, typename Char = char_t<S>> template <typename Char>
inline auto vfprintf( inline auto vfprintf(
std::FILE* f, const S& fmt, std::FILE* f, basic_string_view<Char> fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int { -> int {
basic_memory_buffer<Char> buffer; auto buf = basic_memory_buffer<Char>();
vprintf(buffer, detail::to_string_view(fmt), args); detail::vprintf(buf, fmt, args);
size_t size = buffer.size(); size_t size = buf.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size return std::fwrite(buf.data(), sizeof(Char), size, f) < size
? -1 ? -1
: static_cast<int>(size); : static_cast<int>(size);
} }
@ -605,17 +630,16 @@ inline auto vfprintf(
*/ */
template <typename S, typename... T, typename Char = char_t<S>> template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
using context = basic_printf_context_t<Char>;
return vfprintf(f, detail::to_string_view(fmt), return vfprintf(f, detail::to_string_view(fmt),
fmt::make_format_args<context>(args...)); fmt::make_format_args<basic_printf_context<Char>>(args...));
} }
template <typename S, typename Char = char_t<S>> template <typename Char>
inline auto vprintf( FMT_DEPRECATED inline auto vprintf(
const S& fmt, basic_string_view<Char> fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int { -> int {
return vfprintf(stdout, detail::to_string_view(fmt), args); return vfprintf(stdout, fmt, args);
} }
/** /**
@ -627,14 +651,17 @@ inline auto vprintf(
fmt::printf("Elapsed time: %.2f seconds", 1.23); fmt::printf("Elapsed time: %.2f seconds", 1.23);
\endrst \endrst
*/ */
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)> template <typename... T>
inline auto printf(const S& fmt, const T&... args) -> int { inline auto printf(string_view fmt, const T&... args) -> int {
return vprintf( return vfprintf(stdout, fmt, make_printf_args(args...));
detail::to_string_view(fmt), }
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...)); template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_wprintf_args(args...));
} }
FMT_MODULE_EXPORT_END FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_ #endif // FMT_PRINTF_H_

View file

@ -22,27 +22,25 @@ FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename RangeT, typename OutputIterator> template <typename Range, typename OutputIt>
OutputIterator copy(const RangeT& range, OutputIterator out) { auto copy(const Range& range, OutputIt out) -> OutputIt {
for (auto it = range.begin(), end = range.end(); it != end; ++it) for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it; *out++ = *it;
return out; return out;
} }
template <typename OutputIterator> template <typename OutputIt>
OutputIterator copy(const char* str, OutputIterator out) { auto copy(const char* str, OutputIt out) -> OutputIt {
while (*str) *out++ = *str++; while (*str) *out++ = *str++;
return out; return out;
} }
template <typename OutputIterator> template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt {
OutputIterator copy(char ch, OutputIterator out) {
*out++ = ch; *out++ = ch;
return out; return out;
} }
template <typename OutputIterator> template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt {
OutputIterator copy(wchar_t ch, OutputIterator out) {
*out++ = ch; *out++ = ch;
return out; return out;
} }
@ -69,7 +67,7 @@ template <typename T> class is_map {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
#ifdef FMT_FORMAT_MAP_AS_LIST #ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
static constexpr const bool value = false; static constexpr const bool value = false;
#else #else
static constexpr const bool value = static constexpr const bool value =
@ -82,7 +80,7 @@ template <typename T> class is_set {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
#ifdef FMT_FORMAT_SET_AS_LIST #ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
static constexpr const bool value = false; static constexpr const bool value = false;
#else #else
static constexpr const bool value = static constexpr const bool value =
@ -157,8 +155,9 @@ template <typename T>
struct has_mutable_begin_end< struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())), T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_end(std::declval<T>())), decltype(detail::range_end(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>> // the extra int here is because older versions of MSVC don't
: std::true_type {}; // SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T> template <typename T>
struct is_range_<T, void> struct is_range_<T, void>
@ -211,41 +210,61 @@ class is_tuple_formattable_ {
static constexpr const bool value = false; static constexpr const bool value = false;
}; };
template <typename T, typename C> class is_tuple_formattable_<T, C, true> { template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <std::size_t... I> template <std::size_t... Is>
static std::true_type check2(index_sequence<I...>, static std::true_type check2(index_sequence<Is...>,
integer_sequence<bool, (I == I)...>); integer_sequence<bool, (Is == Is)...>);
static std::false_type check2(...); static std::false_type check2(...);
template <std::size_t... I> template <std::size_t... Is>
static decltype(check2( static decltype(check2(
index_sequence<I...>{}, index_sequence<Is...>{},
integer_sequence< integer_sequence<
bool, (is_formattable<typename std::tuple_element<I, T>::type, bool, (is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{})) check(index_sequence<I...>); C>::value)...>{})) check(index_sequence<Is...>);
public: public:
static constexpr const bool value = static constexpr const bool value =
decltype(check(tuple_index_sequence<T>{}))::value; decltype(check(tuple_index_sequence<T>{}))::value;
}; };
template <class Tuple, class F, size_t... Is> template <typename Tuple, typename F, size_t... Is>
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept { FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
using std::get; using std::get;
// using free function get<I>(T) now. // Using a free function get<Is>(Tuple) now.
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
(void)_; // blocks warnings ignore_unused(unused);
} }
template <class T> template <typename Tuple, typename F>
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes( FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
T const&) { for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
return {}; std::forward<Tuple>(t), std::forward<F>(f));
} }
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) { template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
const auto indexes = get_indexes(tup); void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); using std::get;
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
ignore_unused(unused);
} }
template <typename Tuple1, typename Tuple2, typename F>
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f));
}
namespace tuple {
// Workaround a bug in MSVC 2019 (v140).
template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
// Older MSVC doesn't get the reference type correctly for arrays. // Older MSVC doesn't get the reference type correctly for arrays.
template <typename R> struct range_reference_type_impl { template <typename R> struct range_reference_type_impl {
@ -269,45 +288,37 @@ using range_reference_type =
template <typename Range> template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>; using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Range> template <typename Formatter>
using uncvref_first_type = remove_cvref_t< FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
decltype(std::declval<range_reference_type<Range>>().first)>; -> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename Range> // These are not generic lambdas for compatibility with C++11.
using uncvref_second_type = remove_cvref_t< template <typename ParseContext> struct parse_empty_specs {
decltype(std::declval<range_reference_type<Range>>().second)>; template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) { template <typename T>
*out++ = ','; void operator()(const formatter<T, char_type>& f, const T& v) {
*out++ = ' '; if (i > 0)
return out; ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
ctx.advance_to(f.format(v, ctx));
++i;
} }
template <typename Char, typename OutputIt> int i;
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt { FormatContext& ctx;
return write_escaped_string(out, str); basic_string_view<char_type> separator;
} };
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
auto sv = std_string_view<Char>(str);
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
}
template <typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg v) {
return write_escaped_char(out, v);
}
template <
typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
!std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg& v) {
return write<Char>(out, v);
}
} // namespace detail } // namespace detail
@ -321,45 +332,58 @@ template <typename T, typename C> struct is_tuple_formattable {
detail::is_tuple_formattable_<T, C>::value; detail::is_tuple_formattable_<T, C>::value;
}; };
template <typename TupleT, typename Char> template <typename Tuple, typename Char>
struct formatter<TupleT, Char, struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<TupleT>::value && enable_if_t<fmt::is_tuple_like<Tuple>::value &&
fmt::is_tuple_formattable<TupleT, Char>::value>> { fmt::is_tuple_formattable<Tuple, Char>::value>> {
private: private:
// C++11 generic lambda for format(). decltype(detail::tuple::get_formatters<Tuple, Char>(
template <typename FormatContext> struct format_each { detail::tuple_index_sequence<Tuple>())) formatters_;
template <typename T> void operator()(const T& v) {
if (i > 0) out = detail::write_delimiter(out); basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
out = detail::write_range_entry<Char>(out, v); basic_string_view<Char> opening_bracket_ =
++i; detail::string_literal<Char, '('>{};
} basic_string_view<Char> closing_bracket_ =
int i; detail::string_literal<Char, ')'>{};
typename FormatContext::iterator& out;
};
public: public:
template <typename ParseContext> FMT_CONSTEXPR formatter() {}
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
} }
template <typename FormatContext = format_context> FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
auto format(const TupleT& values, FormatContext& ctx) const basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it != '}')
FMT_THROW(format_error("invalid format specifier"));
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
auto out = ctx.out(); ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
*out++ = '('; detail::for_each2(
detail::for_each(values, format_each<FormatContext>{0, out}); formatters_, value,
*out++ = ')'; detail::format_tuple_element<FormatContext>{0, ctx, separator_});
return out; return detail::copy_str<Char>(closing_bracket_, ctx.out());
} }
}; };
template <typename T, typename Char> struct is_range { template <typename T, typename Char> struct is_range {
static constexpr const bool value = static constexpr const bool value =
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value && detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
!detail::is_map<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value && !std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value; !std::is_convertible<T, detail::std_string_view<Char>>::value;
}; };
namespace detail { namespace detail {
@ -380,126 +404,171 @@ template <typename Context> struct range_mapper {
}; };
template <typename Char, typename Element> template <typename Char, typename Element>
using range_formatter_type = conditional_t< using range_formatter_type =
is_formattable<Element, Char>::value,
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map( formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
std::declval<Element>()))>, std::declval<Element>()))>,
Char>, Char>;
fallback_formatter<Element, Char>>;
template <typename R> template <typename R>
using maybe_const_range = using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>; 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 } // namespace detail
template <typename R, typename Char> template <typename T, typename Char, typename Enable = void>
struct formatter< struct range_formatter;
R, Char,
enable_if_t<
conjunction<fmt::is_range<R, Char>
// Workaround a bug in MSVC 2017 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
,
disjunction<
is_formattable<detail::uncvref_type<detail::maybe_const_range<R>>,
Char>,
detail::has_fallback_formatter<
detail::uncvref_type<detail::maybe_const_range<R>>, Char>
>
#endif
>::value
>> {
using range_type = detail::maybe_const_range<R>; template <typename T, typename Char>
using formatter_type = struct range_formatter<
detail::range_formatter_type<Char, detail::uncvref_type<range_type>>; T, Char,
formatter_type underlying_; enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
bool custom_specs_ = false; is_formattable<T, Char>>::value>> {
private:
detail::range_formatter_type<Char, T> underlying_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '['>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{};
public:
FMT_CONSTEXPR range_formatter() {}
FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {
return underlying_;
}
FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {
separator_ = sep;
}
FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,
basic_string_view<Char> close) {
opening_bracket_ = open;
closing_bracket_ = close;
}
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it != ':') if (it != end && *it == 'n') {
FMT_THROW(format_error("no top-level range formatters supported")); set_brackets({}, {});
custom_specs_ = true;
++it; ++it;
}
if (it != end && *it != '}') {
if (*it != ':') FMT_THROW(format_error("invalid format specifier"));
++it;
} else {
detail::maybe_set_debug_format(underlying_, true);
}
ctx.advance_to(it); ctx.advance_to(it);
return underlying_.parse(ctx); return underlying_.parse(ctx);
} }
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
detail::range_mapper<buffer_context<Char>> mapper;
auto out = ctx.out();
out = detail::copy_str<Char>(opening_bracket_, out);
int i = 0;
auto it = detail::range_begin(range);
auto end = detail::range_end(range);
for (; it != end; ++it) {
if (i > 0) out = detail::copy_str<Char>(separator_, out);
ctx.advance_to(out);
out = underlying_.format(mapper.map(*it), ctx);
++i;
}
out = detail::copy_str<Char>(closing_bracket_, out);
return out;
}
};
enum class range_format { disabled, map, set, sequence, string, debug_string };
namespace detail {
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
std::is_same<uncvref_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence> {};
template <range_format K, typename R, typename Char, typename Enable = void>
struct range_default_formatter;
template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>;
template <range_format K, typename R, typename Char>
struct range_default_formatter<
K, R, Char,
enable_if_t<(K == range_format::sequence || K == range_format::map ||
K == range_format::set)>> {
using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> underlying_;
FMT_CONSTEXPR range_default_formatter() { init(range_format_constant<K>()); }
FMT_CONSTEXPR void init(range_format_constant<range_format::set>) {
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
detail::string_literal<Char, '}'>{});
}
FMT_CONSTEXPR void init(range_format_constant<range_format::map>) {
underlying_.set_brackets(detail::string_literal<Char, '{'>{},
detail::string_literal<Char, '}'>{});
underlying_.underlying().set_brackets({}, {});
underlying_.underlying().set_separator(
detail::string_literal<Char, ':', ' '>{});
}
FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) {}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return underlying_.parse(ctx);
}
template <typename FormatContext> template <typename FormatContext>
auto format(range_type& range, FormatContext& ctx) const auto format(range_type& range, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
Char prefix = detail::is_set<R>::value ? '{' : '['; return underlying_.format(range, ctx);
Char postfix = detail::is_set<R>::value ? '}' : ']';
detail::range_mapper<buffer_context<Char>> mapper;
auto out = ctx.out();
*out++ = prefix;
int i = 0;
auto it = detail::range_begin(range);
auto end = detail::range_end(range);
for (; it != end; ++it) {
if (i > 0) out = detail::write_delimiter(out);
if (custom_specs_) {
ctx.advance_to(out);
out = underlying_.format(mapper.map(*it), ctx);
} else {
out = detail::write_range_entry<Char>(out, *it);
}
++i;
}
*out++ = postfix;
return out;
} }
}; };
} // namespace detail
template <typename T, typename Char> template <typename T, typename Char, typename Enable = void>
struct range_format_kind
: conditional_t<
is_range<T, Char>::value, detail::range_format_kind_<T>,
std::integral_constant<range_format, range_format::disabled>> {};
template <typename R, typename Char>
struct formatter< struct formatter<
T, Char, R, Char,
enable_if_t<conjunction<detail::is_map<T> enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value !=
// Workaround a bug in MSVC 2017 and earlier. range_format::disabled>
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 // Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
, ,
disjunction< detail::is_formattable_delayed<R, Char>
is_formattable<detail::uncvref_first_type<T>, Char>,
detail::has_fallback_formatter<detail::uncvref_first_type<T>, Char>
>,
disjunction<
is_formattable<detail::uncvref_second_type<T>, Char>,
detail::has_fallback_formatter<detail::uncvref_second_type<T>, Char>
>
#endif #endif
>::value >::value>>
>> { : detail::range_default_formatter<range_format_kind<R, Char>::value, R,
template <typename ParseContext> Char> {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <
typename FormatContext, typename U,
FMT_ENABLE_IF(
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
const T, T>>::value)>
auto format(U& map, FormatContext& ctx) const -> decltype(ctx.out()) {
auto out = ctx.out();
*out++ = '{';
int i = 0;
for (const auto& item : map) {
if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, item.first);
*out++ = ':';
*out++ = ' ';
out = detail::write_range_entry<Char>(out, item.second);
++i;
}
*out++ = '}';
return out;
}
}; };
template <typename Char, typename... T> struct tuple_join_view : detail::view { template <typename Char, typename... T> struct tuple_join_view : detail::view {
@ -510,9 +579,6 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
: tuple(t), sep{s} {} : tuple(t), sep{s} {}
}; };
template <typename Char, typename... T>
using tuple_arg_join = tuple_join_view<Char, T...>;
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with // support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision. // the dynamic width and precision.
@ -582,7 +648,45 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
} }
}; };
FMT_MODULE_EXPORT_BEGIN 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
/** /**
\rst \rst
@ -625,7 +729,7 @@ auto join(std::initializer_list<T> list, string_view sep)
return join(std::begin(list), std::end(list), sep); return join(std::begin(list), std::end(list), sep);
} }
FMT_MODULE_EXPORT_END FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_RANGES_H_ #endif // FMT_RANGES_H_

View file

@ -8,10 +8,18 @@
#ifndef FMT_STD_H_ #ifndef FMT_STD_H_
#define FMT_STD_H_ #define FMT_STD_H_
#include <atomic>
#include <bitset>
#include <cstdlib>
#include <exception>
#include <memory>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
#include <typeinfo>
#include <utility> #include <utility>
#include <vector>
#include "format.h"
#include "ostream.h" #include "ostream.h"
#if FMT_HAS_INCLUDE(<version>) #if FMT_HAS_INCLUDE(<version>)
@ -25,6 +33,30 @@
# if FMT_HAS_INCLUDE(<variant>) # if FMT_HAS_INCLUDE(<variant>)
# include <variant> # include <variant>
# endif # endif
# if FMT_HAS_INCLUDE(<optional>)
# include <optional>
# endif
#endif
// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some architectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMT_HAS_ABI_CXA_DEMANGLE
# endif
#endif
// Check if typeid is available.
#ifndef FMT_USE_TYPEID
// __RTTI is for EDG compilers. In MSVC typeid is available without RTTI.
# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || FMT_MSC_VERSION || \
defined(__INTEL_RTTI__) || defined(__RTTI)
# define FMT_USE_TYPEID 1
# else
# define FMT_USE_TYPEID 0
# endif
#endif #endif
#ifdef __cpp_lib_filesystem #ifdef __cpp_lib_filesystem
@ -32,21 +64,32 @@ FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename Char> auto get_path_string(const std::filesystem::path& p) {
return p.string<Char>();
}
template <typename Char> template <typename Char>
void write_escaped_path(basic_memory_buffer<Char>& quoted, void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p) { const std::filesystem::path& p) {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>()); write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
} }
# ifdef _WIN32 # ifdef _WIN32
template <> template <>
inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted, inline auto get_path_string<char>(const std::filesystem::path& p) {
const std::filesystem::path& p) { return to_utf8<wchar_t>(p.native(), to_utf8_error_policy::replace);
auto s = p.u8string();
write_escaped_string<char>(
std::back_inserter(quoted),
string_view(reinterpret_cast<const char*>(s.c_str()), s.size()));
} }
# endif
template <>
inline void write_escaped_path<char>(memory_buffer& quoted,
const std::filesystem::path& p) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
FMT_ASSERT(valid, "invalid utf16");
}
# endif // _WIN32
template <> template <>
inline void write_escaped_path<std::filesystem::path::value_type>( inline void write_escaped_path<std::filesystem::path::value_type>(
basic_memory_buffer<std::filesystem::path::value_type>& quoted, basic_memory_buffer<std::filesystem::path::value_type>& quoted,
@ -57,67 +100,118 @@ inline void write_escaped_path<std::filesystem::path::value_type>(
} // namespace detail } // namespace detail
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 FMT_EXPORT
// For MSVC 2017 and earlier using the partial specialization template <typename Char> struct formatter<std::filesystem::path, Char> {
// would cause an ambiguity error, therefore we provide it only private:
// conditionally. format_specs<Char> specs_;
template <typename Char> detail::arg_ref<Char> width_ref_;
struct formatter<std::filesystem::path, Char> bool debug_ = false;
: formatter<basic_string_view<Char>> {
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
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;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
return it;
}
template <typename FormatContext> template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const -> auto format(const std::filesystem::path& p, FormatContext& ctx) const {
typename FormatContext::iterator { auto specs = specs_;
basic_memory_buffer<Char> quoted; detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
}
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p); detail::write_escaped_path(quoted, p);
return formatter<basic_string_view<Char>>::format( return detail::write(ctx.out(),
basic_string_view<Char>(quoted.data(), quoted.size()), ctx); basic_string_view<Char>(quoted.data(), quoted.size()),
specs);
} }
}; };
#endif
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif #endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {}; struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE FMT_END_NAMESPACE
#ifdef __cpp_lib_variant #ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> struct formatter<std::monostate, Char> { FMT_EXPORT
template <typename ParseContext> template <typename T, typename Char>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { struct formatter<std::optional<T>, Char,
return ctx.begin(); std::enable_if_t<is_formattable<T, Char>::value>> {
private:
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:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const auto format(std::optional<T> const& opt, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out(); auto out = ctx.out();
out = detail::write<Char>(out, "monostate"); out = detail::write<Char>(out, optional);
return out; ctx.advance_to(out);
out = underlying_.format(*opt, ctx);
return detail::write(out, ')');
} }
}; };
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#ifdef __cpp_lib_variant
FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename T> template <typename T>
using variant_index_sequence = using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>; std::make_index_sequence<std::variant_size<T>::value>;
// variant_size and variant_alternative check. template <typename> struct is_variant_like_ : std::false_type {};
template <typename T, typename U = void> template <typename... Types>
struct is_variant_like_ : std::false_type {}; struct is_variant_like_<std::variant<Types...>> : std::true_type {};
template <typename T>
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
: std::true_type {};
// formattable element check // formattable element check.
template <typename T, typename C> class is_variant_formattable_ { template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... I> template <std::size_t... Is>
static std::conjunction< static std::conjunction<
is_formattable<std::variant_alternative_t<I, T>, C>...> is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<I...>); check(std::index_sequence<Is...>);
public: public:
static constexpr const bool value = static constexpr const bool value =
@ -145,6 +239,21 @@ template <typename T, typename C> struct is_variant_formattable {
detail::is_variant_formattable_<T, C>::value; detail::is_variant_formattable_<T, C>::value;
}; };
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const
-> decltype(ctx.out()) {
return detail::write<Char>(ctx.out(), "monostate");
}
};
FMT_EXPORT
template <typename Variant, typename Char> template <typename Variant, typename Char>
struct formatter< struct formatter<
Variant, Char, Variant, Char,
@ -161,16 +270,196 @@ struct formatter<
auto out = ctx.out(); auto out = ctx.out();
out = detail::write<Char>(out, "variant("); out = detail::write<Char>(out, "variant(");
FMT_TRY {
std::visit( std::visit(
[&](const auto& v) { [&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v); out = detail::write_variant_alternative<Char>(out, v);
}, },
value); value);
}
FMT_CATCH(const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')'; *out++ = ')';
return out; return out;
} }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // __cpp_lib_variant
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_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(), format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
FMT_EXPORT
template <typename T, typename Char>
struct formatter<
T, Char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
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;
if (*it == 't') {
++it;
with_typename_ = FMT_USE_TYPEID != 0;
}
return it;
}
template <typename OutputIt>
auto format(const std::exception& ex,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
format_specs<Char> spec;
auto out = ctx.out();
if (!with_typename_)
return detail::write_bytes(out, string_view(ex.what()), spec);
#if FMT_USE_TYPEID
const std::type_info& ti = typeid(ex);
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, decltype(&std::free)> 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());
}
out = detail::write_bytes(out, demangled_name_view, spec);
# elif FMT_MSC_VERSION
string_view demangled_name_view(ti.name());
if (demangled_name_view.starts_with("class "))
demangled_name_view.remove_prefix(6);
else if (demangled_name_view.starts_with("struct "))
demangled_name_view.remove_prefix(7);
out = detail::write_bytes(out, demangled_name_view, spec);
# else
out = detail::write_bytes(out, string_view(ti.name()), spec);
# endif
*out++ = ':';
*out++ = ' ';
return detail::write_bytes(out, string_view(ex.what()), spec);
#endif
}
};
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 #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>>
: formatter<bool, Char> {
template <typename FormatContext>
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v, ctx);
}
};
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>>
: formatter<T, Char> {
template <typename FormatContext>
auto format(const std::atomic<T>& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<T, Char>::format(v.load(), ctx);
}
};
#ifdef __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename Char>
struct formatter<std::atomic_flag, Char>
: formatter<bool, Char> {
template <typename FormatContext>
auto format(const std::atomic_flag& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v.test(), ctx);
}
};
#endif // __cpp_lib_atomic_flag_test
FMT_END_NAMESPACE
#endif // FMT_STD_H_ #endif // FMT_STD_H_

View file

@ -9,17 +9,35 @@
#define FMT_XCHAR_H_ #define FMT_XCHAR_H_
#include <cwchar> #include <cwchar>
#include <tuple>
#include "format.h" #include "format.h"
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename T> template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>; using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
}
FMT_MODULE_EXPORT_BEGIN inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out,
loc_value value, const format_specs<wchar_t>& specs,
locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
#endif
return false;
}
} // namespace detail
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>; using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>; using wformat_parse_context = basic_format_parse_context<wchar_t>;
@ -30,9 +48,13 @@ using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc. // Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view; template <typename... Args> using wformat_string = wstring_view;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else #else
template <typename... Args> template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<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 #endif
template <> struct is_char<wchar_t> : std::true_type {}; template <> struct is_char<wchar_t> : std::true_type {};
@ -40,9 +62,9 @@ template <> struct is_char<detail::char8_type> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {}; template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {}; template <> struct is_char<char32_t> : std::true_type {};
template <typename... Args> template <typename... T>
constexpr format_arg_store<wformat_context, Args...> make_wformat_args( constexpr format_arg_store<wformat_context, T...> make_wformat_args(
const Args&... args) { const T&... args) {
return {args...}; return {args...};
} }
@ -77,26 +99,22 @@ template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> format_str, auto vformat(basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
basic_memory_buffer<Char> buffer; auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args); detail::vformat_to(buf, format_str, args);
return to_string(buffer); return to_string(buf);
} }
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
#endif
template <typename... T> template <typename... T>
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring { auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
return vformat(fmt, fmt::make_wformat_args(args...)); return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
} }
// Pass char_t as a default template parameter instead of using // Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size. // std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... Args, typename Char = char_t<S>, template <typename S, typename... T, typename Char = char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value)> FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> { !std::is_same<Char, wchar_t>::value)>
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str), return vformat(detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...)); fmt::make_format_args<buffer_context<Char>>(args...));
} }
@ -111,11 +129,10 @@ inline auto vformat(
return detail::vformat(loc, detail::to_string_view(format_str), args); return detail::vformat(loc, detail::to_string_view(format_str), args);
} }
template <typename Locale, typename S, typename... Args, template <typename Locale, typename S, typename... T, typename Char = char_t<S>,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& format_str, Args&&... args) inline auto format(const Locale& loc, const S& format_str, T&&... args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str), return detail::vformat(loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...)); fmt::make_format_args<buffer_context<Char>>(args...));
@ -129,14 +146,14 @@ auto vformat_to(OutputIt out, const S& format_str,
-> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args); detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf); return detail::get_iterator(buf, out);
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt { inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
return vformat_to(out, detail::to_string_view(fmt), return vformat_to(out, detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...)); fmt::make_format_args<buffer_context<Char>>(args...));
} }
@ -152,18 +169,18 @@ inline auto vformat_to(
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args, vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc)); detail::locale_ref(loc));
return detail::get_iterator(buf); return detail::get_iterator(buf, out);
} }
template < template <
typename OutputIt, typename Locale, typename S, typename... Args, typename OutputIt, typename Locale, typename S, typename... T,
typename Char = char_t<S>, typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value&& bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value> detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str, inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
Args&&... args) -> T&&... args) ->
typename std::enable_if<enable, OutputIt>::type { typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, to_string_view(format_str), return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...)); fmt::make_format_args<buffer_context<Char>>(args...));
} }
@ -174,36 +191,36 @@ inline auto vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str, OutputIt out, size_t n, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out, using traits = detail::fixed_buffer_traits;
n); auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args); detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
const Args&... args) -> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
return vformat_to_n(out, n, detail::to_string_view(fmt), return vformat_to_n(out, n, detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...)); fmt::make_format_args<buffer_context<Char>>(args...));
} }
template <typename S, typename... Args, typename Char = char_t<S>, template <typename S, typename... T, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)> FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t { inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
detail::counting_buffer<Char> buf; auto buf = detail::counting_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), detail::vformat_to(buf, detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...)); fmt::make_format_args<buffer_context<Char>>(args...));
return buf.count(); return buf.count();
} }
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
wmemory_buffer buffer; auto buf = wmemory_buffer();
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buf, fmt, args);
buffer.push_back(L'\0'); buf.push_back(L'\0');
if (std::fputws(buffer.data(), f) == -1) if (std::fputws(buf.data(), f) == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
} }
@ -220,13 +237,22 @@ template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
} }
template <typename... T>
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
/** /**
Converts *value* to ``std::wstring`` using the default format for type *T*. Converts *value* to ``std::wstring`` using the default format for type *T*.
*/ */
template <typename T> inline auto to_wstring(const T& value) -> std::wstring { template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value); return format(FMT_STRING(L"{}"), value);
} }
FMT_MODULE_EXPORT_END FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_XCHAR_H_ #endif // FMT_XCHAR_H_

View file

@ -1,49 +1,44 @@
module; module;
#ifndef __cpp_modules
# error Module not supported.
#endif
// put all implementation-provided headers into the global module fragment
// to prevent attachment to this module
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
# define _CRT_SECURE_NO_WARNINGS
#endif
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
#endif
// Put all implementation-provided headers into the global module fragment
// to prevent attachment to this module.
#include <algorithm> #include <algorithm>
#include <cctype>
#include <cerrno> #include <cerrno>
#include <chrono> #include <chrono>
#include <climits> #include <climits>
#include <clocale>
#include <cmath> #include <cmath>
#include <cstdarg>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <ctime> #include <ctime>
#include <cwchar>
#include <exception> #include <exception>
#include <filesystem>
#include <fstream>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <locale> #include <locale>
#include <memory> #include <memory>
#include <optional>
#include <ostream> #include <ostream>
#include <sstream>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <system_error> #include <system_error>
#include <thread>
#include <type_traits> #include <type_traits>
#include <typeinfo>
#include <utility> #include <utility>
#include <variant>
#include <vector> #include <vector>
#include <version>
#if _MSC_VER #if __has_include(<cxxabi.h>)
# include <cxxabi.h>
#endif
#if defined(_MSC_VER) || defined(__MINGW32__)
# include <intrin.h> # include <intrin.h>
#endif #endif
#if defined __APPLE__ || defined(__FreeBSD__) #if defined __APPLE__ || defined(__FreeBSD__)
@ -65,22 +60,33 @@ module;
# endif # endif
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
# if defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# elif defined(_LIBCPP_VERSION)
# include <__std_stream>
# endif
# define WIN32_LEAN_AND_MEAN
# include <windows.h> # include <windows.h>
#endif #endif
export module fmt; export module fmt;
#define FMT_MODULE_EXPORT export #define FMT_EXPORT export
#define FMT_MODULE_EXPORT_BEGIN export { #define FMT_BEGIN_EXPORT export {
#define FMT_MODULE_EXPORT_END } #define FMT_END_EXPORT }
#define FMT_BEGIN_DETAIL_NAMESPACE \
} \ // If you define FMT_ATTACH_TO_GLOBAL_MODULE
namespace detail { // - all declarations are detached from module 'fmt'
#define FMT_END_DETAIL_NAMESPACE \ // - the module behaves like a traditional static library, too
} \ // - all library symbols are mangled traditionally
export { // - you can mix TUs with either importing or #including the {fmt} API
// all library-provided declarations and definitions #ifdef FMT_ATTACH_TO_GLOBAL_MODULE
// must be in the module purview to be exported extern "C++" {
#endif
// All library-provided declarations and definitions must be in the module
// purview to be exported.
#include "fmt/args.h" #include "fmt/args.h"
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include "fmt/color.h" #include "fmt/color.h"
@ -88,8 +94,13 @@ export module fmt;
#include "fmt/format.h" #include "fmt/format.h"
#include "fmt/os.h" #include "fmt/os.h"
#include "fmt/printf.h" #include "fmt/printf.h"
#include "fmt/std.h"
#include "fmt/xchar.h" #include "fmt/xchar.h"
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif
// gcc doesn't yet implement private module fragments // gcc doesn't yet implement private module fragments
#if !FMT_GCC_VERSION #if !FMT_GCC_VERSION
module : private; module : private;

View file

@ -28,12 +28,8 @@ template FMT_API auto decimal_point_impl(locale_ref) -> char;
template FMT_API void buffer<char>::append(const char*, const char*); template FMT_API void buffer<char>::append(const char*, const char*);
// DEPRECATED!
// There is no correspondent extern template in format.h because of
// incompatibility between clang and gcc (#2377).
template FMT_API void vformat_to(buffer<char>&, string_view, template FMT_API void vformat_to(buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>, typename vformat_args<>::type, locale_ref);
locale_ref);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.

View file

@ -18,6 +18,10 @@
# include <sys/stat.h> # include <sys/stat.h>
# include <sys/types.h> # include <sys/types.h>
# ifdef _WRS_KERNEL // VxWorks7 kernel
# include <ioLib.h> // getpagesize
# endif
# ifndef _WIN32 # ifndef _WIN32
# include <unistd.h> # include <unistd.h>
# else # else
@ -72,34 +76,6 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#ifdef _WIN32 #ifdef _WIN32
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
if (int error_code = convert(s)) {
FMT_THROW(windows_error(error_code,
"cannot convert string from UTF-16 to UTF-8"));
}
}
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size());
if (s_size == 0) {
// WideCharToMultiByte does not support zero length, handle separately.
buffer_.resize(1);
buffer_[0] = 0;
return 0;
}
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
nullptr, nullptr);
if (length == 0) return GetLastError();
buffer_.resize(length + 1);
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
length, nullptr, nullptr);
if (length == 0) return GetLastError();
buffer_[length] = 0;
return 0;
}
namespace detail { namespace detail {
class system_message { class system_message {
@ -138,10 +114,10 @@ class utf8_system_category final : public std::error_category {
public: public:
const char* name() const noexcept override { return "system"; } const char* name() const noexcept override { return "system"; }
std::string message(int error_code) const override { std::string message(int error_code) const override {
system_message msg(error_code); auto&& msg = system_message(error_code);
if (msg) { if (msg) {
utf16_to_utf8 utf8_message; auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg) == ERROR_SUCCESS) { if (utf8_message.convert(msg)) {
return utf8_message.str(); return utf8_message.str();
} }
} }
@ -165,11 +141,12 @@ std::system_error vwindows_error(int err_code, string_view format_str,
void detail::format_windows_error(detail::buffer<char>& out, int error_code, void detail::format_windows_error(detail::buffer<char>& out, int error_code,
const char* message) noexcept { const char* message) noexcept {
FMT_TRY { FMT_TRY {
system_message msg(error_code); auto&& msg = system_message(error_code);
if (msg) { if (msg) {
utf16_to_utf8 utf8_message; auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg) == ERROR_SUCCESS) { if (utf8_message.convert(msg)) {
fmt::format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message); fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
string_view(utf8_message));
return; return;
} }
} }
@ -192,37 +169,47 @@ buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
nullptr); nullptr);
if (!file_) if (!file_)
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str())); FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
filename.c_str()));
} }
void buffered_file::close() { void buffered_file::close() {
if (!file_) return; if (!file_) return;
int result = FMT_SYSTEM(fclose(file_)); int result = FMT_SYSTEM(fclose(file_));
file_ = nullptr; file_ = nullptr;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
} }
int buffered_file::descriptor() const { int buffered_file::descriptor() const {
#ifdef fileno // fileno is a macro on OpenBSD so we cannot use FMT_POSIX_CALL.
int fd = fileno(file_);
#else
int fd = FMT_POSIX_CALL(fileno(file_)); int fd = FMT_POSIX_CALL(fileno(file_));
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor")); #endif
if (fd == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
return fd; return fd;
} }
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
file::file(cstring_view path, int oflag) {
# ifdef _WIN32 # ifdef _WIN32
using mode_t = int; using mode_t = int;
# endif # endif
constexpr mode_t mode = constexpr mode_t default_open_mode =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
file::file(cstring_view path, int oflag) {
# if defined(_WIN32) && !defined(__MINGW32__) # if defined(_WIN32) && !defined(__MINGW32__)
fd_ = -1; fd_ = -1;
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
*this = file::open_windows_file(converted.c_str(), oflag);
# else # else
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
# endif
if (fd_ == -1) if (fd_ == -1)
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str())); FMT_THROW(
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
# endif
} }
file::~file() noexcept { file::~file() noexcept {
@ -238,7 +225,8 @@ void file::close() {
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
int result = FMT_POSIX_CALL(close(fd_)); int result = FMT_POSIX_CALL(close(fd_));
fd_ = -1; fd_ = -1;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
} }
long long file::size() const { long long file::size() const {
@ -260,7 +248,7 @@ long long file::size() const {
using Stat = struct stat; using Stat = struct stat;
Stat file_stat = Stat(); Stat file_stat = Stat();
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
FMT_THROW(system_error(errno, "cannot get file attributes")); FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
static_assert(sizeof(long long) >= sizeof(file_stat.st_size), static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
"return type of file::size is not large enough"); "return type of file::size is not large enough");
return file_stat.st_size; return file_stat.st_size;
@ -270,14 +258,16 @@ long long file::size() const {
std::size_t file::read(void* buffer, std::size_t count) { std::size_t file::read(void* buffer, std::size_t count) {
rwresult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file")); if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
return detail::to_unsigned(result); return detail::to_unsigned(result);
} }
std::size_t file::write(const void* buffer, std::size_t count) { std::size_t file::write(const void* buffer, std::size_t count) {
rwresult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file")); if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
return detail::to_unsigned(result); return detail::to_unsigned(result);
} }
@ -286,7 +276,8 @@ file file::dup(int fd) {
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd)); int new_fd = FMT_POSIX_CALL(dup(fd));
if (new_fd == -1) if (new_fd == -1)
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd)); FMT_THROW(system_error(
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
return file(new_fd); return file(new_fd);
} }
@ -294,8 +285,9 @@ void file::dup2(int fd) {
int result = 0; int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) { if (result == -1) {
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}", FMT_THROW(system_error(
fd_, fd)); errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
fd));
} }
} }
@ -320,7 +312,8 @@ void file::pipe(file& read_end, file& write_end) {
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
int result = FMT_POSIX_CALL(pipe(fds)); int result = FMT_POSIX_CALL(pipe(fds));
# endif # endif
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe")); if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
// The following assignments don't throw because read_fd and write_fd // The following assignments don't throw because read_fd and write_fd
// are closed. // are closed.
read_end = file(fds[0]); read_end = file(fds[0]);
@ -334,28 +327,72 @@ buffered_file file::fdopen(const char* mode) {
# else # else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode)); FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif # endif
if (!f) if (!f) {
FMT_THROW( FMT_THROW(system_error(
system_error(errno, "cannot associate stream with file descriptor")); errno, FMT_STRING("cannot associate stream with file descriptor")));
}
buffered_file bf(f); buffered_file bf(f);
fd_ = -1; fd_ = -1;
return bf; return bf;
} }
# if defined(_WIN32) && !defined(__MINGW32__)
file file::open_windows_file(wcstring_view path, int oflag) {
int fd = -1;
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
if (fd == -1) {
FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
detail::to_utf8<wchar_t>(path.c_str()).c_str()));
}
return file(fd);
}
# endif
# if !defined(__MSDOS__)
long getpagesize() { long getpagesize() {
# ifdef _WIN32 # ifdef _WIN32
SYSTEM_INFO si; SYSTEM_INFO si;
GetSystemInfo(&si); GetSystemInfo(&si);
return si.dwPageSize; return si.dwPageSize;
# else
# ifdef _WRS_KERNEL
long size = FMT_POSIX_CALL(getpagesize());
# else # else
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size")); # endif
if (size < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
return size; return size;
# endif # endif
} }
# endif
FMT_API void ostream::grow(size_t) { namespace detail {
void file_buffer::grow(size_t) {
if (this->size() == this->capacity()) flush(); if (this->size() == this->capacity()) flush();
} }
file_buffer::file_buffer(cstring_view path,
const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
file_buffer::file_buffer(file_buffer&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
file_buffer::~file_buffer() {
flush();
delete[] data();
}
} // namespace detail
ostream::~ostream() = default;
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
FMT_END_NAMESPACE FMT_END_NAMESPACE

View file

@ -13,8 +13,8 @@ Vagrant.configure("2") do |config|
config.vm.provision "shell", inline: <<-SHELL config.vm.provision "shell", inline: <<-SHELL
apt-get update apt-get update
apt-get install -y g++ make wget git apt-get install -y g++ make wget git
wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz tar xzf cmake-3.26.0-Linux-x86_64.tar.gz
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin
SHELL SHELL
end end

View file

@ -1,43 +0,0 @@
#!/usr/bin/env python
# Build the project on AppVeyor.
import os
from subprocess import check_call
build = os.environ['BUILD']
config = os.environ['CONFIGURATION']
platform = os.environ['PLATFORM']
path = os.environ['PATH']
image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE']
jobid = os.environ['APPVEYOR_JOB_ID']
cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..']
if build == 'mingw':
cmake_command.append('-GMinGW Makefiles')
build_command = ['mingw32-make', '-j4']
test_command = ['mingw32-make', 'test']
# Remove the path to Git bin directory from $PATH because it breaks
# MinGW config.
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
os.environ['PATH'] = r'C:\MinGW\bin;' + path
else:
# Add MSBuild 14.0 to PATH as described in
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path
if image == 'Visual Studio 2019':
generator = 'Visual Studio 16 2019'
if platform == 'x64':
cmake_command.extend(['-A', 'x64'])
else:
if image == 'Visual Studio 2015':
generator = 'Visual Studio 14 2015'
elif image == 'Visual Studio 2017':
generator = 'Visual Studio 15 2017'
if platform == 'x64':
generator += ' Win64'
cmake_command.append('-G' + generator)
build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
test_command = ['ctest', '-C', config]
check_call(cmake_command)
check_call(build_command)
check_call(test_command)

View file

@ -1,31 +0,0 @@
configuration:
- Debug
- Release
clone_depth: 1
image:
- Visual Studio 2015
platform:
- x64
environment:
CTEST_OUTPUT_ON_FAILURE: 1
MSVC_DEFAULT_OPTIONS: ON
BUILD: msvc
before_build:
- mkdir build
- cd build
build_script:
- python ../support/appveyor-build.py
on_failure:
- appveyor PushArtifact Testing/Temporary/LastTest.log
- appveyor AddTest test
# Uncomment this to debug AppVeyor failures.
#on_finish:
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

View file

@ -1 +0,0 @@
build --symlink_prefix=/ # Out of source build

View file

@ -1 +1 @@
5.1.1 6.1.2

View file

@ -11,8 +11,8 @@ cc_library(
"include/fmt/color.h", "include/fmt/color.h",
"include/fmt/compile.h", "include/fmt/compile.h",
"include/fmt/core.h", "include/fmt/core.h",
"include/fmt/format-inl.h",
"include/fmt/format.h", "include/fmt/format.h",
"include/fmt/format-inl.h",
"include/fmt/os.h", "include/fmt/os.h",
"include/fmt/ostream.h", "include/fmt/ostream.h",
"include/fmt/printf.h", "include/fmt/printf.h",

View file

@ -1,6 +1,6 @@
# Bazel support # Bazel support
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, 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}). To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.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 {fmt} as a dependency
@ -37,7 +37,6 @@ git_repository(
branch = "master", branch = "master",
remote = "https://github.com/fmtlib/fmt", remote = "https://github.com/fmtlib/fmt",
patch_cmds = [ patch_cmds = [
"mv support/bazel/.bazelrc .bazelrc",
"mv support/bazel/.bazelversion .bazelversion", "mv support/bazel/.bazelversion .bazelversion",
"mv support/bazel/BUILD.bazel BUILD.bazel", "mv support/bazel/BUILD.bazel BUILD.bazel",
"mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel", "mv support/bazel/WORKSPACE.bazel WORKSPACE.bazel",
@ -47,7 +46,6 @@ git_repository(
# https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes # https://docs.bazel.build/versions/main/install-windows.html#installing-compilers-and-language-runtimes
# Even if MSYS2 is installed the Windows related patch commands can still be used. # Even if MSYS2 is installed the Windows related patch commands can still be used.
patch_cmds_win = [ patch_cmds_win = [
"Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc",
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion", "Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel", "Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.bazel",
"Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel", "Move-Item -Path support/bazel/WORKSPACE.bazel -Destination WORKSPACE.bazel",
@ -55,7 +53,7 @@ git_repository(
) )
``` ```
In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, `.bazelrc`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace. In the *WORKSPACE* file, the {fmt} GitHub repository is fetched. Using the attribute `patch_cmds` the files `BUILD.bazel`, `WORKSPACE.bazel`, and `.bazelversion` are moved to the root of the {fmt} repository. This way the {fmt} repository is recognized as a bazelized workspace.
*BUILD.bazel*: *BUILD.bazel*:
@ -71,3 +69,6 @@ The *BUILD* file defines a binary named `Demo` that has a dependency to {fmt}.
To execute the binary you can run `bazel run //:Demo`. To execute the binary you can run `bazel run //:Demo`.
# Using Bzlmod
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) also provides support for {fmt}.

View file

@ -11,7 +11,7 @@ buildscript {
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
// //
// Notice that 4.0.0 here is the version of [Android Gradle Plugin] // Notice that 4.0.0 here is the version of [Android Gradle Plugin]
// Accroding to URL above you will need Gradle 6.1 or higher // According to URL above you will need Gradle 6.1 or higher
// //
classpath "com.android.tools.build:gradle:4.1.1" classpath "com.android.tools.build:gradle:4.1.1"
} }

View file

@ -1,54 +0,0 @@
# C++14 feature support detection
include(CheckCXXCompilerFlag)
function (fmt_check_cxx_compiler_flag flag result)
if (NOT MSVC)
check_cxx_compiler_flag("${flag}" ${result})
endif ()
endfunction ()
if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
endif()
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
if (CMAKE_CXX_STANDARD EQUAL 20)
fmt_check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
fmt_check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
if (has_std_20_flag)
set(CXX_STANDARD_FLAG -std=c++20)
elseif (has_std_2a_flag)
set(CXX_STANDARD_FLAG -std=c++2a)
endif ()
elseif (CMAKE_CXX_STANDARD EQUAL 17)
fmt_check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
fmt_check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
if (has_std_17_flag)
set(CXX_STANDARD_FLAG -std=c++17)
elseif (has_std_1z_flag)
set(CXX_STANDARD_FLAG -std=c++1z)
endif ()
elseif (CMAKE_CXX_STANDARD EQUAL 14)
fmt_check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
fmt_check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
if (has_std_14_flag)
set(CXX_STANDARD_FLAG -std=c++14)
elseif (has_std_1y_flag)
set(CXX_STANDARD_FLAG -std=c++1y)
endif ()
elseif (CMAKE_CXX_STANDARD EQUAL 11)
fmt_check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
fmt_check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
if (has_std_11_flag)
set(CXX_STANDARD_FLAG -std=c++11)
elseif (has_std_0x_flag)
set(CXX_STANDARD_FLAG -std=c++0x)
endif ()
endif ()

View file

@ -183,6 +183,12 @@ def update_site(env):
with rewrite(index) as b: with rewrite(index) as b:
b.data = b.data.replace( b.data = b.data.replace(
'doc/latest/index.html#format-string-syntax', 'syntax.html') 'doc/latest/index.html#format-string-syntax', 'syntax.html')
# Fix issues in syntax.rst.
index = os.path.join(target_doc_dir, 'syntax.rst')
with rewrite(index) as b:
b.data = b.data.replace(
'..productionlist:: sf\n', '.. productionlist:: sf\n ')
b.data = b.data.replace('Examples:\n', 'Examples::\n')
# Build the docs. # Build the docs.
html_dir = os.path.join(env.build_dir, 'html') html_dir = os.path.join(env.build_dir, 'html')
if os.path.exists(html_dir): if os.path.exists(html_dir):

View file

@ -138,6 +138,13 @@ class Translator(nodes.NodeVisitor):
def depart_table(self, node): def depart_table(self, node):
pass pass
def visit_system_message(self, node):
pass
def depart_system_message(self, node):
pass
class MDWriter(writers.Writer): class MDWriter(writers.Writer):
"""GitHub-flavored markdown writer""" """GitHub-flavored markdown writer"""

View file

@ -1,5 +1,7 @@
add_subdirectory(gtest) add_subdirectory(gtest)
include(CheckSymbolExists)
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
add_library(test-main STATIC ${TEST_MAIN_SRC}) add_library(test-main STATIC ${TEST_MAIN_SRC})
target_include_directories(test-main PUBLIC target_include_directories(test-main PUBLIC
@ -8,9 +10,6 @@ target_link_libraries(test-main gtest fmt)
function(add_fmt_executable name) function(add_fmt_executable name)
add_executable(${name} ${ARGN}) add_executable(${name} ${ARGN})
if (MINGW)
target_link_libraries(${name} -static-libgcc -static-libstdc++)
endif ()
# (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings # (Wstringop-overflow) - [meta-bug] bogus/missing -Wstringop-overflow warnings
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
# Bogus -Wstringop-overflow warning # Bogus -Wstringop-overflow warning
@ -19,6 +18,8 @@ function(add_fmt_executable name)
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95353
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND
NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) 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) target_link_libraries(${name} -Wno-stringop-overflow)
endif () endif ()
endfunction() endfunction()
@ -36,7 +37,7 @@ function(add_fmt_test name)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
endif () endif ()
elseif (ADD_FMT_TEST_MODULE) elseif (ADD_FMT_TEST_MODULE)
set(libs gtest test-module) set(libs test-main test-module)
set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module) set_source_files_properties(${name}.cc PROPERTIES OBJECT_DEPENDS test-module)
else () else ()
set(libs test-main fmt) set(libs test-main fmt)
@ -54,6 +55,10 @@ function(add_fmt_test name)
add_test(NAME ${name} COMMAND ${name}) add_test(NAME ${name} COMMAND ${name})
endfunction() endfunction()
if (FMT_MODULE)
return ()
endif ()
add_fmt_test(args-test) add_fmt_test(args-test)
add_fmt_test(assert-test) add_fmt_test(assert-test)
add_fmt_test(chrono-test) add_fmt_test(chrono-test)
@ -76,7 +81,13 @@ if (MSVC)
endif() endif()
add_fmt_test(printf-test) add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc) add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(scan-test) add_fmt_test(scan-test)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME)
if (HAVE_STRPTIME)
target_compile_definitions(scan-test PRIVATE FMT_HAVE_STRPTIME)
endif ()
add_fmt_test(std-test) add_fmt_test(std-test)
try_compile(compile_result_unused try_compile(compile_result_unused
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
@ -95,13 +106,13 @@ add_fmt_test(enforce-checks-test)
target_compile_definitions(enforce-checks-test PRIVATE target_compile_definitions(enforce-checks-test PRIVATE
-DFMT_ENFORCE_COMPILE_STRING) -DFMT_ENFORCE_COMPILE_STRING)
if (FMT_CAN_MODULE) if (FMT_MODULE)
# The tests need {fmt} to be compiled as traditional library # The tests need {fmt} to be compiled as traditional library
# because of visibility of implementation details. # because of visibility of implementation details.
# If module support is present the module tests require a # If module support is present the module tests require a
# test-only module to be built from {fmt} # test-only module to be built from {fmt}
add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc) add_library(test-module OBJECT ${CMAKE_SOURCE_DIR}/src/fmt.cc)
target_compile_features(test-module PUBLIC ${FMT_REQUIRED_FEATURES}) target_compile_features(test-module PUBLIC cxx_std_11)
target_include_directories(test-module PUBLIC target_include_directories(test-module PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>) $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
enable_module(test-module) enable_module(test-module)

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.8...3.25)
project(fmt-test CXX) project(fmt-test CXX)

View file

@ -44,7 +44,7 @@ template <> struct formatter<custom_type> {
template <typename FormatContext> template <typename FormatContext>
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) { auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
return format_to(ctx.out(), "cust={}", p.i); return fmt::format_to(ctx.out(), "cust={}", p.i);
} }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE

View file

@ -7,6 +7,7 @@
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include <algorithm>
#include <ctime> #include <ctime>
#include <vector> #include <vector>
@ -14,9 +15,15 @@
#include "util.h" // get_locale #include "util.h" // get_locale
using fmt::runtime; using fmt::runtime;
using testing::Contains; using testing::Contains;
#if defined(__MINGW32__) && !defined(_UCRT)
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
# define FMT_HAS_C99_STRFTIME 0
#else
# define FMT_HAS_C99_STRFTIME 1
#endif
auto make_tm() -> std::tm { auto make_tm() -> std::tm {
auto time = std::tm(); auto time = std::tm();
time.tm_mday = 1; time.tm_mday = 1;
@ -121,6 +128,13 @@ TEST(chrono_test, format_tm) {
make_tm(2000, 1, 2, 12, 14, 16), // W52 make_tm(2000, 1, 2, 12, 14, 16), // W52
make_tm(2000, 1, 3, 12, 14, 16) // W1 make_tm(2000, 1, 3, 12, 14, 16) // W1
}; };
#if !FMT_HAS_C99_STRFTIME
GTEST_SKIP() << "Skip the rest of this test because it relies on strftime() "
"conforming to C99, but on this platform, MINGW + MSVCRT, "
"the function conforms only to C89.";
#endif
const std::string iso_week_spec = "%Y-%m-%d: %G %g %V"; const std::string iso_week_spec = "%Y-%m-%d: %G %g %V";
for (auto ctm : tm_list) { for (auto ctm : tm_list) {
// Calculate tm_yday, tm_wday, etc. // Calculate tm_yday, tm_wday, etc.
@ -227,32 +241,135 @@ auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
lhs.tm_isdst == rhs.tm_isdst; lhs.tm_isdst == rhs.tm_isdst;
} }
TEST(chrono_test, localtime) {
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
}
TEST(chrono_test, gmtime) { TEST(chrono_test, gmtime) {
auto t = std::time(nullptr); auto t = std::time(nullptr);
auto tm = *std::gmtime(&t); auto tm = *std::gmtime(&t);
EXPECT_TRUE(equal(tm, fmt::gmtime(t))); EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
} }
template <typename TimePoint> auto strftime_full(TimePoint tp) -> std::string { template <typename TimePoint>
auto strftime_full_utc(TimePoint tp) -> std::string {
auto t = std::chrono::system_clock::to_time_t(tp); 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, 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));
using time_point =
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(42));
EXPECT_EQ(strftime_full_utc(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"};
#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");
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));
}
// 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("+0000", fmt::format("{:%z}", t1));
EXPECT_EQ("+0000", fmt::format("{:%z}", tm));
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", t1));
EXPECT_EQ("+00:00", fmt::format("{:%Ez}", tm));
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", t1));
EXPECT_EQ("+00:00", fmt::format("{:%Oz}", tm));
}
}
#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); auto tm = *std::localtime(&t);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm); return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
} }
TEST(chrono_test, time_point) { TEST(chrono_test, local_system_clock_time_point) {
auto t1 = std::chrono::system_clock::now(); # ifdef _WIN32
EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1)); return; // Not supported on Windows.
EXPECT_EQ(strftime_full(t1), fmt::format("{}", t1)); # endif
using time_point = auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>; std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
auto t2 = time_point(std::chrono::seconds(42)); EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); 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 = { std::vector<std::string> spec_list = {
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", "%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
@ -261,15 +378,20 @@ TEST(chrono_test, time_point) {
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH", "%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X", "%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"}; "%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
spec_list.push_back("%Y-%m-%d %H:%M:%S");
# ifndef _WIN32 # ifndef _WIN32
// Disabled on Windows because these formats are not consistent among // Disabled on Windows because these formats are not consistent among
// platforms. // platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"}); 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 # endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) { for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1); auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t); auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm); auto sys_output = system_strftime(spec, &tm);
@ -278,7 +400,25 @@ TEST(chrono_test, time_point) {
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1)); EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm)); 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 #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
@ -324,7 +464,7 @@ TEST(chrono_test, format_default) {
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42))); fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
} }
TEST(chrono_test, align) { TEST(chrono_test, duration_align) {
auto s = std::chrono::seconds(42); auto s = std::chrono::seconds(42);
EXPECT_EQ("42s ", fmt::format("{:5}", s)); EXPECT_EQ("42s ", fmt::format("{:5}", s));
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5)); EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
@ -340,6 +480,35 @@ TEST(chrono_test, align) {
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12)); fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
} }
TEST(chrono_test, tm_align) {
auto t = make_tm(1975, 12, 29, 12, 14, 16);
EXPECT_EQ("1975-12-29 12:14:16", fmt::format("{:%F %T}", t));
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:30%F %T}", t));
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:{}%F %T}", t, 30));
EXPECT_EQ("1975-12-29 12:14:16 ", fmt::format("{:<30%F %T}", t));
EXPECT_EQ(" 1975-12-29 12:14:16 ", fmt::format("{:^30%F %T}", t));
EXPECT_EQ(" 1975-12-29 12:14:16", fmt::format("{:>30%F %T}", t));
EXPECT_EQ("1975-12-29 12:14:16***********", fmt::format("{:*<30%F %T}", t));
EXPECT_EQ("*****1975-12-29 12:14:16******", fmt::format("{:*^30%F %T}", t));
EXPECT_EQ("***********1975-12-29 12:14:16", fmt::format("{:*>30%F %T}", t));
}
TEST(chrono_test, tp_align) {
auto tp = std::chrono::time_point_cast<std::chrono::microseconds>(
std::chrono::system_clock::from_time_t(0));
EXPECT_EQ("00:00.000000", fmt::format("{:%M:%S}", tp));
EXPECT_EQ("00:00.000000 ", fmt::format("{:15%M:%S}", tp));
EXPECT_EQ("00:00.000000 ", fmt::format("{:{}%M:%S}", tp, 15));
EXPECT_EQ("00:00.000000 ", fmt::format("{:<15%M:%S}", tp));
EXPECT_EQ(" 00:00.000000 ", fmt::format("{:^15%M:%S}", tp));
EXPECT_EQ(" 00:00.000000", fmt::format("{:>15%M:%S}", tp));
EXPECT_EQ("00:00.000000***", fmt::format("{:*<15%M:%S}", tp));
EXPECT_EQ("*00:00.000000**", fmt::format("{:*^15%M:%S}", tp));
EXPECT_EQ("***00:00.000000", fmt::format("{:*>15%M:%S}", tp));
}
TEST(chrono_test, format_specs) { TEST(chrono_test, format_specs) {
EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0))); EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0))); EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
@ -407,6 +576,10 @@ TEST(chrono_test, invalid_specs) {
"invalid format"); "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error, EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
"invalid format"); "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:abc}"), sec), fmt::format_error,
"invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.2f}"), sec), fmt::format_error,
"invalid format");
} }
auto format_tm(const std::tm& time, fmt::string_view spec, auto format_tm(const std::tm& time, fmt::string_view spec,
@ -454,7 +627,7 @@ TEST(chrono_test, format_default_fp) {
TEST(chrono_test, format_precision) { TEST(chrono_test, format_precision) {
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
(void)fmt::format(runtime("{:.2}"), std::chrono::seconds(42)), (void)fmt::format(runtime("{:.2%Q}"), std::chrono::seconds(42)),
fmt::format_error, "precision not allowed for this argument type"); fmt::format_error, "precision not allowed for this argument type");
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234))); EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
@ -557,9 +730,9 @@ TEST(chrono_test, special_durations) {
"03:33"); "03:33");
EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}), EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
"03:33:20"); "03:33:20");
EXPECT_EQ("44.000000000000", EXPECT_EQ("01.234",
fmt::format("{:%S}", std::chrono::duration<float, std::pico>( fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>(
1.54213895E+26))); 1.234e12)));
} }
TEST(chrono_test, unsigned_duration) { TEST(chrono_test, unsigned_duration) {
@ -599,12 +772,28 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}), EXPECT_EQ(fmt::format("{:%S}", std::chrono::nanoseconds{-13420148734}),
"-13.420148734"); "-13.420148734");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234"); EXPECT_EQ(fmt::format("{:%S}", std::chrono::milliseconds{1234}), "01.234");
// Check subsecond presision modifier.
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::nanoseconds{1234}),
"00.000001");
EXPECT_EQ(fmt::format("{:.18%S}", std::chrono::nanoseconds{1234}),
"00.000001234000000000");
EXPECT_EQ(fmt::format("{:.{}%S}", std::chrono::nanoseconds{1234}, 6),
"00.000001");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{1234}),
"01.234000");
EXPECT_EQ(fmt::format("{:.6%S}", std::chrono::milliseconds{-1234}),
"-01.234000");
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");
EXPECT_EQ(fmt::format("{:.8%S}", dms(1.234)), "00.00123400");
{ {
// Check that {:%H:%M:%S} is equivalent to {:%T}. // Check that {:%H:%M:%S} is equivalent to {:%T}.
auto dur = std::chrono::milliseconds{3601234}; auto dur = std::chrono::milliseconds{3601234};
auto formatted_dur = fmt::format("{:%T}", dur); auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "01:00:01.234"); EXPECT_EQ(formatted_dur, "01:00:01.234");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
} }
using nanoseconds_dbl = std::chrono::duration<double, std::nano>; 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{-123456789}), "-00.123456789");
@ -618,15 +807,197 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
auto formatted_dur = fmt::format("{:%T}", dur); auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "-00:01:39.123456789"); EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur); EXPECT_EQ(fmt::format("{:%H:%M:%S}", dur), formatted_dur);
EXPECT_EQ(fmt::format("{:.3%H:%M:%S}", dur), "-00:01:39.123");
} }
// Check that durations with precision greater than std::chrono::seconds have // Check that durations with precision greater than std::chrono::seconds have
// fixed precision, and print zeros even if there is no fractional part. // 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"); "07.000000");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 3>>(1)), EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
"00.333333"); "00.333333");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 7>>(1)), EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 7>>(1)),
"00.142857"); "00.142857");
EXPECT_EQ(
fmt::format("{:%S}",
std::chrono::duration<signed char, std::ratio<1, 100>>(0x80)),
"-01.28");
EXPECT_EQ(
fmt::format("{:%M:%S}",
std::chrono::duration<short, std::ratio<1, 100>>(0x8000)),
"-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}),
"01.500000");
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
"-01:01.250000");
} }
#endif // FMT_STATIC_THOUSANDS_SEPARATOR #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)
TEST(chrono_test, utc_clock) {
auto t1 = std::chrono::system_clock::now();
auto t1_utc = std::chrono::utc_clock::from_sys(t1);
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t1),
fmt::format("{:%Y-%m-%d %H:%M:%S}", t1_utc));
}
#endif
TEST(chrono_test, timestamps_ratios) {
std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>
t1(std::chrono::milliseconds(67890));
EXPECT_EQ(fmt::format("{:%M:%S}", t1), "01:07.890");
std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>
t2(std::chrono::minutes(7));
EXPECT_EQ(fmt::format("{:%M:%S}", t2), "07:00");
std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<int, std::ratio<9>>>
t3(std::chrono::duration<int, std::ratio<9>>(7));
EXPECT_EQ(fmt::format("{:%M:%S}", t3), "01:03");
std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<int, std::ratio<63>>>
t4(std::chrono::duration<int, std::ratio<63>>(1));
EXPECT_EQ(fmt::format("{:%M:%S}", t4), "01:03");
}
TEST(chrono_test, timestamps_sub_seconds) {
std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<long long, std::ratio<1, 3>>>
t1(std::chrono::duration<long long, std::ratio<1, 3>>(4));
EXPECT_EQ(fmt::format("{:%S}", t1), "01.333333");
std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<double, std::ratio<1, 3>>>
t2(std::chrono::duration<double, std::ratio<1, 3>>(4));
EXPECT_EQ(fmt::format("{:%S}", t2), "01.333333");
const std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>
t3(std::chrono::seconds(2));
EXPECT_EQ(fmt::format("{:%S}", t3), "02");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<double>>
t4(std::chrono::duration<double, std::ratio<1, 1>>(9.5));
EXPECT_EQ(fmt::format("{:%S}", t4), "09.500000");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::duration<double>>
t5(std::chrono::duration<double, std::ratio<1, 1>>(9));
EXPECT_EQ(fmt::format("{:%S}", t5), "09");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>
t6(std::chrono::seconds(1) + std::chrono::milliseconds(120));
EXPECT_EQ(fmt::format("{:%S}", t6), "01.120");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::microseconds>
t7(std::chrono::microseconds(1234567));
EXPECT_EQ(fmt::format("{:%S}", t7), "01.234567");
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::nanoseconds>
t8(std::chrono::nanoseconds(123456789));
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
const auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now());
const 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));
const std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>
t10(std::chrono::milliseconds(2000));
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
{
const auto epoch = std::chrono::time_point<std::chrono::system_clock,
std::chrono::milliseconds>();
const auto d = std::chrono::milliseconds(250);
EXPECT_EQ("59.750", fmt::format("{:%S}", epoch - d));
EXPECT_EQ("00.000", fmt::format("{:%S}", epoch));
EXPECT_EQ("00.250", fmt::format("{:%S}", epoch + d));
}
}
TEST(chrono_test, glibc_extensions) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%0}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%_}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%-}"), std::chrono::seconds()),
fmt::format_error, "invalid format");
{
const auto d = std::chrono::hours(1) + std::chrono::minutes(2) +
std::chrono::seconds(3);
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", d), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", d), "1,1,2,3");
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", d), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", d), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", d), "1,1,2,3");
}
{
const auto tm = make_tm(1970, 1, 1, 1, 2, 3);
EXPECT_EQ(fmt::format("{:%I,%H,%M,%S}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0I,%0H,%0M,%0S}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_I,%_H,%_M,%_S}", tm), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-I,%-H,%-M,%-S}", tm), "1,1,2,3");
EXPECT_EQ(fmt::format("{:%OI,%OH,%OM,%OS}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%0OI,%0OH,%0OM,%0OS}", tm), "01,01,02,03");
EXPECT_EQ(fmt::format("{:%_OI,%_OH,%_OM,%_OS}", tm), " 1, 1, 2, 3");
EXPECT_EQ(fmt::format("{:%-OI,%-OH,%-OM,%-OS}", tm), "1,1,2,3");
}
{
const auto d = std::chrono::seconds(3) + std::chrono::milliseconds(140);
EXPECT_EQ(fmt::format("{:%S}", d), "03.140");
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140");
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140");
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140");
}
{
const auto d = std::chrono::duration<double>(3.14);
EXPECT_EQ(fmt::format("{:%S}", d), "03.140000");
EXPECT_EQ(fmt::format("{:%0S}", d), "03.140000");
EXPECT_EQ(fmt::format("{:%_S}", d), " 3.140000");
EXPECT_EQ(fmt::format("{:%-S}", d), "3.140000");
}
}

View file

@ -1,6 +1,6 @@
# Test if compile errors are produced where necessary. # Test if compile errors are produced where necessary.
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.8...3.25)
project(compile-error-test CXX) project(compile-error-test CXX)
set(fmt_headers " set(fmt_headers "
@ -64,7 +64,7 @@ function (run_tests)
") ")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" " file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test/CMakeLists.txt" "
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.8...3.25)
project(tests CXX) project(tests CXX)
add_subdirectory(${FMT_DIR} fmt) add_subdirectory(${FMT_DIR} fmt)
${cmake_targets} ${cmake_targets}

View file

@ -12,6 +12,7 @@
defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \ defined(__cpp_constexpr) && __cpp_constexpr >= 201907 && \
defined(__cpp_constexpr_dynamic_alloc) && \ defined(__cpp_constexpr_dynamic_alloc) && \
__cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L __cpp_constexpr_dynamic_alloc >= 201907 && FMT_CPLUSPLUS >= 202002L
template <size_t max_string_length, typename Char = char> struct test_string { template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept { template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0; return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;

View file

@ -21,38 +21,6 @@ TEST(iterator_test, counting_iterator) {
EXPECT_EQ((it + 41).count(), 42); EXPECT_EQ((it + 41).count(), 42);
} }
TEST(iterator_test, truncating_iterator) {
char* p = nullptr;
auto it = fmt::detail::truncating_iterator<char*>(p, 3);
auto prev = it++;
EXPECT_EQ(prev.base(), p);
EXPECT_EQ(it.base(), p + 1);
}
TEST(iterator_test, truncating_iterator_default_construct) {
auto it = fmt::detail::truncating_iterator<char*>();
EXPECT_EQ(nullptr, it.base());
EXPECT_EQ(std::size_t{0}, it.count());
}
#ifdef __cpp_lib_ranges
TEST(iterator_test, truncating_iterator_is_output_iterator) {
static_assert(
std::output_iterator<fmt::detail::truncating_iterator<char*>, char>);
}
#endif
TEST(iterator_test, truncating_back_inserter) {
auto buffer = std::string();
auto bi = std::back_inserter(buffer);
auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2);
*it++ = '4';
*it++ = '2';
*it++ = '1';
EXPECT_EQ(buffer.size(), 2);
EXPECT_EQ(buffer, "42");
}
TEST(compile_test, compile_fallback) { TEST(compile_test, compile_fallback) {
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is // FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
// not available. // not available.
@ -227,10 +195,27 @@ TEST(compile_test, format_to_n) {
EXPECT_STREQ("2a", buffer); EXPECT_STREQ("2a", buffer);
} }
TEST(compile_test, formatted_size) { # ifdef __cpp_lib_bit_cast
EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42)); TEST(compile_test, constexpr_formatted_size) {
EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0)); FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
EXPECT_EQ(size, 2);
FMT_CONSTEXPR20 size_t hex_size =
fmt::formatted_size(FMT_COMPILE("{:x}"), 15);
EXPECT_EQ(hex_size, 1);
FMT_CONSTEXPR20 size_t binary_size =
fmt::formatted_size(FMT_COMPILE("{:b}"), 15);
EXPECT_EQ(binary_size, 4);
FMT_CONSTEXPR20 size_t padded_size =
fmt::formatted_size(FMT_COMPILE("{:*^6}"), 42);
EXPECT_EQ(padded_size, 6);
FMT_CONSTEXPR20 size_t float_size =
fmt::formatted_size(FMT_COMPILE("{:.3}"), 12.345);
EXPECT_EQ(float_size, 4);
FMT_CONSTEXPR20 size_t str_size =
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
EXPECT_EQ(str_size, 3);
} }
# endif
TEST(compile_test, text_and_arg) { TEST(compile_test, text_and_arg) {
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
@ -301,6 +286,7 @@ TEST(compile_test, compile_format_string_literal) {
// 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp', // 'D:\a\_work\1\s\src\vctools\Compiler\CxxFE\sl\p1\c\constexpr\constexpr.cpp',
// line 8635) // line 8635)
#if ((FMT_CPLUSPLUS >= 202002L) && \ #if ((FMT_CPLUSPLUS >= 202002L) && \
(!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \ (!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
(FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
template <size_t max_string_length, typename Char = char> struct test_string { template <size_t max_string_length, typename Char = char> struct test_string {

View file

@ -70,6 +70,16 @@ TEST(string_view_test, compare) {
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0); EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
EXPECT_GT(string_view("foo").compare(string_view("fo")), 0); EXPECT_GT(string_view("foo").compare(string_view("fo")), 0);
EXPECT_LT(string_view("fo").compare(string_view("foo")), 0); EXPECT_LT(string_view("fo").compare(string_view("foo")), 0);
EXPECT_TRUE(string_view("foo").starts_with('f'));
EXPECT_FALSE(string_view("foo").starts_with('o'));
EXPECT_FALSE(string_view().starts_with('o'));
EXPECT_TRUE(string_view("foo").starts_with("fo"));
EXPECT_TRUE(string_view("foo").starts_with("foo"));
EXPECT_FALSE(string_view("foo").starts_with("fooo"));
EXPECT_FALSE(string_view().starts_with("fooo"));
check_op<std::equal_to>(); check_op<std::equal_to>();
check_op<std::not_equal_to>(); check_op<std::not_equal_to>();
check_op<std::less>(); check_op<std::less>();
@ -78,24 +88,6 @@ TEST(string_view_test, compare) {
check_op<std::greater_equal>(); check_op<std::greater_equal>();
} }
namespace test_ns {
template <typename Char> class test_string {
private:
std::basic_string<Char> s_;
public:
test_string(const Char* s) : s_(s) {}
const Char* data() const { return s_.data(); }
size_t length() const { return s_.size(); }
operator const Char*() const { return s_.c_str(); }
};
template <typename Char>
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
return {s.data(), s.length()};
}
} // namespace test_ns
TEST(core_test, is_output_iterator) { TEST(core_test, is_output_iterator) {
EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value)); 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<const char*, char>::value));
@ -149,7 +141,7 @@ TEST(buffer_test, indestructible) {
} }
template <typename T> struct mock_buffer final : buffer<T> { template <typename T> struct mock_buffer final : buffer<T> {
MOCK_METHOD1(do_grow, size_t(size_t capacity)); MOCK_METHOD(size_t, do_grow, (size_t));
void grow(size_t capacity) override { void grow(size_t capacity) override {
this->set(this->data(), do_grow(capacity)); this->set(this->data(), do_grow(capacity));
@ -281,7 +273,7 @@ struct custom_context {
bool called = false; bool called = false;
template <typename T> struct formatter_type { template <typename T> struct formatter_type {
auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@ -298,11 +290,11 @@ struct test_struct {};
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> struct formatter<test_struct, Char> { template <typename Char> struct formatter<test_struct, Char> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) { auto format(test_struct, format_context& ctx) const -> decltype(ctx.out()) {
auto test = string_view("test"); auto test = string_view("test");
return std::copy_n(test.data(), test.size(), ctx.out()); return std::copy_n(test.data(), test.size(), ctx.out());
} }
@ -316,7 +308,7 @@ TEST(arg_test, format_args) {
TEST(arg_test, make_value_with_custom_context) { TEST(arg_test, make_value_with_custom_context) {
auto t = test_struct(); auto t = test_struct();
fmt::detail::value<custom_context> arg( auto arg = fmt::detail::value<custom_context>(
fmt::detail::arg_mapper<custom_context>().map(t)); fmt::detail::arg_mapper<custom_context>().map(t));
auto ctx = custom_context(); auto ctx = custom_context();
auto parse_ctx = fmt::format_parse_context(""); auto parse_ctx = fmt::format_parse_context("");
@ -335,12 +327,12 @@ template <typename T> struct mock_visitor {
ON_CALL(*this, visit(_)).WillByDefault(Return(test_result())); ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
} }
MOCK_METHOD1_T(visit, test_result(T value)); MOCK_METHOD(test_result, visit, (T));
MOCK_METHOD0_T(unexpected, void()); MOCK_METHOD(void, unexpected, ());
test_result operator()(T value) { return visit(value); } auto operator()(T value) -> test_result { return visit(value); }
template <typename U> test_result operator()(U) { template <typename U> auto operator()(U) -> test_result {
unexpected(); unexpected();
return test_result(); return test_result();
} }
@ -369,10 +361,11 @@ VISIT_TYPE(unsigned long, unsigned long long);
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \ testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
EXPECT_CALL(visitor, visit(expected)); \ EXPECT_CALL(visitor, visit(expected)); \
using iterator = std::back_insert_iterator<buffer<Char>>; \ using iterator = std::back_insert_iterator<buffer<Char>>; \
auto var = value; \
fmt::visit_format_arg( \ fmt::visit_format_arg( \
visitor, \ visitor, \
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \ fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \
value)); \ var)); \
} }
#define CHECK_ARG_SIMPLE(value) \ #define CHECK_ARG_SIMPLE(value) \
@ -392,13 +385,13 @@ using test_types =
TYPED_TEST_SUITE(numeric_arg_test, test_types); TYPED_TEST_SUITE(numeric_arg_test, test_types);
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0> template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
T test_value() { auto test_value() -> T {
return static_cast<T>(42); return static_cast<T>(42);
} }
template <typename T, template <typename T,
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0> fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
T test_value() { auto test_value() -> T {
return static_cast<T>(4.2); return static_cast<T>(4.2);
} }
@ -441,8 +434,8 @@ TEST(arg_test, pointer_arg) {
} }
struct check_custom { struct check_custom {
test_result operator()( auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
fmt::basic_format_arg<fmt::format_context>::handle h) const { -> test_result {
struct test_buffer final : fmt::detail::buffer<char> { struct test_buffer final : fmt::detail::buffer<char> {
char data[10]; char data[10];
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {} test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
@ -461,13 +454,13 @@ TEST(arg_test, custom_arg) {
auto test = test_struct(); auto test = test_struct();
using visitor = using visitor =
mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>; mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
testing::StrictMock<visitor> v; auto&& v = testing::StrictMock<visitor>();
EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom())); EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test)); fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test));
} }
TEST(arg_test, visit_invalid_arg) { TEST(arg_test, visit_invalid_arg) {
testing::StrictMock<mock_visitor<fmt::monostate>> visitor; auto&& visitor = testing::StrictMock<mock_visitor<fmt::monostate>>();
EXPECT_CALL(visitor, visit(_)); EXPECT_CALL(visitor, visit(_));
auto arg = fmt::basic_format_arg<fmt::format_context>(); auto arg = fmt::basic_format_arg<fmt::format_context>();
fmt::visit_format_arg(visitor, arg); fmt::visit_format_arg(visitor, arg);
@ -475,185 +468,78 @@ TEST(arg_test, visit_invalid_arg) {
#if FMT_USE_CONSTEXPR #if FMT_USE_CONSTEXPR
enum class arg_id_result { none, empty, index, name, error }; enum class arg_id_result { none, empty, index, name };
struct test_arg_id_handler { struct test_arg_id_handler {
arg_id_result res = arg_id_result::none; arg_id_result res = arg_id_result::none;
int index = 0; int index = 0;
string_view name; string_view name;
constexpr void operator()() { res = arg_id_result::empty; } constexpr void on_auto() { res = arg_id_result::empty; }
constexpr void operator()(int i) { constexpr void on_index(int i) {
res = arg_id_result::index; res = arg_id_result::index;
index = i; index = i;
} }
constexpr void operator()(string_view n) { constexpr void on_name(string_view n) {
res = arg_id_result::name; res = arg_id_result::name;
name = n; name = n;
} }
constexpr void on_error(const char*) { res = arg_id_result::error; }
}; };
template <size_t N> template <size_t N>
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) { constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
test_arg_id_handler h; auto h = test_arg_id_handler();
fmt::detail::parse_arg_id(s, s + N, h); fmt::detail::parse_arg_id(s, s + N, h);
return h; return h;
} }
TEST(format_test, constexpr_parse_arg_id) { TEST(core_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("}").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:").res == arg_id_result::index, "");
static_assert(parse_arg_id("42:").index == 42, ""); static_assert(parse_arg_id("42:").index == 42, "");
static_assert(parse_arg_id("foo:").res == arg_id_result::name, ""); static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
static_assert(parse_arg_id("foo:").name.size() == 3, ""); static_assert(parse_arg_id("foo:").name.size() == 3, "");
static_assert(parse_arg_id("!").res == arg_id_result::error, "");
} }
struct test_format_specs_handler { template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
enum result { none, hash, zero, loc, error }; auto ctx = fmt::detail::compile_parse_context<char>(fmt::string_view(s, N),
result res = none; 43, nullptr);
auto specs = fmt::detail::dynamic_format_specs<>();
fmt::align_t alignment = fmt::align::none; fmt::detail::parse_format_specs(s, s + N - 1, specs, ctx,
fmt::sign_t sign = fmt::sign::none; fmt::detail::type::float_type);
char fill = 0; return specs;
int width = 0;
fmt::detail::arg_ref<char> width_ref;
int precision = 0;
fmt::detail::arg_ref<char> precision_ref;
fmt::presentation_type type = fmt::presentation_type::none;
// Workaround for MSVC2017 bug that results in "expression did not evaluate
// to a constant" with compiler-generated copy ctor.
constexpr test_format_specs_handler() {}
constexpr test_format_specs_handler(const test_format_specs_handler& other) =
default;
constexpr void on_align(fmt::align_t a) { alignment = a; }
constexpr void on_fill(fmt::string_view f) { fill = f[0]; }
constexpr void on_sign(fmt::sign_t s) { sign = s; }
constexpr void on_hash() { res = hash; }
constexpr void on_zero() { res = zero; }
constexpr void on_localized() { res = loc; }
constexpr void on_width(int w) { width = w; }
constexpr void on_dynamic_width(fmt::detail::auto_id) {}
constexpr void on_dynamic_width(int index) { width_ref = index; }
constexpr void on_dynamic_width(string_view) {}
constexpr void on_precision(int p) { precision = p; }
constexpr void on_dynamic_precision(fmt::detail::auto_id) {}
constexpr void on_dynamic_precision(int index) { precision_ref = index; }
constexpr void on_dynamic_precision(string_view) {}
constexpr void end_precision() {}
constexpr void on_type(fmt::presentation_type t) { type = t; }
constexpr void on_error(const char*) { res = error; }
};
template <size_t N>
constexpr test_format_specs_handler parse_test_specs(const char (&s)[N]) {
auto h = test_format_specs_handler();
fmt::detail::parse_format_specs(s, s + N - 1, h);
return h;
} }
TEST(core_test, constexpr_parse_format_specs) { TEST(core_test, constexpr_parse_format_specs) {
using handler = test_format_specs_handler; static_assert(parse_test_specs("<").align == fmt::align::left, "");
static_assert(parse_test_specs("<").alignment == fmt::align::left, ""); static_assert(parse_test_specs("*^").fill[0] == '*', "");
static_assert(parse_test_specs("*^").fill == '*', "");
static_assert(parse_test_specs("+").sign == fmt::sign::plus, ""); 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::minus, "");
static_assert(parse_test_specs(" ").sign == fmt::sign::space, ""); static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
static_assert(parse_test_specs("#").res == handler::hash, ""); static_assert(parse_test_specs("#").alt, "");
static_assert(parse_test_specs("0").res == handler::zero, ""); static_assert(parse_test_specs("0").align == fmt::align::numeric, "");
static_assert(parse_test_specs("L").res == handler::loc, ""); static_assert(parse_test_specs("L").localized, "");
static_assert(parse_test_specs("42").width == 42, ""); static_assert(parse_test_specs("42").width == 42, "");
static_assert(parse_test_specs("{42}").width_ref.val.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 == 42, "");
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, ""); static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(parse_test_specs("d").type == fmt::presentation_type::dec, ""); static_assert(
static_assert(parse_test_specs("{<").res == handler::error, ""); parse_test_specs("f").type == fmt::presentation_type::fixed_lower, "");
}
struct test_parse_context {
using char_type = char;
constexpr int next_arg_id() { return 11; }
template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {}
constexpr const char* begin() { return nullptr; }
constexpr const char* end() { return nullptr; }
void on_error(const char*) {}
};
template <size_t N>
constexpr fmt::detail::dynamic_format_specs<char> parse_dynamic_specs(
const char (&s)[N]) {
auto specs = fmt::detail::dynamic_format_specs<char>();
auto ctx = test_parse_context();
auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx);
parse_format_specs(s, s + N - 1, h);
return specs;
}
TEST(format_test, constexpr_dynamic_specs_handler) {
static_assert(parse_dynamic_specs("<").align == fmt::align::left, "");
static_assert(parse_dynamic_specs("*^").fill[0] == '*', "");
static_assert(parse_dynamic_specs("+").sign == fmt::sign::plus, "");
static_assert(parse_dynamic_specs("-").sign == fmt::sign::minus, "");
static_assert(parse_dynamic_specs(" ").sign == fmt::sign::space, "");
static_assert(parse_dynamic_specs("#").alt, "");
static_assert(parse_dynamic_specs("0").align == fmt::align::numeric, "");
static_assert(parse_dynamic_specs("42").width == 42, "");
static_assert(parse_dynamic_specs("{}").width_ref.val.index == 11, "");
static_assert(parse_dynamic_specs("{42}").width_ref.val.index == 42, "");
static_assert(parse_dynamic_specs(".42").precision == 42, "");
static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, "");
static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(parse_dynamic_specs("d").type == fmt::presentation_type::dec,
"");
}
template <size_t N>
constexpr test_format_specs_handler check_specs(const char (&s)[N]) {
fmt::detail::specs_checker<test_format_specs_handler> checker(
test_format_specs_handler(), fmt::detail::type::double_type);
parse_format_specs(s, s + N - 1, checker);
return checker;
}
TEST(format_test, constexpr_specs_checker) {
using handler = test_format_specs_handler;
static_assert(check_specs("<").alignment == fmt::align::left, "");
static_assert(check_specs("*^").fill == '*', "");
static_assert(check_specs("+").sign == fmt::sign::plus, "");
static_assert(check_specs("-").sign == fmt::sign::minus, "");
static_assert(check_specs(" ").sign == fmt::sign::space, "");
static_assert(check_specs("#").res == handler::hash, "");
static_assert(check_specs("0").res == handler::zero, "");
static_assert(check_specs("42").width == 42, "");
static_assert(check_specs("{42}").width_ref.val.index == 42, "");
static_assert(check_specs(".42").precision == 42, "");
static_assert(check_specs(".{42}").precision_ref.val.index == 42, "");
static_assert(check_specs("d").type == fmt::presentation_type::dec, "");
static_assert(check_specs("{<").res == handler::error, "");
} }
struct test_format_string_handler { struct test_format_string_handler {
constexpr void on_text(const char*, const char*) {} constexpr void on_text(const char*, const char*) {}
constexpr int on_arg_id() { return 0; } constexpr auto on_arg_id() -> int { return 0; }
template <typename T> constexpr int on_arg_id(T) { return 0; } template <typename T> constexpr auto on_arg_id(T) -> int { return 0; }
constexpr void on_replacement_field(int, const char*) {} constexpr void on_replacement_field(int, const char*) {}
constexpr const char* on_format_specs(int, const char* begin, const char*) { constexpr auto on_format_specs(int, const char* begin, const char*) -> const
char* {
return begin; return begin;
} }
@ -668,7 +554,7 @@ template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
return !h.error; return !h.error;
} }
TEST(format_test, constexpr_parse_format_string) { TEST(core_test, constexpr_parse_format_string) {
static_assert(parse_string("foo"), ""); static_assert(parse_string("foo"), "");
static_assert(!parse_string("}"), ""); static_assert(!parse_string("}"), "");
static_assert(parse_string("{}"), ""); static_assert(parse_string("{}"), "");
@ -687,19 +573,20 @@ struct disabled_formatter_convertible {
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> struct formatter<enabled_formatter> { template <> struct formatter<enabled_formatter> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) { auto format(enabled_formatter, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out(); return ctx.out();
} }
}; };
template <> struct formatter<enabled_ptr_formatter*> { template <> struct formatter<enabled_ptr_formatter*> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
auto format(enabled_ptr_formatter*, format_context& ctx) auto format(enabled_ptr_formatter*, format_context& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return ctx.out(); return ctx.out();
} }
@ -720,7 +607,7 @@ struct nonconst_formattable {};
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> struct formatter<const_formattable> { template <> struct formatter<const_formattable> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@ -732,7 +619,7 @@ template <> struct formatter<const_formattable> {
}; };
template <> struct formatter<nonconst_formattable> { template <> struct formatter<nonconst_formattable> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@ -754,7 +641,7 @@ struct convertible_to_pointer_formattable {
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> struct formatter<convertible_to_pointer_formattable> { template <> struct formatter<convertible_to_pointer_formattable> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@ -768,29 +655,17 @@ FMT_END_NAMESPACE
enum class unformattable_scoped_enum {}; enum class unformattable_scoped_enum {};
namespace test {
enum class formattable_scoped_enum {};
auto format_as(formattable_scoped_enum) -> int { return 42; }
struct convertible_to_enum {
operator formattable_scoped_enum() const { return {}; }
};
} // namespace test
TEST(core_test, is_formattable) { TEST(core_test, is_formattable) {
#if 0
// This should be enabled once corresponding map overloads are gone.
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, "");
#endif
static_assert(!fmt::is_formattable<wchar_t>::value, ""); static_assert(!fmt::is_formattable<wchar_t>::value, "");
#ifdef __cpp_char8_t #ifdef __cpp_char8_t
static_assert(!fmt::is_formattable<char8_t>::value, ""); static_assert(!fmt::is_formattable<char8_t>::value, "");
#endif #endif
static_assert(!fmt::is_formattable<char16_t>::value, ""); static_assert(!fmt::is_formattable<char16_t>::value, "");
static_assert(!fmt::is_formattable<char32_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*>::value, "");
static_assert(!fmt::is_formattable<const wchar_t[3]>::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<fmt::basic_string_view<wchar_t>>::value,
@ -798,7 +673,8 @@ TEST(core_test, is_formattable) {
static_assert(fmt::is_formattable<enabled_formatter>::value, ""); static_assert(fmt::is_formattable<enabled_formatter>::value, "");
static_assert(!fmt::is_formattable<enabled_ptr_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>::value, "");
static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, ""); static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
"");
static_assert(fmt::is_formattable<const_formattable&>::value, ""); static_assert(fmt::is_formattable<const_formattable&>::value, "");
static_assert(fmt::is_formattable<const const_formattable&>::value, ""); static_assert(fmt::is_formattable<const const_formattable&>::value, "");
@ -818,45 +694,47 @@ TEST(core_test, is_formattable) {
static_assert(!fmt::is_formattable<int(s::*)>::value, ""); static_assert(!fmt::is_formattable<int(s::*)>::value, "");
static_assert(!fmt::is_formattable<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, "");
static_assert(fmt::is_formattable<test::formattable_scoped_enum>::value, ""); static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
static_assert(!fmt::is_formattable<test::convertible_to_enum>::value, "");
} }
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); } TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
TEST(core_test, format_to) { TEST(core_test, format_to) {
std::string s; auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", 42); fmt::format_to(std::back_inserter(s), "{}", 42);
EXPECT_EQ(s, "42"); EXPECT_EQ(s, "42");
} }
TEST(core_test, format_as) { #ifdef __cpp_lib_byte
EXPECT_EQ(fmt::format("{}", test::formattable_scoped_enum()), "42"); TEST(core_test, format_byte) {
EXPECT_EQ(fmt::format("{}", std::byte(42)), "42");
} }
#endif
struct convertible_to_int { struct convertible_to_int {
operator int() const { return 42; } operator int() const { return 42; }
}; };
struct convertible_to_c_string { struct convertible_to_cstring {
operator const char*() const { return "foo"; } operator const char*() const { return "foo"; }
}; };
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> struct formatter<convertible_to_int> { template <> struct formatter<convertible_to_int> {
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) { auto format(convertible_to_int, format_context& ctx) const
-> decltype(ctx.out()) {
return std::copy_n("foo", 3, ctx.out()); return std::copy_n("foo", 3, ctx.out());
} }
}; };
template <> struct formatter<convertible_to_c_string> { template <> struct formatter<convertible_to_cstring> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
auto format(convertible_to_c_string, format_context& ctx) auto format(convertible_to_cstring, format_context& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return std::copy_n("bar", 3, ctx.out()); return std::copy_n("bar", 3, ctx.out());
} }
@ -865,7 +743,7 @@ FMT_END_NAMESPACE
TEST(core_test, formatter_overrides_implicit_conversion) { TEST(core_test, formatter_overrides_implicit_conversion) {
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo"); EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar"); EXPECT_EQ(fmt::format("{}", convertible_to_cstring()), "bar");
} }
// Test that check is not found by ADL. // Test that check is not found by ADL.
@ -874,27 +752,26 @@ TEST(core_test, adl_check) {
EXPECT_EQ(fmt::format("{}", test_struct()), "test"); EXPECT_EQ(fmt::format("{}", test_struct()), "test");
} }
TEST(core_test, to_string_view_foreign_strings) {
using namespace test_ns;
EXPECT_EQ(to_string_view(test_string<char>("42")), "42");
fmt::detail::type type =
fmt::detail::mapped_type_constant<test_string<char>,
fmt::format_context>::value;
EXPECT_EQ(type, fmt::detail::type::string_type);
}
struct implicitly_convertible_to_string {
operator std::string() const { return "foo"; }
};
struct implicitly_convertible_to_string_view { struct implicitly_convertible_to_string_view {
operator fmt::string_view() const { return "foo"; } operator fmt::string_view() const { return "foo"; }
}; };
TEST(core_test, format_implicitly_convertible_to_string_view) { TEST(core_test, no_implicit_conversion_to_string_view) {
EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view())); EXPECT_FALSE(
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(core_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. // std::is_constructible is broken in MSVC until version 2015.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
struct explicitly_convertible_to_string_view { struct explicitly_convertible_to_string_view {
@ -923,25 +800,6 @@ TEST(core_test, format_explicitly_convertible_to_std_string_view) {
# endif # endif
#endif #endif
struct convertible_to_long_long {
operator long long() const { return 1LL << 32; }
};
TEST(format_test, format_convertible_to_long_long) {
EXPECT_EQ("100000000", fmt::format("{:x}", convertible_to_long_long()));
}
struct disabled_rvalue_conversion {
operator const char*() const& { return "foo"; }
operator const char*() & { return "foo"; }
operator const char*() const&& = delete;
operator const char*() && = delete;
};
TEST(core_test, disabled_rvalue_conversion) {
EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
}
namespace adl_test { namespace adl_test {
template <typename... T> void make_format_args(const T&...) = delete; template <typename... T> void make_format_args(const T&...) = delete;
@ -973,3 +831,28 @@ TEST(core_test, has_const_formatter) {
TEST(core_test, format_nonconst) { TEST(core_test, format_nonconst) {
EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test"); EXPECT_EQ(fmt::format("{}", nonconst_formattable()), "test");
} }
struct its_a_trap {
template <typename T> operator T() const {
auto v = T();
v.x = 42;
return v;
}
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<its_a_trap> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(its_a_trap, format_context& ctx) const -> decltype(ctx.out()) {
auto s = string_view("42");
return std::copy(s.begin(), s.end(), ctx.out());
}
};
FMT_END_NAMESPACE
TEST(core_test, trappy_conversion) {
EXPECT_EQ(fmt::format("{}", its_a_trap()), "42");
}

View file

@ -8,12 +8,14 @@
#include <iterator> #include <iterator>
#include <vector> #include <vector>
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include "fmt/color.h" #include "fmt/color.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "fmt/ostream.h" #include "fmt/ostream.h"
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "fmt/xchar.h" #include "fmt/xchar.h"
#undef I
// Exercise the API to verify that everything we expect to can compile. // Exercise the API to verify that everything we expect to can compile.
void test_format_api() { void test_format_api() {

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.8...3.25)
project(fmt-test) project(fmt-test)

View file

@ -196,43 +196,6 @@ TEST(fp_test, multiply) {
EXPECT_EQ(v.e, 4 + 8 + 64); EXPECT_EQ(v.e, 4 + 8 + 64);
} }
TEST(fp_test, get_cached_power) {
using limits = std::numeric_limits<double>;
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
int dec_exp = 0;
auto power = fmt::detail::get_cached_power(exp, dec_exp);
bigint exact, cache(power.f);
if (dec_exp >= 0) {
exact.assign_pow10(dec_exp);
if (power.e <= 0)
exact <<= -power.e;
else
cache <<= power.e;
exact.align(cache);
cache.align(exact);
auto exact_str = fmt::to_string(exact);
auto cache_str = fmt::to_string(cache);
EXPECT_EQ(exact_str.size(), cache_str.size());
EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15));
int diff = cache_str[15] - exact_str[15];
if (diff == 1)
EXPECT_GT(exact_str[16], '8');
else
EXPECT_EQ(diff, 0);
} else {
cache.assign_pow10(-dec_exp);
cache *= power.f + 1; // Inexact check.
exact = 1;
exact <<= -power.e;
exact.align(cache);
auto exact_str = fmt::to_string(exact);
auto cache_str = fmt::to_string(cache);
EXPECT_EQ(exact_str.size(), cache_str.size());
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
}
}
}
TEST(fp_test, dragonbox_max_k) { TEST(fp_test, dragonbox_max_k) {
using fmt::detail::dragonbox::floor_log10_pow2; using fmt::detail::dragonbox::floor_log10_pow2;
using float_info = fmt::detail::dragonbox::float_info<float>; using float_info = fmt::detail::dragonbox::float_info<float>;
@ -242,66 +205,18 @@ TEST(fp_test, dragonbox_max_k) {
floor_log10_pow2(std::numeric_limits<float>::min_exponent - floor_log10_pow2(std::numeric_limits<float>::min_exponent -
fmt::detail::num_significand_bits<float>() - 1)); fmt::detail::num_significand_bits<float>() - 1));
using double_info = fmt::detail::dragonbox::float_info<double>; using double_info = fmt::detail::dragonbox::float_info<double>;
EXPECT_EQ( EXPECT_EQ(fmt::detail::const_check(double_info::max_k),
fmt::detail::const_check(double_info::max_k),
double_info::kappa - double_info::kappa -
floor_log10_pow2(std::numeric_limits<double>::min_exponent - floor_log10_pow2(
fmt::detail::num_significand_bits<double>() - 1)); std::numeric_limits<double>::min_exponent -
} 2 * fmt::detail::num_significand_bits<double>() - 1));
TEST(fp_test, get_round_direction) {
using fmt::detail::get_round_direction;
using fmt::detail::round_direction;
EXPECT_EQ(get_round_direction(100, 50, 0), round_direction::down);
EXPECT_EQ(get_round_direction(100, 51, 0), round_direction::up);
EXPECT_EQ(get_round_direction(100, 40, 10), round_direction::down);
EXPECT_EQ(get_round_direction(100, 60, 10), round_direction::up);
for (size_t i = 41; i < 60; ++i)
EXPECT_EQ(get_round_direction(100, i, 10), round_direction::unknown);
uint64_t max = max_value<uint64_t>();
EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
// Check that remainder + error doesn't overflow.
EXPECT_EQ(get_round_direction(max, max - 1, 2), round_direction::up);
// Check that 2 * (remainder + error) doesn't overflow.
EXPECT_EQ(get_round_direction(max, max / 2 + 1, max / 2),
round_direction::unknown);
// Check that remainder - error doesn't overflow.
EXPECT_EQ(get_round_direction(100, 40, 41), round_direction::unknown);
// Check that 2 * (remainder - error) doesn't overflow.
EXPECT_EQ(get_round_direction(max, max - 1, 1), round_direction::up);
}
TEST(fp_test, fixed_handler) {
struct handler : fmt::detail::gen_digits_handler {
char buffer[10];
handler(int prec = 0) : fmt::detail::gen_digits_handler() {
buf = buffer;
precision = prec;
}
};
handler().on_digit('0', 100, 99, 0, false);
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, false), assertion_failure);
namespace digits = fmt::detail::digits;
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, false), digits::error);
// Check that divisor - error doesn't overflow.
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, false), digits::error);
// Check that 2 * error doesn't overflow.
uint64_t max = max_value<uint64_t>();
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, false), digits::error);
}
TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
auto buf = fmt::memory_buffer();
format_float(0.42, -1, fmt::detail::float_specs(), buf);
} }
TEST(format_impl_test, format_error_code) { TEST(format_impl_test, format_error_code) {
std::string msg = "error 42", sep = ": "; std::string msg = "error 42", sep = ": ";
{ {
auto buffer = fmt::memory_buffer(); auto buffer = fmt::memory_buffer();
format_to(fmt::appender(buffer), "garbage"); fmt::format_to(fmt::appender(buffer), "garbage");
fmt::detail::format_error_code(buffer, 42, "test"); fmt::detail::format_error_code(buffer, 42, "test");
EXPECT_EQ(to_string(buffer), "test: " + msg); EXPECT_EQ(to_string(buffer), "test: " + msg);
} }
@ -353,6 +268,16 @@ TEST(format_impl_test, count_digits) {
test_count_digits<uint64_t>(); test_count_digits<uint64_t>();
} }
TEST(format_impl_test, countl_zero) {
constexpr auto num_bits = fmt::detail::num_bits<uint32_t>();
uint32_t n = 1u;
for (int i = 1; i < num_bits - 1; i++) {
n <<= 1;
EXPECT_EQ(fmt::detail::countl_zero(n - 1), num_bits - i);
EXPECT_EQ(fmt::detail::countl_zero(n), num_bits - i - 1);
}
}
#if FMT_USE_FLOAT128 #if FMT_USE_FLOAT128
TEST(format_impl_test, write_float128) { TEST(format_impl_test, write_float128) {
auto s = std::string(); auto s = std::string();
@ -372,10 +297,22 @@ struct double_double {
auto operator-() const -> double_double { return double_double(-a, -b); } auto operator-() const -> double_double { return double_double(-a, -b); }
}; };
auto format_as(double_double d) -> double { return d; }
bool operator>=(const double_double& lhs, const double_double& rhs) { bool operator>=(const double_double& lhs, const double_double& rhs) {
return lhs.a + lhs.b >= rhs.a + rhs.b; return lhs.a + lhs.b >= rhs.a + rhs.b;
} }
struct slow_float {
float value;
explicit constexpr slow_float(float val = 0) : value(val) {}
operator float() const { return value; }
auto operator-() const -> slow_float { return slow_float(-value); }
};
auto format_as(slow_float f) -> float { return f; }
namespace std { namespace std {
template <> struct is_floating_point<double_double> : std::true_type {}; template <> struct is_floating_point<double_double> : std::true_type {};
template <> struct numeric_limits<double_double> { template <> struct numeric_limits<double_double> {
@ -383,14 +320,35 @@ template <> struct numeric_limits<double_double> {
static constexpr bool is_iec559 = true; static constexpr bool is_iec559 = true;
static constexpr int digits = 106; static constexpr int digits = 106;
}; };
template <> struct is_floating_point<slow_float> : std::true_type {};
template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
} // namespace std } // namespace std
FMT_BEGIN_NAMESPACE
namespace detail {
template <> struct is_fast_float<slow_float> : std::false_type {};
namespace dragonbox {
template <> struct float_info<slow_float> {
using carrier_uint = uint32_t;
static const int exponent_bits = 8;
};
} // namespace dragonbox
} // namespace detail
FMT_END_NAMESPACE
TEST(format_impl_test, write_double_double) { TEST(format_impl_test, write_double_double) {
auto s = std::string(); auto s = std::string();
fmt::detail::write<char>(std::back_inserter(s), double_double(42), {}); fmt::detail::write<char>(std::back_inserter(s), double_double(42), {});
#ifndef _MSC_VER // MSVC has an issue with specializing is_floating_point. // Specializing is_floating_point is broken in MSVC.
EXPECT_EQ(s, "42"); if (!FMT_MSC_VERSION) EXPECT_EQ(s, "42");
#endif }
TEST(format_impl_test, write_dragon_even) {
auto s = std::string();
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
// Specializing is_floating_point is broken in MSVC.
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
} }
#ifdef _WIN32 #ifdef _WIN32
@ -401,3 +359,125 @@ TEST(format_impl_test, write_console_signature) {
(void)p; (void)p;
} }
#endif #endif
// A public domain branchless UTF-8 decoder by Christopher Wellons:
// https://github.com/skeeto/branchless-utf8
constexpr bool unicode_is_surrogate(uint32_t c) {
return c >= 0xD800U && c <= 0xDFFFU;
}
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
if (c >= (1UL << 16)) {
s[0] = static_cast<char>(0xf0 | (c >> 18));
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));
s[2] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
s[3] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
return s + 4;
} else if (c >= (1UL << 11)) {
s[0] = static_cast<char>(0xe0 | (c >> 12));
s[1] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
s[2] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
return s + 3;
} else if (c >= (1UL << 7)) {
s[0] = static_cast<char>(0xc0 | (c >> 6));
s[1] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
return s + 2;
} else {
s[0] = static_cast<char>(c);
return s + 1;
}
}
// Make sure it can decode every character
TEST(format_impl_test, utf8_decode_decode_all) {
for (uint32_t i = 0; i < 0x10ffff; i++) {
if (!unicode_is_surrogate(i)) {
int e;
uint32_t c;
char buf[8] = {0};
char* end = utf8_encode(buf, i);
const char* res = fmt::detail::utf8_decode(buf, &c, &e);
EXPECT_EQ(end, res);
EXPECT_EQ(c, i);
EXPECT_EQ(e, 0);
}
}
}
// Reject everything outside of U+0000..U+10FFFF
TEST(format_impl_test, utf8_decode_out_of_range) {
for (uint32_t i = 0x110000; i < 0x1fffff; i++) {
int e;
uint32_t c;
char buf[8] = {0};
utf8_encode(buf, i);
const char* end = fmt::detail::utf8_decode(buf, &c, &e);
EXPECT_NE(e, 0);
EXPECT_EQ(end - buf, 4);
}
}
// Does it reject all surrogate halves?
TEST(format_impl_test, utf8_decode_surrogate_halves) {
for (uint32_t i = 0xd800; i <= 0xdfff; i++) {
int e;
uint32_t c;
char buf[8] = {0};
utf8_encode(buf, i);
fmt::detail::utf8_decode(buf, &c, &e);
EXPECT_NE(e, 0);
}
}
// How about non-canonical encodings?
TEST(format_impl_test, utf8_decode_non_canonical_encodings) {
int e;
uint32_t c;
const char* end;
char buf2[8] = {char(0xc0), char(0xA4)};
end = fmt::detail::utf8_decode(buf2, &c, &e);
EXPECT_NE(e, 0); // non-canonical len 2
EXPECT_EQ(end, buf2 + 2); // non-canonical recover 2
char buf3[8] = {char(0xe0), char(0x80), char(0xA4)};
end = fmt::detail::utf8_decode(buf3, &c, &e);
EXPECT_NE(e, 0); // non-canonical len 3
EXPECT_EQ(end, buf3 + 3); // non-canonical recover 3
char buf4[8] = {char(0xf0), char(0x80), char(0x80), char(0xA4)};
end = fmt::detail::utf8_decode(buf4, &c, &e);
EXPECT_NE(e, 0); // non-canonical encoding len 4
EXPECT_EQ(end, buf4 + 4); // non-canonical recover 4
}
// Let's try some bogus byte sequences
TEST(format_impl_test, utf8_decode_bogus_byte_sequences) {
int e;
uint32_t c;
// Invalid first byte
char buf0[4] = {char(0xff)};
auto len = fmt::detail::utf8_decode(buf0, &c, &e) - buf0;
EXPECT_NE(e, 0); // "bogus [ff] 0x%02x U+%04lx", e, (unsigned long)c);
EXPECT_EQ(len, 1); // "bogus [ff] recovery %d", len);
// Invalid first byte
char buf1[4] = {char(0x80)};
len = fmt::detail::utf8_decode(buf1, &c, &e) - buf1;
EXPECT_NE(e, 0); // "bogus [80] 0x%02x U+%04lx", e, (unsigned long)c);
EXPECT_EQ(len, 1); // "bogus [80] recovery %d", len);
// Looks like a two-byte sequence but second byte is wrong
char buf2[4] = {char(0xc0), char(0x0a)};
len = fmt::detail::utf8_decode(buf2, &c, &e) - buf2;
EXPECT_NE(e, 0); // "bogus [c0 0a] 0x%02x U+%04lx", e, (unsigned long)c
EXPECT_EQ(len, 2); // "bogus [c0 0a] recovery %d", len);
}
TEST(format_impl_test, to_utf8) {
auto s = std::string("ёжик");
auto u = fmt::detail::to_utf8<wchar_t>(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,7 @@ function(add_fuzzer source)
if (FMT_FUZZ_LDFLAGS) if (FMT_FUZZ_LDFLAGS)
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS}) target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
endif () endif ()
target_compile_features(${name} PRIVATE cxx_generic_lambdas) target_compile_features(${name} PRIVATE cxx_std_14)
endfunction() endfunction()
foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc) foreach (source chrono-duration.cc chrono-timepoint.cc float.cc named-arg.cc one-arg.cc two-args.cc)

View file

@ -201,6 +201,7 @@ TEST(gtest_extra_test, expect_write_streaming) {
// EXPECT_THROW_MSG macro. // EXPECT_THROW_MSG macro.
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) { TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
int n = 0; int n = 0;
(void)n;
using std::runtime_error; using std::runtime_error;
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, ""); EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), ""); EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
@ -213,6 +214,7 @@ TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
// EXPECT_SYSTEM_ERROR macro. // EXPECT_SYSTEM_ERROR macro.
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) { TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
int n = 0; int n = 0;
(void)n;
EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test"); EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), ""); EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), ""); EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), "");

View file

@ -12,12 +12,7 @@
#include <string> #include <string>
#ifdef FMT_MODULE_TEST
import fmt;
#else
#include "fmt/os.h" #include "fmt/os.h"
#endif // FMG_MODULE_TEST
#include "gmock/gmock.h" #include "gmock/gmock.h"
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \

View file

@ -9,6 +9,7 @@ add_library(gtest STATIC
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h) gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1) target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
target_include_directories(gtest SYSTEM PUBLIC .) target_include_directories(gtest SYSTEM PUBLIC .)
target_compile_features(gtest PUBLIC cxx_std_11)
find_package(Threads) find_package(Threads)
if (Threads_FOUND) if (Threads_FOUND)
@ -17,13 +18,6 @@ else ()
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0) target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
endif () endif ()
# Workaround GTest bug https://github.com/google/googletest/issues/705.
fmt_check_cxx_compiler_flag(
-fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
target_compile_options(gtest PUBLIC -fno-delete-null-pointer-checks)
endif ()
if (MSVC) if (MSVC)
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions. # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS) target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)

View file

@ -2202,7 +2202,7 @@ bool UnitTestOptions::MatchesFilter(const std::string& name_str,
// Check if this pattern matches name_str. // Check if this pattern matches name_str.
if (PatternMatchesString(name_str, pattern, pattern_end)) { if (PatternMatchesString(name_str, pattern, pattern_end)) {
return true; break;
} }
// Give up on this pattern. However, if we found a pattern separator (:), // Give up on this pattern. However, if we found a pattern separator (:),
@ -13778,7 +13778,7 @@ UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith(
UntypedActionResultHolderBase* result = nullptr; UntypedActionResultHolderBase* result = nullptr;
auto perform_action = [&] { auto perform_action = [&, this] {
return untyped_action == nullptr return untyped_action == nullptr
? this->UntypedPerformDefaultAction(untyped_args, ss.str()) ? this->UntypedPerformDefaultAction(untyped_args, ss.str())
: this->UntypedPerformAction(untyped_action, untyped_args); : this->UntypedPerformAction(untyped_action, untyped_args);

View file

@ -20,8 +20,8 @@ template <typename T> class mock_allocator {
mock_allocator() {} mock_allocator() {}
mock_allocator(const mock_allocator&) {} mock_allocator(const mock_allocator&) {}
using value_type = T; using value_type = T;
MOCK_METHOD1_T(allocate, T*(size_t n)); MOCK_METHOD(T*, allocate, (size_t));
MOCK_METHOD2_T(deallocate, void(T* p, size_t n)); MOCK_METHOD(void, deallocate, (T*, size_t));
}; };
template <typename Allocator> class allocator_ref { template <typename Allocator> class allocator_ref {

View file

@ -15,8 +15,6 @@
# define FMT_HIDE_MODULE_BUGS # define FMT_HIDE_MODULE_BUGS
#endif #endif
#define FMT_MODULE_TEST
#include <bit> #include <bit>
#include <chrono> #include <chrono>
#include <exception> #include <exception>
@ -41,7 +39,6 @@
#else #else
# define FMT_POSIX(call) call # define FMT_POSIX(call) call
#endif #endif
#define FMT_OS_H_ // don't pull in os.h directly or indirectly
import fmt; import fmt;
@ -53,13 +50,8 @@ static bool macro_leaked =
false; false;
#endif #endif
// Include sources to pick up functions and classes from the module rather than #define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly
// from the non-modular library which is baked into the 'test-main' library. #include "gtest-extra.h"
// This averts linker problems:
// - strong ownership model: missing linker symbols
// - weak ownership model: duplicate linker symbols
#include "gtest-extra.cc"
#include "util.cc"
// an implicitly exported namespace must be visible [module.interface]/2.2 // an implicitly exported namespace must be visible [module.interface]/2.2
TEST(module_test, namespace) { TEST(module_test, namespace) {
@ -75,9 +67,8 @@ bool oops_detail_namespace_is_visible;
namespace fmt { namespace fmt {
bool namespace_detail_invisible() { bool namespace_detail_invisible() {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ #if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \ _MSC_FULL_VER <= 193700000
(_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704)) // bug in msvc up to at least 17.7:
// bug in msvc up to 16.11.5 / 17.0-pre5:
// the namespace is visible even when it is neither // the namespace is visible even when it is neither
// implicitly nor explicitly exported // implicitly nor explicitly exported
@ -111,7 +102,7 @@ TEST(module_test, macros) {
// but rather visibility of all client-facing overloads, reachability of // but rather visibility of all client-facing overloads, reachability of
// non-exported entities, name lookup and overload resolution within // non-exported entities, name lookup and overload resolution within
// template instantitions. // template instantitions.
// Excercise all exported entities of the API at least once. // Exercise all exported entities of the API at least once.
// Instantiate as many code paths as possible. // Instantiate as many code paths as possible.
TEST(module_test, to_string) { TEST(module_test, to_string) {
@ -140,7 +131,7 @@ TEST(module_test, format_to) {
EXPECT_EQ("42", std::string_view(buffer)); EXPECT_EQ("42", std::string_view(buffer));
fmt::memory_buffer mb; fmt::memory_buffer mb;
fmt::format_to(mb, "{}", 42); fmt::format_to(std::back_inserter(mb), "{}", 42);
EXPECT_EQ("42", std::string_view(buffer)); EXPECT_EQ("42", std::string_view(buffer));
std::wstring w; std::wstring w;
@ -152,7 +143,7 @@ TEST(module_test, format_to) {
EXPECT_EQ(L"42", std::wstring_view(wbuffer)); EXPECT_EQ(L"42", std::wstring_view(wbuffer));
fmt::wmemory_buffer wb; fmt::wmemory_buffer wb;
fmt::format_to(wb, L"{}", 42); fmt::format_to(std::back_inserter(wb), L"{}", 42);
EXPECT_EQ(L"42", std::wstring_view(wbuffer)); EXPECT_EQ(L"42", std::wstring_view(wbuffer));
} }
@ -211,8 +202,8 @@ TEST(module_test, dynamic_format_args) {
TEST(module_test, vformat) { TEST(module_test, vformat) {
EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42))); EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42)));
EXPECT_EQ(L"42", fmt::vformat(fmt::to_string_view(L"{}"), EXPECT_EQ(L"42",
fmt::make_wformat_args(42))); fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(42)));
} }
TEST(module_test, vformat_to) { TEST(module_test, vformat_to) {
@ -245,9 +236,9 @@ TEST(module_test, vformat_to_n) {
auto wstore = fmt::make_wformat_args(12345); auto wstore = fmt::make_wformat_args(12345);
std::wstring w; std::wstring w;
auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1, auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1,
fmt::to_string_view(L"{}"), wstore); fmt::wstring_view(L"{}"), wstore);
wchar_t wbuffer[4] = {0}; wchar_t wbuffer[4] = {0};
fmt::vformat_to_n(wbuffer, 3, fmt::to_string_view(L"{:}"), wstore); fmt::vformat_to_n(wbuffer, 3, fmt::wstring_view(L"{:}"), wstore);
} }
std::string as_string(std::wstring_view text) { std::string as_string(std::wstring_view text) {
@ -258,23 +249,19 @@ std::string as_string(std::wstring_view text) {
TEST(module_test, print) { TEST(module_test, print) {
EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ"); EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ");
EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ"); EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ");
if (false) {
EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ")); EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ"));
EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ")); EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
} }
}
TEST(module_test, vprint) { TEST(module_test, vprint) {
EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ"); EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ");
EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)), EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)),
"4.2"); "4.2");
if (false) {
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)), EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
as_string(L"42µ")); as_string(L"42µ"));
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)), EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
as_string(L"42")); as_string(L"42"));
} }
}
TEST(module_test, named_args) { TEST(module_test, named_args) {
EXPECT_EQ("42", fmt::format("{answer}", fmt::arg("answer", 42))); EXPECT_EQ("42", fmt::format("{answer}", fmt::arg("answer", 42)));
@ -284,9 +271,7 @@ TEST(module_test, named_args) {
TEST(module_test, literals) { TEST(module_test, literals) {
using namespace fmt::literals; using namespace fmt::literals;
EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = 42)); EXPECT_EQ("42", fmt::format("{answer}", "answer"_a = 42));
EXPECT_EQ("42", "{}"_format(42));
EXPECT_EQ(L"42", fmt::format(L"{answer}", L"answer"_a = 42)); EXPECT_EQ(L"42", fmt::format(L"{answer}", L"answer"_a = 42));
EXPECT_EQ(L"42", L"{}"_format(42));
} }
TEST(module_test, locale) { TEST(module_test, locale) {
@ -320,7 +305,7 @@ TEST(module_test, string_view) {
TEST(module_test, memory_buffer) { TEST(module_test, memory_buffer) {
fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer; fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer;
fmt::format_to(buffer, "{}", "42"); fmt::format_to(std::back_inserter(buffer), "{}", "42");
EXPECT_EQ("42", to_string(buffer)); EXPECT_EQ("42", to_string(buffer));
fmt::memory_buffer nbuffer(std::move(buffer)); fmt::memory_buffer nbuffer(std::move(buffer));
EXPECT_EQ("42", to_string(nbuffer)); EXPECT_EQ("42", to_string(nbuffer));
@ -395,27 +380,20 @@ struct test_formatter : fmt::formatter<char> {
bool check() { return true; } bool check() { return true; }
}; };
struct test_dynamic_formatter : fmt::dynamic_formatter<> { TEST(module_test, formatter) { EXPECT_TRUE(test_formatter{}.check()); }
bool check() { return true; }
};
TEST(module_test, formatter) {
EXPECT_TRUE(test_formatter{}.check());
EXPECT_TRUE(test_dynamic_formatter{}.check());
}
TEST(module_test, join) { TEST(module_test, join) {
int arr[3] = {1, 2, 3}; int arr[3] = {1, 2, 3};
std::vector<double> vec{1.0, 2.0, 3.0}; std::vector<double> vec{1.0, 2.0, 3.0};
std::initializer_list<int> il{1, 2, 3}; std::initializer_list<int> il{1, 2, 3};
auto sep = fmt::to_string_view(", "); auto sep = fmt::string_view(", ");
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep)));
EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep)));
auto wsep = fmt::to_string_view(L", "); auto wsep = fmt::wstring_view(L", ");
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep))); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep)));
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep))); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep)));
EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep))); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep)));
@ -426,7 +404,6 @@ TEST(module_test, time) {
EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120); EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120);
EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120); EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120);
auto chrono_now = std::chrono::system_clock::now(); auto chrono_now = std::chrono::system_clock::now();
EXPECT_TRUE(fmt::localtime(chrono_now).tm_year > 120);
EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120); EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120);
} }
@ -453,34 +430,16 @@ TEST(module_test, weekday) {
EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1))); EXPECT_EQ("Mon", fmt::format(std::locale::classic(), "{}", fmt::weekday(1)));
} }
TEST(module_test, to_string_view) {
using fmt::to_string_view;
fmt::string_view nsv{to_string_view("42")};
EXPECT_EQ("42", nsv);
fmt::wstring_view wsv{to_string_view(L"42")};
EXPECT_EQ(L"42", wsv);
}
TEST(module_test, printf) { TEST(module_test, printf) {
EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456"); EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456");
EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42"); EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42");
if (false) { EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456), as_string(L"42.123456"));
EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456),
as_string(L"42.123456"));
EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42")); EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42"));
} }
}
TEST(module_test, fprintf) { TEST(module_test, fprintf) {
EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42"); EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42");
std::ostringstream os;
fmt::fprintf(os, "%s", "bla");
EXPECT_EQ("bla", os.str());
EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42")); EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42"));
std::wostringstream ws;
fmt::fprintf(ws, L"%s", L"bla");
EXPECT_EQ(L"bla", ws.str());
} }
TEST(module_test, sprintf) { TEST(module_test, sprintf) {
@ -490,26 +449,16 @@ TEST(module_test, sprintf) {
TEST(module_test, vprintf) { TEST(module_test, vprintf) {
EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42"); EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42");
if (false) {
EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)), EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)),
as_string(L"42")); as_string(L"42"));
} }
}
TEST(module_test, vfprintf) { TEST(module_test, vfprintf) {
auto args = fmt::make_printf_args(42); auto args = fmt::make_printf_args(42);
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42"); EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42");
std::ostringstream os;
fmt::vfprintf(os, "%d", args);
EXPECT_EQ("42", os.str());
auto wargs = fmt::make_wprintf_args(42); auto wargs = fmt::make_wprintf_args(42);
if (false) {
EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42")); EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42"));
} }
std::wostringstream ws;
fmt::vfprintf(ws, L"%d", wargs);
EXPECT_EQ(L"42", ws.str());
}
TEST(module_test, vsprintf) { TEST(module_test, vsprintf) {
EXPECT_EQ("42", fmt::vsprintf("%d", fmt::make_printf_args(42))); EXPECT_EQ("42", fmt::vsprintf("%d", fmt::make_printf_args(42)));
@ -538,8 +487,13 @@ TEST(module_test, buffered_file) {
} }
TEST(module_test, output_file) { TEST(module_test, output_file) {
#ifdef __clang__
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
"Clang 16.0 emits multiple copies of vtables");
#else
fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1); fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1);
out.close(); out.close();
#endif
} }
struct custom_context { struct custom_context {
@ -552,21 +506,15 @@ TEST(module_test, custom_context) {
EXPECT_TRUE(!custom_arg); EXPECT_TRUE(!custom_arg);
} }
struct disabled_formatter {};
TEST(module_test, has_formatter) {
EXPECT_FALSE(
(fmt::has_formatter<disabled_formatter, fmt::format_context>::value));
}
TEST(module_test, is_formattable) {
EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
}
TEST(module_test, compile_format_string) { TEST(module_test, compile_format_string) {
using namespace fmt::literals; using namespace fmt::literals;
#ifdef __clang__
fmt::println("\033[0;33m[=disabled=] {}\033[0;0m",
"Clang 16.0 fails to import user-defined literals");
#else
EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42)); EXPECT_EQ("42", fmt::format("{0:x}"_cf, 0x42));
EXPECT_EQ(L"42", fmt::format(L"{:}"_cf, 42)); EXPECT_EQ(L"42", fmt::format(L"{:}"_cf, 42));
EXPECT_EQ("4.2", fmt::format("{arg:3.1f}"_cf, "arg"_a = 4.2)); EXPECT_EQ("4.2", fmt::format("{arg:3.1f}"_cf, "arg"_a = 4.2));
EXPECT_EQ(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42")); EXPECT_EQ(L" 42", fmt::format(L"{arg:>3}"_cf, L"arg"_a = L"42"));
#endif
} }

View file

@ -22,60 +22,6 @@ using wstring_view = fmt::basic_string_view<wchar_t>;
# include <windows.h> # include <windows.h>
TEST(util_test, utf16_to_utf8) {
auto s = std::string("ёжик");
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}
TEST(util_test, utf16_to_utf8_empty_string) {
std::string s = "";
fmt::detail::utf16_to_utf8 u(L"");
EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size());
}
template <typename Converter, typename Char>
void check_utf_conversion_error(const char* message,
fmt::basic_string_view<Char> str =
fmt::basic_string_view<Char>(nullptr, 1)) {
fmt::memory_buffer out;
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
auto error = std::system_error(std::error_code());
try {
(Converter)(str);
} catch (const std::system_error& e) {
error = e;
}
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.code().value());
EXPECT_THAT(error.what(), HasSubstr(fmt::to_string(out)));
}
TEST(util_test, utf16_to_utf8_error) {
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
"cannot convert string from UTF-16 to UTF-8");
}
TEST(util_test, utf16_to_utf8_convert) {
fmt::detail::utf16_to_utf8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(nullptr, 1)));
EXPECT_EQ(ERROR_INVALID_PARAMETER,
u.convert(wstring_view(L"foo", INT_MAX + 1u)));
}
TEST(os_test, format_std_error_code) {
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())));
}
TEST(os_test, format_windows_error) { TEST(os_test, format_windows_error) {
LPWSTR message = nullptr; LPWSTR message = nullptr;
auto result = FormatMessageW( auto result = FormatMessageW(
@ -83,7 +29,8 @@ TEST(os_test, format_windows_error) {
FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, nullptr); reinterpret_cast<LPWSTR>(&message), 0, nullptr);
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2)); auto utf8_message =
fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2));
LocalFree(message); LocalFree(message);
fmt::memory_buffer actual_message; fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test"); fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
@ -108,7 +55,8 @@ TEST(os_test, format_long_windows_error) {
LocalFree(message); LocalFree(message);
return; return;
} }
fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2)); auto utf8_message =
fmt::detail::to_utf8<wchar_t>(wstring_view(message, result - 2));
LocalFree(message); LocalFree(message);
fmt::memory_buffer actual_message; fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed, fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
@ -139,6 +87,17 @@ TEST(os_test, report_windows_error) {
fmt::to_string(out)); fmt::to_string(out));
} }
# if FMT_USE_FCNTL && !defined(__MINGW32__)
TEST(file_test, open_windows_file) {
using fmt::file;
file out = file::open_windows_file(L"test-file",
file::WRONLY | file::CREATE | file::TRUNC);
out.write("x", 1);
file in = file::open_windows_file(L"test-file", file::RDONLY);
EXPECT_READ(in, "x");
}
# endif // FMT_USE_FCNTL && !defined(__MINGW32__)
#endif // _WIN32 #endif // _WIN32
#if FMT_USE_FCNTL #if FMT_USE_FCNTL

View file

@ -86,13 +86,13 @@ TEST(ostream_test, format_specs) {
EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def"))); EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def"))); EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:+}"), test_string()),
format_error, "format specifier requires numeric argument"); format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:-}"), test_string()),
format_error, "format specifier requires numeric argument"); format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()), EXPECT_THROW_MSG((void)fmt::format(runtime("{0: }"), test_string()),
format_error, "format specifier requires numeric argument"); format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:#}"), test_string()),
format_error, "format specifier requires numeric argument"); format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()), EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()),
format_error, "format specifier requires numeric argument"); format_error, "format specifier requires numeric argument");
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test"))); EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
@ -106,11 +106,19 @@ TEST(ostream_test, empty_custom_output) {
} }
TEST(ostream_test, print) { TEST(ostream_test, print) {
{
std::ostringstream os; std::ostringstream os;
fmt::print(os, "Don't {}!", "panic"); fmt::print(os, "Don't {}!", "panic");
EXPECT_EQ("Don't panic!", os.str()); EXPECT_EQ("Don't panic!", os.str());
} }
{
std::ostringstream os;
fmt::println(os, "Don't {}!", "panic");
EXPECT_EQ("Don't panic!\n", os.str());
}
}
TEST(ostream_test, write_to_ostream) { TEST(ostream_test, write_to_ostream) {
std::ostringstream os; std::ostringstream os;
fmt::memory_buffer buffer; fmt::memory_buffer buffer;
@ -132,7 +140,7 @@ TEST(ostream_test, write_to_ostream_max_size) {
} buffer(max_size); } buffer(max_size);
struct mock_streambuf : std::streambuf { struct mock_streambuf : std::streambuf {
MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n)); MOCK_METHOD(std::streamsize, xsputn, (const void*, std::streamsize));
auto xsputn(const char* s, std::streamsize n) -> std::streamsize override { auto xsputn(const char* s, std::streamsize n) -> std::streamsize override {
const void* v = s; const void* v = s;
return xsputn(v, n); return xsputn(v, n);
@ -218,45 +226,6 @@ TEST(ostream_test, format_to_n) {
EXPECT_EQ("xabx", fmt::string_view(buffer, 4)); EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
} }
template <typename T> struct convertible {
T value;
explicit convertible(const T& val) : value(val) {}
operator T() const { return value; }
};
TEST(ostream_test, disable_builtin_ostream_operators) {
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
}
struct streamable_and_convertible_to_bool {
operator bool() const { return true; }
};
std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
return os << "foo";
}
TEST(ostream_test, format_convertible_to_bool) {
// operator<< is intentionally not used because of potential ODR violations.
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true");
}
struct streamable_and_convertible_to_string_view {
operator fmt::string_view() const { return "foo"; }
};
std::ostream& operator<<(std::ostream& os,
streamable_and_convertible_to_string_view) {
return os << "bar";
}
TEST(ostream_test, format_convertible_to_string_vew) {
// operator<< is intentionally not used because of potential ODR violations.
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_string_view()),
"foo");
}
struct copyfmt_test {}; struct copyfmt_test {};
std::ostream& operator<<(std::ostream& os, copyfmt_test) { std::ostream& operator<<(std::ostream& os, copyfmt_test) {

View file

@ -72,12 +72,6 @@ int test::open(const char* path, int oflag, int mode) {
EMULATE_EINTR(open, -1); EMULATE_EINTR(open, -1);
return ::open(path, oflag, mode); return ::open(path, oflag, mode);
} }
#else
errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag,
int pmode) {
EMULATE_EINTR(open, EINTR);
return _sopen_s(pfh, filename, oflag, shflag, pmode);
}
#endif #endif
#ifndef _WIN32 #ifndef _WIN32
@ -220,11 +214,11 @@ TEST(os_test, getpagesize) {
} }
TEST(file_test, open_retry) { TEST(file_test, open_retry) {
# ifndef _WIN32
write_file("temp", "there must be something here"); write_file("temp", "there must be something here");
std::unique_ptr<file> f{nullptr}; std::unique_ptr<file> f{nullptr};
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open, EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
"cannot open file temp"); "cannot open file temp");
# ifndef _WIN32
char c = 0; char c = 0;
f->read(&c, 1); f->read(&c, 1);
# endif # endif

View file

@ -37,8 +37,6 @@ int fstat(int fd, struct stat* buf);
#else #else
typedef unsigned size_t; typedef unsigned size_t;
typedef int ssize_t; typedef int ssize_t;
errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag,
int pmode);
#endif #endif
#ifndef _WIN32 #ifndef _WIN32

View file

@ -225,10 +225,8 @@ TEST(printf_test, hash_flag) {
EXPECT_PRINTF("-42.0000", "%#g", -42.0); EXPECT_PRINTF("-42.0000", "%#g", -42.0);
EXPECT_PRINTF("-42.0000", "%#G", -42.0); EXPECT_PRINTF("-42.0000", "%#G", -42.0);
safe_sprintf(buffer, "%#a", 16.0); EXPECT_PRINTF("0x1.p+4", "%#a", 16.0);
EXPECT_PRINTF(buffer, "%#a", 16.0); EXPECT_PRINTF("0X1.P+4", "%#A", 16.0);
safe_sprintf(buffer, "%#A", 16.0);
EXPECT_PRINTF(buffer, "%#A", 16.0);
// '#' flag is ignored for non-numeric types. // '#' flag is ignored for non-numeric types.
EXPECT_PRINTF("x", "%#c", 'x'); EXPECT_PRINTF("x", "%#c", 'x');
@ -239,7 +237,7 @@ TEST(printf_test, width) {
// Width cannot be specified twice. // Width cannot be specified twice.
EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error, EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error,
"invalid type specifier"); "invalid format specifier");
EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error, EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error,
"number is too big"); "number is too big");
@ -407,10 +405,7 @@ TEST(printf_test, length) {
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max); EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
} }
TEST(printf_test, bool) { TEST(printf_test, bool) { EXPECT_PRINTF("1", "%d", true); }
EXPECT_PRINTF("1", "%d", true);
EXPECT_PRINTF("true", "%s", true);
}
TEST(printf_test, int) { TEST(printf_test, int) {
EXPECT_PRINTF("-42", "%d", -42); EXPECT_PRINTF("-42", "%d", -42);
@ -534,11 +529,12 @@ TEST(printf_test, wide_string) {
} }
TEST(printf_test, vprintf) { TEST(printf_test, vprintf) {
fmt::format_arg_store<fmt::printf_context, int> as{42}; int n = 42;
fmt::basic_format_args<fmt::printf_context> args(as); auto store = fmt::format_arg_store<fmt::printf_context, int>(n);
EXPECT_EQ(fmt::vsprintf("%d", args), "42"); auto args = fmt::basic_format_args<fmt::printf_context>(store);
EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42"); EXPECT_EQ(fmt::vsprintf(fmt::string_view("%d"), args), "42");
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42"); EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args),
"42");
} }
template <typename... Args> template <typename... Args>
@ -554,31 +550,11 @@ TEST(printf_test, fixed_large_exponent) {
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21)); EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
} }
TEST(printf_test, vsprintf_make_args_example) { TEST(printf_test, make_printf_args) {
fmt::format_arg_store<fmt::printf_context, int, const char*> as{42,
"something"};
fmt::basic_format_args<fmt::printf_context> args(as);
EXPECT_EQ("[42] something happened", fmt::vsprintf("[%d] %s happened", args));
auto as2 = fmt::make_printf_args(42, "something");
fmt::basic_format_args<fmt::printf_context> args2(as2);
EXPECT_EQ("[42] something happened", EXPECT_EQ("[42] something happened",
fmt::vsprintf("[%d] %s happened", args2)); fmt::vsprintf(fmt::string_view("[%d] %s happened"),
EXPECT_EQ("[42] something happened",
fmt::vsprintf("[%d] %s happened",
{fmt::make_printf_args(42, "something")})); {fmt::make_printf_args(42, "something")}));
}
TEST(printf_test, vsprintf_make_wargs_example) {
fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{
42, L"something"};
fmt::basic_format_args<fmt::wprintf_context> args(as);
EXPECT_EQ(L"[42] something happened", EXPECT_EQ(L"[42] something happened",
fmt::vsprintf(L"[%d] %s happened", args)); fmt::vsprintf(fmt::basic_string_view<wchar_t>(L"[%d] %s happened"),
auto as2 = fmt::make_wprintf_args(42, L"something");
fmt::basic_format_args<fmt::wprintf_context> args2(as2);
EXPECT_EQ(L"[42] something happened",
fmt::vsprintf(L"[%d] %s happened", args2));
EXPECT_EQ(L"[42] something happened",
fmt::vsprintf(L"[%d] %s happened",
{fmt::make_wprintf_args(42, L"something")})); {fmt::make_wprintf_args(42, L"something")}));
} }

View file

@ -12,7 +12,10 @@
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include <map> #include <map>
#include <queue>
#include <stack>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "gtest/gtest.h" #include "gtest/gtest.h"
@ -40,6 +43,8 @@ TEST(ranges_test, format_2d_array) {
TEST(ranges_test, format_array_of_literals) { TEST(ranges_test, format_array_of_literals) {
const char* arr[] = {"1234", "abcd"}; const char* arr[] = {"1234", "abcd"};
EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]"); EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
EXPECT_EQ(fmt::format("{:n}", arr), "\"1234\", \"abcd\"");
EXPECT_EQ(fmt::format("{:n:}", arr), "1234, abcd");
} }
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY #endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
@ -47,17 +52,28 @@ TEST(ranges_test, format_vector) {
auto v = std::vector<int>{1, 2, 3, 5, 7, 11}; auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]"); EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]"); EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
auto vc = std::vector<char>{'a', 'b', 'c'};
auto vvc = std::vector<std::vector<char>>{vc, vc};
EXPECT_EQ(fmt::format("{}", vc), "['a', 'b', 'c']");
EXPECT_EQ(fmt::format("{}", vvc), "[['a', 'b', 'c'], ['a', 'b', 'c']]");
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");
} }
TEST(ranges_test, format_vector2) { TEST(ranges_test, format_vector2) {
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}}; auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]"); EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]"); EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]");
EXPECT_EQ(fmt::format("{:n:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
} }
TEST(ranges_test, format_map) { TEST(ranges_test, format_map) {
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}}; auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}"); EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
} }
TEST(ranges_test, format_set) { TEST(ranges_test, format_set) {
@ -65,6 +81,35 @@ TEST(ranges_test, format_set) {
"{\"one\", \"two\"}"); "{\"one\", \"two\"}");
} }
// Models std::flat_set close enough to test if no ambiguous lookup of a
// formatter happens due to the flat_set type matching is_set and
// is_container_adaptor_like
template <typename T> class flat_set {
public:
using key_type = T;
using container_type = std::vector<T>;
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
template <typename... Ts>
explicit flat_set(Ts&&... args) : c{std::forward<Ts>(args)...} {}
iterator begin() { return c.begin(); }
const_iterator begin() const { return c.begin(); }
iterator end() { return c.end(); }
const_iterator end() const { return c.end(); }
private:
std::vector<T> c;
};
TEST(ranges_test, format_flat_set) {
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
"{\"one\", \"two\"}");
}
namespace adl { namespace adl {
struct box { struct box {
int value; int value;
@ -91,6 +136,7 @@ TEST(ranges_test, format_tuple) {
auto t = auto t =
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i'); 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("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()"); EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value)); EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
@ -103,6 +149,25 @@ TEST(ranges_test, format_tuple) {
EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value)); EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
} }
struct not_default_formattable {};
struct bad_format {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<not_default_formattable> {
auto parse(format_parse_context&) -> const char* { throw bad_format(); }
auto format(not_default_formattable, format_context& ctx)
-> format_context::iterator {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(ranges_test, tuple_parse_calls_element_parse) {
auto f = fmt::formatter<std::tuple<not_default_formattable>>();
auto ctx = fmt::format_parse_context("");
EXPECT_THROW(f.parse(ctx), bad_format);
}
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT #ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
struct tuple_like { struct tuple_like {
int i; int i;
@ -144,32 +209,24 @@ TEST(ranges_test, format_to) {
EXPECT_STREQ(buf, "[1, 2, 3]"); EXPECT_STREQ(buf, "[1, 2, 3]");
} }
struct path_like { template <typename Char> struct path_like {
const path_like* begin() const; const path_like* begin() const;
const path_like* end() const; const path_like* end() const;
operator std::string() const; operator std::basic_string<Char>() const;
}; };
TEST(ranges_test, path_like) { TEST(ranges_test, disabled_range_formatting_of_path) {
EXPECT_FALSE((fmt::is_range<path_like, char>::value)); // Range formatting of path is disabled because of infinite recursion
// (path element is itself a path).
EXPECT_EQ((fmt::range_format_kind<path_like<char>, char>::value),
fmt::range_format::disabled);
EXPECT_EQ((fmt::range_format_kind<path_like<wchar_t>, char>::value),
fmt::range_format::disabled);
} }
#ifdef FMT_USE_STRING_VIEW // A range that provides non-const only begin()/end() to test fmt::join
struct string_like { // handles that.
const char* begin();
const char* end();
operator fmt::string_view() const { return "foo"; }
operator std::string_view() const { return "foo"; }
};
TEST(ranges_test, format_string_like) {
EXPECT_EQ(fmt::format("{}", string_like()), "foo");
}
#endif // FMT_USE_STRING_VIEW
// A range that provides non-const only begin()/end() to test fmt::join handles
// that.
// //
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache // Some ranges (e.g. those produced by range-v3's views::filter()) can cache
// information during iteration so they only provide non-const begin()/end(). // information during iteration so they only provide non-const begin()/end().
@ -193,7 +250,7 @@ template <typename T> class noncopyable_range {
std::vector<T> vec; std::vector<T> vec;
public: public:
using const_iterator = typename ::std::vector<T>::const_iterator; using iterator = typename ::std::vector<T>::iterator;
template <typename... Args> template <typename... Args>
explicit noncopyable_range(Args&&... args) explicit noncopyable_range(Args&&... args)
@ -202,8 +259,8 @@ template <typename T> class noncopyable_range {
noncopyable_range(noncopyable_range const&) = delete; noncopyable_range(noncopyable_range const&) = delete;
noncopyable_range(noncopyable_range&) = delete; noncopyable_range(noncopyable_range&) = delete;
const_iterator begin() const { return vec.begin(); } iterator begin() { return vec.begin(); }
const_iterator end() const { return vec.end(); } iterator end() { return vec.end(); }
}; };
TEST(ranges_test, range) { TEST(ranges_test, range) {
@ -232,7 +289,6 @@ TEST(ranges_test, enum_range) {
} }
#if !FMT_MSC_VERSION #if !FMT_MSC_VERSION
TEST(ranges_test, unformattable_range) { TEST(ranges_test, unformattable_range) {
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>, EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
fmt::format_context>::value)); fmt::format_context>::value));
@ -364,7 +420,7 @@ TEST(ranges_test, is_printable) {
EXPECT_FALSE(is_printable(0x110000)); EXPECT_FALSE(is_printable(0x110000));
} }
TEST(ranges_test, escape_string) { TEST(ranges_test, escape) {
using vec = std::vector<std::string>; using vec = std::vector<std::string>;
EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]"); EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]"); EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
@ -375,22 +431,22 @@ TEST(ranges_test, escape_string) {
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]"); EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
// Unassigned Unicode code points. // Unassigned Unicode code points.
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]"); EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
// Broken utf-8.
EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}), EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}),
"[\"\\xf4\\x8f\\xbf\\xc0\"]"); "[\"\\xf4\\x8f\\xbf\\xc0\"]");
EXPECT_EQ(fmt::format("{}", vec{"понедельник"}), "[\"понедельник\"]"); EXPECT_EQ(fmt::format("{}", vec{"\xf0\x28"}), "[\"\\xf0(\"]");
} EXPECT_EQ(fmt::format("{}", vec{"\xe1\x28"}), "[\"\\xe1(\"]");
EXPECT_EQ(fmt::format("{}", vec{std::string("\xf0\x28\0\0anything", 12)}),
"[\"\\xf0(\\x00\\x00anything\"]");
// Correct utf-8.
EXPECT_EQ(fmt::format("{}", vec{"🦄"}), "[\"🦄\"]");
} }
#ifdef FMT_USE_STRING_VIEW EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
struct convertible_to_string_view { "[['x']]");
operator std::string_view() const { return "foo"; } EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
};
TEST(ranges_test, escape_convertible_to_string_view) {
EXPECT_EQ(fmt::format("{}", std::vector<convertible_to_string_view>(1)),
"[\"foo\"]");
} }
#endif // FMT_USE_STRING_VIEW
template <typename R> struct fmt_ref_view { template <typename R> struct fmt_ref_view {
R* r; R* r;
@ -406,3 +462,66 @@ TEST(ranges_test, range_of_range_of_mixed_const) {
fmt_ref_view<decltype(v)> r{&v}; fmt_ref_view<decltype(v)> r{&v};
EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]"); EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]");
} }
TEST(ranges_test, vector_char) {
EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']");
}
TEST(ranges_test, container_adaptor) {
{
using fmt::detail::is_container_adaptor_like;
using T = std::nullptr_t;
static_assert(is_container_adaptor_like<std::stack<T>>::value, "");
static_assert(is_container_adaptor_like<std::queue<T>>::value, "");
static_assert(is_container_adaptor_like<std::priority_queue<T>>::value, "");
static_assert(!is_container_adaptor_like<std::vector<T>>::value, "");
}
{
auto s = std::stack<int>();
s.push(1);
s.push(2);
EXPECT_EQ(fmt::format("{}", s), "[1, 2]");
EXPECT_EQ(fmt::format("{}", const_cast<const decltype(s)&>(s)), "[1, 2]");
}
{
auto q = std::queue<int>();
q.push(1);
q.push(2);
EXPECT_EQ(fmt::format("{}", q), "[1, 2]");
}
{
auto q = std::priority_queue<int>();
q.push(3);
q.push(1);
q.push(2);
q.push(4);
EXPECT_EQ(fmt::format("{}", q), "[4, 3, 2, 1]");
}
{
auto s = std::stack<char, std::string>();
s.push('a');
s.push('b');
// See https://cplusplus.github.io/LWG/issue3881.
EXPECT_EQ(fmt::format("{}", s), "['a', 'b']");
}
{
struct my_container_adaptor {
using value_type = int;
using container_type = std::vector<value_type>;
void push(const value_type& v) { c.push_back(v); }
protected:
container_type c;
};
auto m = my_container_adaptor();
m.push(1);
m.push(2);
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
}
}

View file

@ -65,7 +65,7 @@ TEST(scan_test, read_string_view) {
EXPECT_EQ(s, "foo"); EXPECT_EQ(s, "foo");
} }
#ifndef _WIN32 #ifdef FMT_HAVE_STRPTIME
namespace fmt { namespace fmt {
template <> struct scanner<tm> { template <> struct scanner<tm> {
std::string format; std::string format;

View file

@ -42,7 +42,7 @@ struct scan_context {
public: public:
using iterator = const char*; using iterator = const char*;
explicit scan_context(string_view input) : input_(input) {} explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {}
iterator begin() const { return input_.data(); } iterator begin() const { return input_.data(); }
iterator end() const { return begin() + input_.size(); } iterator end() const { return begin() + input_.size(); }
@ -83,17 +83,21 @@ class scan_arg {
// TODO: more types // TODO: more types
}; };
scan_arg() : type(scan_type::none_type) {} FMT_CONSTEXPR scan_arg() : type(scan_type::none_type), int_value(nullptr) {}
scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {} FMT_CONSTEXPR scan_arg(int& value)
scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {} : type(scan_type::int_type), int_value(&value) {}
scan_arg(long long& value) FMT_CONSTEXPR scan_arg(unsigned& value)
: type(scan_type::uint_type), uint_value(&value) {}
FMT_CONSTEXPR scan_arg(long long& value)
: type(scan_type::long_long_type), long_long_value(&value) {} : type(scan_type::long_long_type), long_long_value(&value) {}
scan_arg(unsigned long long& value) FMT_CONSTEXPR scan_arg(unsigned long long& value)
: type(scan_type::ulong_long_type), ulong_long_value(&value) {} : type(scan_type::ulong_long_type), ulong_long_value(&value) {}
scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {} FMT_CONSTEXPR scan_arg(std::string& value)
scan_arg(fmt::string_view& value) : type(scan_type::string_type), string(&value) {}
FMT_CONSTEXPR scan_arg(fmt::string_view& value)
: type(scan_type::string_view_type), string_view(&value) {} : type(scan_type::string_view_type), string_view(&value) {}
template <typename T> scan_arg(T& value) : type(scan_type::custom_type) { template <typename T>
FMT_CONSTEXPR scan_arg(T& value) : type(scan_type::custom_type) {
custom.value = &value; custom.value = &value;
custom.scan = scan_custom_arg<T>; custom.scan = scan_custom_arg<T>;
} }
@ -114,7 +118,7 @@ struct scan_args {
const detail::scan_arg* data; const detail::scan_arg* data;
template <size_t N> template <size_t N>
scan_args(const std::array<detail::scan_arg, N>& store) FMT_CONSTEXPR scan_args(const std::array<detail::scan_arg, N>& store)
: size(N), data(store.data()) { : size(N), data(store.data()) {
static_assert(N < INT_MAX, "too many arguments"); static_assert(N < INT_MAX, "too many arguments");
} }
@ -154,7 +158,8 @@ struct scan_handler : error_handler {
} }
public: public:
scan_handler(string_view format, string_view input, scan_args args) FMT_CONSTEXPR scan_handler(string_view format, string_view input,
scan_args args)
: parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {} : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {}
const char* pos() const { return scan_ctx_.begin(); } const char* pos() const { return scan_ctx_.begin(); }
@ -162,10 +167,8 @@ struct scan_handler : error_handler {
void on_text(const char* begin, const char* end) { void on_text(const char* begin, const char* end) {
auto size = to_unsigned(end - begin); auto size = to_unsigned(end - begin);
auto it = scan_ctx_.begin(); auto it = scan_ctx_.begin();
if (it + size > scan_ctx_.end() || if (it + size > scan_ctx_.end() || !std::equal(begin, end, it))
!std::equal(begin, end, make_checked(it, size))) {
on_error("invalid input"); on_error("invalid input");
}
scan_ctx_.advance_to(it + size); scan_ctx_.advance_to(it + size);
} }

View file

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.8...3.25)
project(fmt-link CXX) project(fmt-link CXX)

View file

@ -6,52 +6,110 @@
// For the license information refer to format.h. // For the license information refer to format.h.
#include "fmt/std.h" #include "fmt/std.h"
#include "fmt/ranges.h"
#include <bitset>
#include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
#include "gtest/gtest.h" #include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith
#ifdef __cpp_lib_filesystem
TEST(std_test, path) { TEST(std_test, path) {
// Test ambiguity problem described in #2954. We need to exclude compilers using std::filesystem::path;
// where the ambiguity problem cannot be solved for now. EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
#if defined(__cpp_lib_filesystem) && \ EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
(!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920) EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" ");
EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")), EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar");
"\"foo\\\"bar.txt\""); EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\"");
# ifdef _WIN32 # ifdef _WIN32
// File.txt in Russian. EXPECT_EQ(fmt::format("{}", path(
const wchar_t unicode_path[] = {0x424, 0x430, 0x439, 0x43b, 0x2e, L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
0x74, 0x78, 0x74, 0}; L"\x0447\x044B\x043D\x0430")),
const char unicode_u8path[] = {'"', char(0xd0), char(0xa4), char(0xd0), "Шчучыншчына");
char(0xb0), char(0xd0), char(0xb9), char(0xd0), EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
char(0xbb), '.', 't', 'x', EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
't', '"', '\0'};
EXPECT_EQ(fmt::format("{}", std::filesystem::path(unicode_path)),
unicode_u8path);
# endif
# endif # endif
} }
// Test ambiguity problem described in #2954.
TEST(ranges_std_test, format_vector_path) { TEST(ranges_std_test, format_vector_path) {
// Test ambiguity problem described in #2954. We need to exclude compilers
// where the ambiguity problem cannot be solved for now.
#if defined(__cpp_lib_filesystem) && \
(!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920)
auto p = std::filesystem::path("foo/bar.txt"); auto p = std::filesystem::path("foo/bar.txt");
auto c = std::vector<std::string>{"abc", "def"}; auto c = std::vector<std::string>{"abc", "def"};
EXPECT_EQ(fmt::format("path={}, range={}", p, c), EXPECT_EQ(fmt::format("path={}, range={}", p, c),
"path=\"foo/bar.txt\", range=[\"abc\", \"def\"]"); "path=foo/bar.txt, range=[\"abc\", \"def\"]");
}
// Test that path is not escaped twice in the debug mode.
TEST(ranges_std_test, format_quote_path) {
auto vec =
std::vector<std::filesystem::path>{"path1/file1.txt", "path2/file2.txt"};
EXPECT_EQ(fmt::format("{}", vec),
"[\"path1/file1.txt\", \"path2/file2.txt\"]");
# ifdef __cpp_lib_optional
auto o = std::optional<std::filesystem::path>("path/file.txt");
EXPECT_EQ(fmt::format("{}", o), "optional(\"path/file.txt\")");
EXPECT_EQ(fmt::format("{:?}", o), "optional(\"path/file.txt\")");
# endif # endif
} }
#endif
TEST(std_test, thread_id) { TEST(std_test, thread_id) {
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty()); EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
} }
TEST(std_test, optional) {
#ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")");
EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2},
std::optional{3}}),
"[optional(1), optional(2), optional(3)]");
EXPECT_EQ(
fmt::format("{}", std::optional<std::optional<const char*>>{{"nested"}}),
"optional(optional(\"nested\"))");
EXPECT_EQ(
fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30),
"optional(\"left aligned\" )");
EXPECT_EQ(
fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}),
"optional([104, 101, 108, 108, 111])");
EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}),
"optional(\"string\")");
EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')");
EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)");
struct unformattable {};
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));
#endif
}
struct throws_on_move {
throws_on_move() = default;
[[noreturn]] throws_on_move(throws_on_move&&) {
throw std::runtime_error("Thrown by throws_on_move");
}
throws_on_move(const throws_on_move&) = default;
};
namespace fmt {
template <> struct formatter<throws_on_move> : formatter<string_view> {
auto format(const throws_on_move&, format_context& ctx) const
-> decltype(ctx.out()) {
string_view str("<throws_on_move>");
return formatter<string_view>::format(str, ctx);
}
};
} // namespace fmt
TEST(std_test, variant) { TEST(std_test, variant) {
#ifdef __cpp_lib_variant #ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate"); EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");
@ -80,5 +138,120 @@ TEST(std_test, variant) {
EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)"); EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)");
EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")"); EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")");
volatile int i = 42; // Test compile error before GCC 11 described in #3068.
EXPECT_EQ(fmt::format("{}", i), "42");
std::variant<std::monostate, throws_on_move> v6;
try {
throws_on_move thrower;
v6.emplace<throws_on_move>(std::move(thrower));
} catch (const std::runtime_error&) {
}
// v6 is now valueless by exception
EXPECT_EQ(fmt::format("{}", v6), "variant(valueless by exception)");
#endif #endif
} }
TEST(std_test, error_code) {
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() {
try {
throw std::runtime_error("Test Exception");
} catch (const Catch& ex) {
EXPECT_EQ("Test Exception", fmt::format("{}", ex));
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{:t}", ex));
}
}
namespace my_ns1 {
namespace my_ns2 {
struct my_exception : public std::exception {
private:
std::string msg;
public:
my_exception(const std::string& s) : msg(s) {}
const char* what() const noexcept override;
};
const char* my_exception::what() const noexcept { return msg.c_str(); }
} // namespace my_ns2
} // namespace my_ns1
TEST(std_test, exception) {
using testing::StartsWith;
exception_test<std::exception>();
exception_test<std::runtime_error>();
try {
using namespace my_ns1::my_ns2;
throw my_exception("My Exception");
} catch (const std::exception& ex) {
EXPECT_EQ("my_ns1::my_ns2::my_exception: My Exception",
fmt::format("{:t}", ex));
EXPECT_EQ("My Exception", fmt::format("{:}", ex));
}
try {
throw std::system_error(std::error_code(), "message");
} catch (const std::system_error& ex) {
EXPECT_THAT(fmt::format("{:t}", ex), StartsWith("std::system_error: "));
}
#ifdef __cpp_lib_filesystem
// Tests that the inline namespace is stripped out, e.g.
// std::filesystem::__cxx11::* -> std::filesystem::*.
try {
throw std::filesystem::filesystem_error("message", std::error_code());
} catch (const std::filesystem::filesystem_error& ex) {
EXPECT_THAT(fmt::format("{:t}", ex),
StartsWith("std::filesystem::filesystem_error: "));
}
#endif
}
TEST(std_test, format_bit_reference) {
std::bitset<2> bs(1);
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
std::vector<bool> v = {true, false};
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
}
TEST(std_test, format_const_bit_reference) {
const std::bitset<2> bs(1);
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
const std::vector<bool> v = {true, false};
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
}
TEST(std_test, format_atomic) {
std::atomic<bool> b(false);
EXPECT_EQ(fmt::format("{}", b), "false");
const std::atomic<bool> cb(true);
EXPECT_EQ(fmt::format("{}", cb), "true");
}
#ifdef __cpp_lib_atomic_flag_test
TEST(std_test, format_atomic_flag) {
std::atomic_flag f = ATOMIC_FLAG_INIT;
(void) f.test_and_set();
EXPECT_EQ(fmt::format("{}", f), "true");
const std::atomic_flag cf = ATOMIC_FLAG_INIT;
EXPECT_EQ(fmt::format("{}", cf), "false");
}
#endif // __cpp_lib_atomic_flag_test

View file

@ -18,12 +18,12 @@ using testing::Contains;
TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); } TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); }
TEST(unicode_test, legacy_locale) { TEST(unicode_test, legacy_locale) {
auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251"); auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");
if (loc == std::locale::classic()) return; if (loc == std::locale::classic()) return;
auto s = std::string(); auto s = std::string();
try { try {
s = fmt::format(loc, "День недели: {:L}", fmt::weekday(1)); s = fmt::format(loc, "Дзень тыдня: {:L}", fmt::weekday(1));
} catch (const fmt::format_error& e) { } catch (const fmt::format_error& e) {
// Formatting can fail due to an unsupported encoding. // Formatting can fail due to an unsupported encoding.
fmt::print("Format error: {}\n", e.what()); fmt::print("Format error: {}\n", e.what());
@ -38,11 +38,11 @@ TEST(unicode_test, legacy_locale) {
os << std::put_time(&tm, "%a"); os << std::put_time(&tm, "%a");
auto wd = os.str(); auto wd = os.str();
if (wd == "??") { if (wd == "??") {
EXPECT_EQ(s, "День недели: ??"); EXPECT_EQ(s, "Дзень тыдня: ??");
fmt::print("std::locale gives ?? as a weekday.\n"); fmt::print("std::locale gives ?? as a weekday.\n");
return; return;
} }
#endif #endif
EXPECT_THAT((std::vector<std::string>{"День недели: пн", "День недели: Пн"}), EXPECT_THAT((std::vector<std::string>{"Дзень тыдня: пн", "Дзень тыдня: Пан"}),
Contains(s)); Contains(s));
} }

View file

@ -10,11 +10,7 @@
#include <locale> #include <locale>
#include <string> #include <string>
#ifdef FMT_MODULE_TEST
import fmt;
#else
#include "fmt/os.h" #include "fmt/os.h"
#endif // FMT_MODULE_TEST
#ifdef _MSC_VER #ifdef _MSC_VER
# define FMT_VSNPRINTF vsprintf_s # define FMT_VSNPRINTF vsprintf_s
@ -81,5 +77,5 @@ class date {
int day() const { return day_; } int day() const { return day_; }
}; };
// Returns a locale with the given name if available or classic locale othewise. // Returns a locale with the given name if available or classic locale otherwise.
std::locale get_locale(const char* name, const char* alt_name = nullptr); std::locale get_locale(const char* name, const char* alt_name = nullptr);

View file

@ -7,6 +7,7 @@
#include "fmt/xchar.h" #include "fmt/xchar.h"
#include <algorithm>
#include <complex> #include <complex>
#include <cwchar> #include <cwchar>
#include <vector> #include <vector>
@ -15,31 +16,21 @@
#include "fmt/color.h" #include "fmt/color.h"
#include "fmt/ostream.h" #include "fmt/ostream.h"
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "fmt/std.h"
#include "gtest-extra.h" // Contains #include "gtest-extra.h" // Contains
#include "util.h" // get_locale #include "util.h" // get_locale
using fmt::detail::max_value; using fmt::detail::max_value;
using testing::Contains; using testing::Contains;
namespace test_ns { #if defined(__MINGW32__) && !defined(_UCRT)
template <typename Char> class test_string { // Only C89 conversion specifiers when using MSVCRT instead of UCRT
private: # define FMT_HAS_C99_STRFTIME 0
std::basic_string<Char> s_; #else
# define FMT_HAS_C99_STRFTIME 1
public: #endif
test_string(const Char* s) : s_(s) {}
const Char* data() const { return s_.data(); }
size_t length() const { return s_.size(); }
operator const Char*() const { return s_.c_str(); }
};
template <typename Char>
fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
return {s.data(), s.length()};
}
struct non_string {}; struct non_string {};
} // namespace test_ns
template <typename T> class is_string_test : public testing::Test {}; template <typename T> class is_string_test : public testing::Test {};
@ -61,8 +52,7 @@ TYPED_TEST(is_string_test, is_string) {
using fmt_string_view = fmt::detail::std_string_view<TypeParam>; using fmt_string_view = fmt::detail::std_string_view<TypeParam>;
EXPECT_TRUE(std::is_empty<fmt_string_view>::value != EXPECT_TRUE(std::is_empty<fmt_string_view>::value !=
fmt::detail::is_string<fmt_string_view>::value); fmt::detail::is_string<fmt_string_view>::value);
EXPECT_TRUE(fmt::detail::is_string<test_ns::test_string<TypeParam>>::value); EXPECT_FALSE(fmt::detail::is_string<non_string>::value);
EXPECT_FALSE(fmt::detail::is_string<test_ns::non_string>::value);
} }
// std::is_constructible is broken in MSVC until version 2015. // std::is_constructible is broken in MSVC until version 2015.
@ -84,7 +74,7 @@ TEST(xchar_test, format) {
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error); EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
EXPECT_EQ(L"true", fmt::format(L"{}", true)); EXPECT_EQ(L"true", fmt::format(L"{}", true));
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a')); EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
@ -98,8 +88,9 @@ TEST(xchar_test, is_formattable) {
} }
TEST(xchar_test, compile_time_string) { TEST(xchar_test, compile_time_string) {
EXPECT_EQ(fmt::format(fmt::wformat_string<int>(L"{}"), 42), L"42");
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); EXPECT_EQ(fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42), L"42");
#endif #endif
} }
@ -111,10 +102,12 @@ struct custom_char {
template <typename T> template <typename T>
constexpr custom_char(T val) : value(static_cast<int>(val)) {} constexpr custom_char(T val) : value(static_cast<int>(val)) {}
operator int() const { return value; } operator char() const {
return value <= 0xff ? static_cast<char>(value) : '\0';
}
}; };
int to_ascii(custom_char c) { return c; } auto to_ascii(custom_char c) -> char { return c; }
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> struct is_char<custom_char> : std::true_type {}; template <> struct is_char<custom_char> : std::true_type {};
@ -153,17 +146,21 @@ TEST(xchar_test, format_to) {
} }
TEST(xchar_test, vformat_to) { TEST(xchar_test, vformat_to) {
using wcontext = fmt::wformat_context; auto args = fmt::make_wformat_args(42);
fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42);
auto wargs = fmt::basic_format_args<wcontext>(&warg, 1);
auto w = std::wstring(); auto w = std::wstring();
fmt::vformat_to(std::back_inserter(w), L"{}", wargs); fmt::vformat_to(std::back_inserter(w), L"{}", args);
EXPECT_EQ(L"42", w);
w.clear();
fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs);
EXPECT_EQ(L"42", w); EXPECT_EQ(L"42", w);
} }
namespace test {
struct struct_as_wstring_view {};
auto format_as(struct_as_wstring_view) -> fmt::wstring_view { return L"foo"; }
} // namespace test
TEST(xchar_test, format_as) {
EXPECT_EQ(fmt::format(L"{}", test::struct_as_wstring_view()), L"foo");
}
TEST(format_test, wide_format_to_n) { TEST(format_test, wide_format_to_n) {
wchar_t buffer[4]; wchar_t buffer[4];
buffer[3] = L'x'; buffer[3] = L'x';
@ -199,7 +196,10 @@ TEST(xchar_test, named_arg_udl) {
TEST(xchar_test, print) { TEST(xchar_test, print) {
// Check that the wide print overload compiles. // Check that the wide print overload compiles.
if (fmt::detail::const_check(false)) fmt::print(L"test"); if (fmt::detail::const_check(false)) {
fmt::print(L"test");
fmt::println(L"test");
}
} }
TEST(xchar_test, join) { TEST(xchar_test, join) {
@ -229,11 +229,24 @@ TEST(xchar_test, enum) {
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum())); EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
} }
struct streamable_and_unformattable {};
auto operator<<(std::wostream& os, streamable_and_unformattable)
-> std::wostream& {
return os << L"foo";
}
TEST(xchar_test, streamed) {
EXPECT_FALSE(fmt::is_formattable<streamable_and_unformattable>());
EXPECT_EQ(fmt::format(L"{}", fmt::streamed(streamable_and_unformattable())),
L"foo");
}
TEST(xchar_test, sign_not_truncated) { TEST(xchar_test, sign_not_truncated) {
wchar_t format_str[] = { wchar_t format_str[] = {
L'{', L':', L'{', L':',
'+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0}; '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error); EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error);
} }
TEST(xchar_test, chrono) { TEST(xchar_test, chrono) {
@ -269,8 +282,9 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
#endif #endif
} }
TEST(chrono_test, time_point) { TEST(chrono_test_wchar, time_point) {
auto t1 = std::chrono::system_clock::now(); auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::now());
std::vector<std::wstring> spec_list = { std::vector<std::wstring> spec_list = {
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C", L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
@ -278,14 +292,36 @@ TEST(chrono_test, time_point) {
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e", 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"%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"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"}; L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
#ifndef _WIN32 #ifndef _WIN32
// Disabled on Windows, because these formats is not consistent among // Disabled on Windows, because these formats is not consistent among
// platforms. // platforms.
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"}); 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 #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) { for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1); auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t); auto tm = *std::localtime(&t);
@ -293,8 +329,38 @@ TEST(chrono_test, time_point) {
auto sys_output = system_wcsftime(spec, &tm); auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec); auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt_spec, t1)); EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
EXPECT_EQ(sys_output, fmt::format(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));
} }
} }
@ -305,9 +371,17 @@ TEST(xchar_test, color) {
TEST(xchar_test, ostream) { TEST(xchar_test, ostream) {
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409 #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
{
std::wostringstream wos; std::wostringstream wos;
fmt::print(wos, L"Don't {}!", L"panic"); fmt::print(wos, L"Don't {}!", L"panic");
EXPECT_EQ(wos.str(), L"Don't panic!"); EXPECT_EQ(wos.str(), L"Don't panic!");
}
{
std::wostringstream wos;
fmt::println(wos, L"Don't {}!", L"panic");
EXPECT_EQ(wos.str(), L"Don't panic!\n");
}
#endif #endif
} }
@ -325,6 +399,7 @@ TEST(xchar_test, escape_string) {
TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); } TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Char> struct numpunct : std::numpunct<Char> { template <typename Char> struct numpunct : std::numpunct<Char> {
protected: protected:
Char do_decimal_point() const override { return '?'; } Char do_decimal_point() const override { return '?'; }
@ -360,6 +435,9 @@ TEST(locale_test, localized_double) {
EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5"); EXPECT_EQ(fmt::format(loc, "{:L}", 1234.5), "1~234?5");
EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000"); EXPECT_EQ(fmt::format(loc, "{:L}", 12000.0), "12~000");
EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), " 1~230"); EXPECT_EQ(fmt::format(loc, "{:8L}", 1230.0), " 1~230");
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 0.1), " 0?100000");
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1.0), " 1?000000");
EXPECT_EQ(fmt::format(loc, "{:15.6Lf}", 1e3), " 1~000?000000");
} }
TEST(locale_test, format) { TEST(locale_test, format) {
@ -368,8 +446,8 @@ TEST(locale_test, format) {
EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567)); EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567)); EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256)); EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
fmt::format_arg_store<fmt::format_context, int> as{1234567}; auto n = 1234567;
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as))); EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::make_format_args(n)));
auto s = std::string(); auto s = std::string();
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567); fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
EXPECT_EQ("1~234~567", s); EXPECT_EQ("1~234~567", s);
@ -402,10 +480,9 @@ TEST(locale_test, wformat) {
auto loc = std::locale(std::locale(), new numpunct<wchar_t>()); auto loc = std::locale(std::locale(), new numpunct<wchar_t>());
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567)); EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567)); EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
using wcontext = fmt::buffer_context<wchar_t>; int n = 1234567;
fmt::format_arg_store<wcontext, int> as{1234567};
EXPECT_EQ(L"1~234~567", EXPECT_EQ(L"1~234~567",
fmt::vformat(loc, L"{:L}", fmt::basic_format_args<wcontext>(as))); fmt::vformat(loc, L"{:L}", fmt::make_wformat_args(n)));
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567)); EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>()); auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>());
@ -422,16 +499,16 @@ TEST(locale_test, wformat) {
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>())); fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
} }
TEST(locale_test, double_formatter) { TEST(locale_test, int_formatter) {
auto loc = std::locale(std::locale(), new special_grouping<char>()); auto loc = std::locale(std::locale(), new special_grouping<char>());
auto f = fmt::formatter<int>(); auto f = fmt::formatter<int>();
auto parse_ctx = fmt::format_parse_context("L"); auto parse_ctx = fmt::format_parse_context("L");
f.parse(parse_ctx); f.parse(parse_ctx);
char buf[10] = {}; auto buf = fmt::memory_buffer();
fmt::basic_format_context<char*, char> format_ctx( fmt::basic_format_context<fmt::appender, char> format_ctx(
buf, {}, fmt::detail::locale_ref(loc)); fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
*f.format(12345, format_ctx) = 0; f.format(12345, format_ctx);
EXPECT_STREQ("12,345", buf); EXPECT_EQ(fmt::to_string(buf), "12,345");
} }
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
@ -442,13 +519,10 @@ template <class charT> struct formatter<std::complex<double>, charT> {
public: public:
FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse( FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
basic_format_parse_context<charT>& ctx) { basic_format_parse_context<charT>& ctx) {
using handler_type = auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::dynamic_specs_handler<basic_format_parse_context<charT>>; detail::type::float_type);
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx), detail::parse_float_type_spec(specs_, detail::error_handler());
detail::type::string_type); return end;
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
detail::parse_float_type_spec(specs_, ctx.error_handler());
return it;
} }
template <class FormatContext> template <class FormatContext>
@ -495,4 +569,16 @@ TEST(locale_test, chrono_weekday) {
std::locale::global(loc_old); std::locale::global(loc_old);
} }
TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
}
TEST(std_test_xchar, optional) {
# ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')");
EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}),
L"optional(\"wide string\")");
# endif
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR #endif // FMT_STATIC_THOUSANDS_SEPARATOR