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:
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
By submitting this pull request, you agree that your contributions are licensed
under the {fmt} license, and agree to future changes to the licensing.
By submitting this pull request, you agree to license your contribution(s)
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
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
run: |

View file

@ -7,66 +7,83 @@ permissions:
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: ubuntu-20.04
strategy:
matrix:
cxx: [g++-4.8, g++-10, clang++-9]
build_type: [Debug, Release]
std: [11]
os: [ubuntu-18.04]
include:
- cxx: g++-4.8
install: sudo apt install g++-4.8
os: ubuntu-18.04
- cxx: g++-8
build_type: Debug
std: 14
install: sudo apt install g++-8
os: ubuntu-18.04
- cxx: g++-8
build_type: Debug
std: 17
install: sudo apt install g++-8
os: ubuntu-18.04
- cxx: g++-9
build_type: Debug
std: 17
- cxx: g++-10
build_type: Debug
std: 17
os: ubuntu-18.04
- cxx: g++-11
build_type: Debug
std: 20
os: ubuntu-20.04
install: sudo apt install g++-11
- cxx: clang++-8
build_type: Debug
std: 17
cxxflags: -stdlib=libc++
os: ubuntu-18.04
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
- cxx: clang++-9
install: sudo apt install clang-9
- cxx: clang++-9
build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
std: 17
os: ubuntu-18.04
install: sudo apt install clang-9
- cxx: clang++-11
build_type: Debug
std: 20
os: ubuntu-20.04
- cxx: clang++-11
build_type: Debug
std: 20
cxxflags: -stdlib=libc++
os: ubuntu-20.04
install: sudo apt install libc++-11-dev libc++abi-11-dev
- shared: -DBUILD_SHARED_LIBS=ON
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
run: |
${{matrix.install}}
sudo apt update
${{matrix.install}}
sudo apt install locales-all
cmake -E make_directory ${{runner.workspace}}/build

View file

@ -7,15 +7,29 @@ permissions:
jobs:
build:
runs-on: macos-10.15
strategy:
matrix:
os: [macos-11, macos-13]
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:
- shared: -DBUILD_SHARED_LIBS=ON
runs-on: '${{ matrix.os }}'
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
run: cmake -E make_directory ${{runner.workspace}}/build
@ -24,6 +38,7 @@ jobs:
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-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.
os: [windows-2019]
platform: [Win32, x64]
toolset: [v140, v141, v142]
standard: [14, 17, 20]
shared: ["", -DBUILD_SHARED_LIBS=ON]
build_type: [Debug, Release]
standard: [11, 17, 20]
exclude:
- { toolset: v140, standard: 17 }
- { toolset: v140, standard: 20 }
- { toolset: v141, standard: 14 }
- { toolset: v141, standard: 20 }
- { toolset: v142, standard: 14 }
- { platform: Win32, toolset: v140 }
- { platform: Win32, toolset: v141 }
- { platform: Win32, standard: 14 }
- { platform: Win32, standard: 20 }
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
include:
- os: windows-2019
platform: Win32
build_type: Debug
shared: -DBUILD_SHARED_LIBS=ON
- os: windows-2022
platform: x64
toolset: v143
build_type: Debug
standard: 20
exclude:
- os: windows-2019
standard: 11
platform: Win32
- os: windows-2019
standard: 20
platform: Win32
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time"
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -45,9 +54,9 @@ jobs:
shell: bash
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
-A ${{matrix.platform}} \
cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
$GITHUB_WORKSPACE
- name: Build
@ -61,3 +70,31 @@ jobs:
run: ctest -C ${{matrix.build_type}} -V
env:
CTEST_OUTPUT_ON_FAILURE: True
mingw:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
strategy:
matrix:
sys: [ mingw64, 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.
if(${CMAKE_VERSION} VERSION_LESS 3.12)
@ -24,15 +24,86 @@ function(join result_var)
set(${result_var} "${result}" PARENT_SCOPE)
endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target)
if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI})
endif ()
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
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()
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.
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ON)
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
@ -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_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)
# The tests require {fmt} to be compiled as traditional library
message(STATUS "Testing is incompatible with build mode 'module'.")
@ -101,6 +162,10 @@ set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
if (FMT_SYSTEM_HEADERS)
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
endif ()
if(CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
set(FMT_TEST OFF)
message(STATUS "MSDOS is incompatible with gtest")
endif()
# Get version from 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}")
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 ()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
include(cxx14)
include(CheckCXXCompilerFlag)
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)
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
"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
format-inl.h os.h ostream.h printf.h ranges.h std.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 ()
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)
if (FMT_MODULE)
enable_module(fmt)
endif ()
if (FMT_WERROR)
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
@ -237,11 +296,12 @@ endif ()
if (FMT_PEDANTIC)
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
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
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
@ -262,13 +322,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
endif ()
if (BUILD_SHARED_LIBS)
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
NOT EMSCRIPTEN)
# Fix rpmlint warning:
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
target_link_libraries(fmt -Wl,--as-needed)
endif ()
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED)
endif ()
if (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)
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
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
@ -300,7 +354,7 @@ if (FMT_INSTALL)
"Installation directory for libraries, a relative path that "
"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 "
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
"absolute path.")
@ -330,7 +384,6 @@ if (FMT_INSTALL)
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
FRAMEWORK DESTINATION "."
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# 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}
NAMESPACE fmt::)
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
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
------------------
@ -19,7 +812,7 @@
return result;
}
constexpr auto answer = compile_time_itoa(0.42);
constexpr auto answer = compile_time_dtoa(0.42);
works with the default settings.
@ -2134,7 +2927,7 @@
<https://github.com/kwesolowski>`_.
* 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>`_).
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
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
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v?svg=true
:target: https://ci.appveyor.com/project/vitaut/fmt
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
:alt: fmt is continuously fuzzed at oss-fuzz
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
@ -25,6 +22,9 @@
:alt: Ask questions at StackOverflow with the tag 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
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 <https://docs.python.org/3/library/stdtypes.html#str.format>`_
* 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
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
extension for positional arguments
@ -67,7 +69,7 @@ Features
<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
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
errors
* 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
* Clean warning-free codebase even on high warning levels such as
``-Wall -Wextra -pedantic``
* Locale-independence by default
* Locale independence by default
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
See the `documentation <https://fmt.dev>`_ for more details.
@ -194,24 +196,24 @@ Speed tests
================= ============= ===========
Library Method Run Time, s
================= ============= ===========
libc printf 1.04
libc++ std::ostream 3.05
{fmt} 6.1.1 fmt::print 0.75
Boost Format 1.67 boost::format 7.24
Folly Format folly::format 2.23
libc printf 0.91
libc++ std::ostream 2.49
{fmt} 9.1 fmt::print 0.74
Boost Format 1.80 boost::format 6.26
Folly Format folly::format 1.87
================= ============= ===========
{fmt} is the fastest of the benchmarked methods, ~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
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"``
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source
<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
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
`ryu <https://github.com/ulfjack/ryu>`_:
@ -227,7 +229,7 @@ The script `bloat-test.py
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
tests compile time and code bloat for nontrivial projects.
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),
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
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.
**Non-optimized build**
@ -264,14 +266,14 @@ Boost Format 54.1 365 303
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
header-only library so it doesn't provide any linkage options.
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.
__ https://fmt.dev/latest/usage.html#building-the-library
@ -296,9 +298,12 @@ or the bloat test::
Migrating code
--------------
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
``fmt::print``.
`clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`_ v17 (not yet
released) provides the `modernize-use-std-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
---------------------------
@ -306,8 +311,6 @@ Projects using this library
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
real-time strategy game
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
* `AMPL/MP <https://github.com/ampl/mp>`_:
an open-source library for mathematical programming
@ -325,9 +328,11 @@ Projects using this library
* `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
* `Contour <https://github.com/contour-terminal/contour/>`_: a modern terminal emulator
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle
@ -363,6 +368,10 @@ Projects using this library
* `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>`_:
research programming language for concurrent ownership
@ -392,7 +401,7 @@ Projects using this library
proxy
* `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
library
@ -416,6 +425,9 @@ Projects using this library
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
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
terminal
@ -473,7 +485,7 @@ error handling is awkward.
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
various benchmarks, it is much slower than other methods considered here. Boost
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
~~~~~~~~~~
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:
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
~~~~~~~~~~~~~~~~~~
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
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,
@ -517,7 +529,7 @@ Documentation License
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
section in the documentation is based on the one from Python `string module
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
For this reason the documentation is distributed under the Python Software
For this reason, the documentation is distributed under the Python Software
Foundation license available in `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}.
@ -526,8 +538,7 @@ Maintainers
-----------
The {fmt} library is maintained by Victor Zverovich (`vitaut
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
<https://github.com/foonathan>`_) with contributions from many other people.
<https://github.com/vitaut>`_) with contributions from many other people.
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
`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

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)
message(STATUS "Target 'doc' disabled (requires doxygen)")
return ()
endif ()
find_package(PythonInterp QUIET REQUIRED)
# Find the Python interpreter and set the PYTHON_EXECUTABLE variable.
if (CMAKE_VERSION VERSION_LESS 3.12)
# This logic is deprecated in CMake after 3.12.
find_package(PythonInterp QUIET REQUIRED)
else ()
find_package(Python QUIET REQUIRED)
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
endif ()
add_custom_target(doc
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py

View file

@ -7,7 +7,7 @@ API Reference
The {fmt} library API consists of the following parts:
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
for ``char``/UTF-8 with compile-time checks and minimal dependencies
for ``char``/UTF-8 with C++20 compile-time checks and minimal dependencies
* :ref:`fmt/format.h <format-api>`: the full format API providing additional
formatting functions and locale support
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
@ -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/os.h <os-api>`: system APIs
* :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/xchar.h <xchar-api>`: optional ``wchar_t`` support
@ -28,10 +29,11 @@ macros have prefix ``FMT_``.
Core API
========
``fmt/core.h`` defines the core API which provides main formatting functions for
``char``/UTF-8 with compile-time checks. It has minimal include dependencies for
better compile times. This header is only beneficial when using {fmt} as a
library and not in the header-only mode.
``fmt/core.h`` defines the core API which provides main formatting functions
for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include
dependencies for better compile times. This header is only beneficial when
using {fmt} as a library (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>`
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 checks are enabled when using ``FMT_STRING``. They support built-in
and string types as well as user-defined types with ``constexpr`` ``parse``
functions in their ``formatter`` specializations.
Requires C++14 and is a no-op in C++11.
Compile-time format string checks are enabled by default on compilers
that support C++20 ``consteval``. On older compilers you can use the
:ref:`FMT_STRING <legacy-checks>`: macro defined in ``fmt/format.h`` instead.
.. doxygendefine:: FMT_STRING
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.
Unused arguments are allowed as in Python's `str.format` and ordinary functions.
.. doxygenclass:: fmt::basic_format_string
:members:
.. doxygentypedef:: fmt::format_string
.. doxygenfunction:: fmt::runtime(const S&)
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.
.. doxygenfunction:: fmt::runtime(string_view) -> runtime_format_string<>
.. _udt:
@ -186,14 +93,39 @@ Formatting User-Defined Types
The {fmt} library provides formatters for many standard C++ types.
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
and time formatting and :ref:`fmt/std.h <std-api>` for path and variant
formatting.
and time formatting and :ref:`fmt/std.h <std-api>` for other standard library
types.
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
template and implement ``parse`` and ``format`` methods::
There are two ways to make a user-defined type formattable: providing a
``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>
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 {
double x, y;
};
@ -203,7 +135,7 @@ template and implement ``parse`` and ``format`` methods::
char presentation = 'f';
// 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
// the format string starting from the format specifications to be parsed,
// e.g. in
@ -224,7 +156,7 @@ template and implement ``parse`` and ``format`` methods::
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
// 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 it;
@ -232,8 +164,7 @@ template and implement ``parse`` and ``format`` methods::
// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
auto format(const point& p, format_context& ctx) const -> format_context::iterator {
// ctx.out() is an output iterator to write to.
return presentation == 'f'
? 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
example::
// color.h:
#include <fmt/core.h>
enum class color {red, green, blue};
template <> struct fmt::formatter<color>: 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";
switch (c) {
case color::red: name = "red"; break;
@ -264,8 +205,9 @@ example::
}
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
all string format specifications, for example
@ -277,8 +219,9 @@ will return ``" blue"``.
You can also write a formatter for a hierarchy of classes::
// demo.h:
#include <type_traits>
#include <fmt/format.h>
#include <fmt/core.h>
struct A {
virtual ~A() {}
@ -292,42 +235,107 @@ You can also write a formatter for a hierarchy of classes::
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
fmt::formatter<std::string> {
template <typename FormatCtx>
auto format(const A& a, FormatCtx& ctx) const {
auto format(const A& a, format_context& ctx) const {
return fmt::formatter<std::string>::format(a.name(), ctx);
}
};
// demo.cc:
#include "demo.h"
#include <fmt/format.h>
int main() {
B b;
A& a = b;
fmt::print("{}", a); // prints "B"
}
If a type provides both a ``formatter`` specialization and an implicit
conversion to a formattable type, the specialization takes precedence over the
conversion.
Providing both a ``formatter`` specialization and a ``format_as`` overload is
disallowed.
For enums {fmt} also provides the ``format_as`` extension API. To format an enum
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.
Named Arguments
---------------
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 {
enum class film {
house_of_cards, american_beauty, se7en = 7
};
auto format_as(film f) { return fmt::underlying(f); }
Argument Lists
--------------
You can create your own formatting function with compile-time checks and small
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() {
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
template <typename... T>
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
-----------------
@ -339,7 +347,7 @@ Utilities
---------
.. 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::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
``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:
Range and Tuple Formatting
@ -474,10 +517,11 @@ Standard Library Types Formatting
``fmt/std.h`` provides formatters for:
* `std::filesystem::path <std::filesystem::path>`_
* `std::filesystem::path <https://en.cppreference.com/w/cpp/filesystem/path>`_
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
* `std::optional <https://en.cppreference.com/w/cpp/utility/optional>`_
Formatting Variants
-------------------
@ -505,10 +549,20 @@ Format String Compilation
``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings
marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into
efficient formatting code at compile-time. This supports arguments of built-in
and string types as well as user-defined types with ``constexpr`` ``parse``
functions in their ``formatter`` specializations. 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.
and string types as well as user-defined types with ``format`` functions taking
the format context type as a template parameter in their ``formatter``
specializations. For example::
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
@ -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
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

View file

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

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
meaning in this case.
The *sign* option is only valid for number types, and can be one of the
following:
The *sign* option is only valid for floating point and signed integer types,
and can be one of the following:
+---------+------------------------------------------------------------+
| Option | Meaning |
@ -304,8 +304,8 @@ The available presentation types for pointers are:
Chrono Format Specifications
============================
Format specifications for chrono types and ``std::tm`` have the following
syntax:
Format specifications for chrono duration and time point types as well as
``std::tm`` have the following syntax:
.. productionlist:: sf
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
``std::chrono::duration`` types with a floating-point representation type.
The available presentation types (*chrono_type*) for chrono durations and time
points are:
The available presentation types (*chrono_type*) are:
+---------+--------------------------------------------------------------------+
| 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 |
| | single digit, it is prefixed with 0. The modified command ``%OH`` |
| | 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, |
| | it is prefixed with 0. The modified command ``%OM`` produces the |
| | 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 |
| | 10, the result is prefixed with 0. If the precision of the input |
| | 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 |
| | 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)
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:
@ -356,7 +482,7 @@ Range Format Specifications
Format specifications for range types have the following syntax:
..productionlist:: sf
.. productionlist:: sf
range_format_spec: [":" [`underlying_spec`]]
The `underlying_spec` is parsed based on the formatter of the range's
@ -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
or strings are printed according to the provided specification.
Examples:
Examples::
fmt::format("{}", std::vector{10, 20, 30});
// Result: [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'});
// Result: ['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
// All rights reserved.

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
@ -203,7 +203,7 @@ struct rgb {
uint8_t b;
};
FMT_BEGIN_DETAIL_NAMESPACE
namespace detail {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
@ -225,8 +225,7 @@ struct color_type {
uint32_t rgb_color;
} value;
};
FMT_END_DETAIL_NAMESPACE
} // namespace detail
/** A text style consisting of foreground and background colors and emphasis. */
class text_style {
@ -323,7 +322,7 @@ FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept {
return text_style(lhs) | rhs;
}
FMT_BEGIN_DETAIL_NAMESPACE
namespace detail {
template <typename Char> struct ansi_color_escape {
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);
}
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) {
auto reset_color = string_view("\x1b[0m");
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);
}
FMT_END_DETAIL_NAMESPACE
} // namespace detail
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, detail::to_string_view(format), args);
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
format_args args) {
// Legacy wide streams are not supported.
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
if (detail::is_utf8()) {
detail::print(f, basic_string_view<Char>(buf.begin(), buf.size()));
} else {
buf.push_back(Char(0));
detail::fputs(buf.data(), f);
detail::print(f, string_view(buf.begin(), buf.size()));
return;
}
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) {
auto&& buf = detail::get_buffer<Char>(out);
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**::
fmt::print("Elapsed time: {s:.2f} seconds",
fmt::print("Elapsed time: {0:.2f} seconds",
fmt::styled(1.23, fmt::fg(fmt::color::green) |
fmt::bg(fmt::color::blue)));
\endrst
@ -645,7 +626,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts)
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_

View file

@ -14,89 +14,11 @@ FMT_BEGIN_NAMESPACE
namespace detail {
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) {
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.
class compiled_string {};
@ -196,7 +118,8 @@ template <typename Char> struct code_unit {
template <typename OutputIt, typename... Args>
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>
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;
};
constexpr int manual_indexing_id = -1;
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {},
next_arg_id);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
}
template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id;
constexpr int operator()() {
constexpr int on_auto() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int operator()(int id) {
constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id);
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);
return 0;
}
constexpr void on_error(const char* message) {
FMT_THROW(format_error(message));
}
};
template <typename Char> struct parse_arg_id_result {
@ -397,14 +321,21 @@ constexpr auto parse_replacement_field_then_tail(S format_str) {
return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
format_str);
} else if constexpr (c == ':') {
} else if constexpr (c != ':') {
FMT_THROW(format_error("expected ':'"));
} else {
constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
return parse_tail<Args, result.end, result.next_arg_id>(
if constexpr (result.end >= str.size() || str[result.end] != '}') {
FMT_THROW(format_error("expected '}'"));
return 0;
} else {
return parse_tail<Args, result.end + 1, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
format_str);
}
}
}
// Compiles a non-empty format string and returns the compiled representation
@ -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) {
constexpr auto arg_index =
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 =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(format_str);
} else {
if constexpr (c == '}') {
} else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
format_str);
@ -461,7 +391,6 @@ constexpr auto compile_format_string(S format_str) {
}
}
}
}
} else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size())
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)
} // namespace detail
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
#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)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const S& format_str, Args&&... args) {
auto it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n),
format_str, std::forward<Args>(args)...);
return {it.base(), it.count()};
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
format_to(std::back_inserter(buf), format_str, std::forward<Args>(args)...);
return {buf.out(), buf.count()};
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
size_t formatted_size(const S& format_str, const Args&... args) {
FMT_CONSTEXPR20 size_t formatted_size(const S& format_str,
const Args&... args) {
return fmt::format_to(detail::counting_iterator(), format_str, args...)
.count();
}
@ -597,7 +528,7 @@ template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
} // namespace literals
#endif
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#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_
#include <algorithm>
#include <cctype>
#include <cerrno> // errno
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstring> // std::memmove
#include <cwchar>
#include <exception>
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
@ -115,16 +111,43 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.';
}
#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
#if !FMT_MSC_VERSION
FMT_API FMT_FUNC format_error::~format_error() noexcept = default;
template <typename Locale> typename Locale::id format_facet<Locale>::id;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& 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
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) {
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 {
@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
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.
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
// 64-bit unsigned integer.
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;
}
// 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.
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 {
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
return (e * 631305 - 261663) >> 21;
}
static constexpr struct {
FMT_INLINE_VARIABLE constexpr struct {
uint32_t divisor;
int shift_amount;
} 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
template <class T> struct cache_accessor;
template <typename T> struct cache_accessor;
template <> struct cache_accessor<float> {
using carrier_uint = float_info<float>::carrier_uint;
@ -1009,8 +970,23 @@ template <> struct cache_accessor<double> {
{0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},
{0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},
{0xc5a05277621be293, 0xc7098b7305241886},
{ 0xf70867153aa2db38,
0xb8cbee4fc66d1ea8 }
{0xf70867153aa2db38, 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
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
@ -1034,8 +1010,8 @@ template <> struct cache_accessor<double> {
{0x8da471a9de737e24, 0x5ceaecfed289e5d3},
{0xe4d5e82392a40515, 0x0fabaf3feaa5334b},
{0xb8da1662e7b00a17, 0x3d6a751f3b936244},
{ 0x95527a5202df0ccb,
0x0f37801e0c43ebc9 }
{0x95527a5202df0ccb, 0x0f37801e0c43ebc9},
{0xf13e34aabb430a15, 0x647726b9e7c68ff0}
#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
template <class T>
template <typename T>
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_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)
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, "");
const uint32_t mod_inv_5 = 0xcccccccd;
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
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) {
auto q = rotr(n * mod_inv_25, 2);
if (q > max_value<uint32_t>() / 100) break;
@ -1165,7 +1145,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
n = q;
s |= 1;
}
return s;
}
@ -1179,32 +1158,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
// Is n is divisible by 10^8?
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));
const uint32_t mod_inv_5 = 0xcccccccd;
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;
}
// ... and use the 32 bit variant of the function
int s = remove_trailing_zeros(n32, 8);
n = n32;
return s;
}
// If n is not divisible by 10^8, work with n itself.
const uint64_t mod_inv_5 = 0xcccccccccccccccd;
const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5;
constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd;
constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // = mod_inv_5 * mod_inv_5
int s = 0;
while (true) {
@ -1223,7 +1187,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
}
// The main algorithm for shorter interval case
template <class T>
template <typename T>
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
decimal_fp<T> ret_value;
// Compute k and beta
@ -1337,7 +1301,7 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
if (r < deltai) {
// Exclude the right endpoint if necessary.
if (r == 0 && z_mul.is_integer && !include_right_endpoint) {
if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) {
--ret_value.significand;
r = float_info<T>::big_divisor;
goto small_divisor_case_label;
@ -1346,27 +1310,12 @@ template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
goto small_divisor_case_label;
} else {
// r == deltai; compare fractional parts.
const carrier_uint two_fl = two_fc - 1;
if (!include_left_endpoint ||
exponent < float_info<T>::case_fc_pm_half_lower_threshold ||
exponent > float_info<T>::divisibility_check_by_5_threshold) {
// If the left endpoint is not included, the condition for
// success is z^(f) < delta^(f) (odd parity).
// Otherwise, the inequalities on exponent ensure that
// x is not an integer, so if z^(f) >= delta^(f) (even parity), we in fact
// have strict inequality.
if (!cache_accessor<T>::compute_mul_parity(two_fl, cache, beta).parity) {
goto small_divisor_case_label;
}
} else {
const typename cache_accessor<T>::compute_mul_parity_result x_mul =
cache_accessor<T>::compute_mul_parity(two_fl, cache, beta);
if (!x_mul.parity && !x_mul.is_integer) {
cache_accessor<T>::compute_mul_parity(two_fc - 1, cache, beta);
if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint)))
goto small_divisor_case_label;
}
}
}
ret_value.exponent = minus_k + float_info<T>::kappa + 1;
// We may need to remove trailing zeros.
@ -1404,22 +1353,11 @@ small_divisor_case_label:
// or equivalently, when y is an integer.
if (y_mul.parity != approx_y_parity)
--ret_value.significand;
else if (y_mul.is_integer && ret_value.significand % 2 != 0)
else if (y_mul.is_integer & (ret_value.significand % 2 != 0))
--ret_value.significand;
return ret_value;
}
} // 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
template <> struct formatter<detail::bigint> {
@ -1428,9 +1366,8 @@ template <> struct formatter<detail::bigint> {
return ctx.begin();
}
template <typename FormatContext>
auto format(const detail::bigint& n, FormatContext& ctx) const ->
typename FormatContext::iterator {
auto format(const detail::bigint& n, format_context& ctx) const
-> format_context::iterator {
auto out = ctx.out();
bool first = true;
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);
}
#ifdef _WIN32
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>;
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
void*, const void*, dword, dword*, void*);
} // namespace detail
#endif
namespace detail {
FMT_FUNC void print(std::FILE* f, string_view text) {
#ifdef _WIN32
FMT_FUNC bool write_console(std::FILE* f, string_view text) {
auto fd = _fileno(f);
if (_isatty(fd)) {
detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
auto written = detail::dword();
if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
u16.c_str(), static_cast<uint32_t>(u16.size()),
&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()});
if (!_isatty(fd)) return false;
auto u16 = utf8_to_utf16(text);
auto written = dword();
return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),
static_cast<uint32_t>(u16.size()), &written, nullptr) != 0;
}
#ifdef _WIN32
// Print assuming legacy (non-Unicode) encoding.
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
format_args args) {
memory_buffer buffer;
detail::vformat_to(buffer, format_str,
FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt,
basic_format_args<buffer_context<char>>(args));
fwrite_fully(buffer.data(), 1, buffer.size(), f);
}
#endif
FMT_FUNC void vprint(string_view format_str, format_args args) {
vprint(stdout, format_str, args);
FMT_FUNC void print(std::FILE* f, string_view text) {
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 {

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)
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
/**
\rst
@ -120,51 +120,13 @@ template <typename Char> class basic_cstring_view {
using cstring_view = basic_cstring_view<char>;
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
FMT_API const std::error_category& system_category() noexcept;
FMT_BEGIN_DETAIL_NAMESPACE
// 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);
};
namespace detail {
FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
FMT_END_DETAIL_NAMESPACE
}
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
format_args args);
@ -355,12 +317,18 @@ class FMT_API file {
// Creates a buffered_file object associated with this file and detaches
// this file object from the file.
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.
long getpagesize();
FMT_BEGIN_DETAIL_NAMESPACE
namespace detail {
struct buffer_size {
buffer_size() = default;
@ -397,56 +365,61 @@ struct ostream_params {
# 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
// occur in Xcode versions 7.2.1 and 8.2.1.
constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> {
class FMT_API ostream {
private:
file file_;
void grow(size_t) override;
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
: buffer_(path, params) {}
public:
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() {
flush();
file_.close();
}
void close() { buffer_.close(); }
/**
Formats ``args`` according to specifications in ``fmt`` and writes the
output to the file.
*/
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(detail::buffer_appender<char>(*this), fmt,
vformat_to(detail::buffer_appender<char>(buffer_), fmt,
fmt::make_format_args(args...));
}
};
@ -472,7 +445,7 @@ inline ostream output_file(cstring_view path, T... params) {
}
#endif // FMT_USE_FCNTL
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_OS_H_

View file

@ -8,86 +8,65 @@
#ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_
#include <fstream>
#include <ostream>
#include <fstream> // std::filebuf
#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"
FMT_BEGIN_NAMESPACE
template <typename OutputIt, typename Char> class basic_printf_context;
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
// type in an anonymous namespace.
namespace {
struct filebuf_access_tag {};
struct file_access_tag {};
} // namespace
template <typename Tag, typename FileMemberPtr, FileMemberPtr file>
class filebuf_access {
friend FILE* get_file(filebuf_type& buf) { return buf.*file; }
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
template class filebuf_access<filebuf_access_tag,
decltype(&filebuf_type::_Myfile),
&filebuf_type::_Myfile>;
inline bool write(std::filebuf& buf, fmt::string_view data) {
FILE* f = get_file(buf);
if (!f) return false;
print(f, data);
return true;
#if FMT_MSC_VERSION
template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
template class file_access<file_access_tag, std::__stdoutbuf<char>,
&std::__stdoutbuf<char>::__file_>;
auto get_file(std::__stdoutbuf<char>&) -> FILE*;
#endif
inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
#if FMT_MSC_VERSION
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
if (FILE* f = get_file(*buf)) return write_console(f, data);
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
FILE* c_file;
if (auto* 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;
}
@ -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.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
if (const_check(FMT_MSC_VERSION)) {
auto filebuf = dynamic_cast<std::basic_filebuf<Char>*>(os.rdbuf());
if (filebuf && write(*filebuf, {buf.data(), buf.size()})) return;
}
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
unsigned_streamsize size = buf.size();
@ -130,11 +105,13 @@ template <typename T> struct streamed_view { const T& value; };
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename Char>
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
void set_debug_format() = delete;
template <typename T, typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt {
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(
{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>;
template <typename T>
struct formatter<detail::streamed_view<T>> : ostream_formatter {
template <typename T, typename Char>
struct formatter<detail::streamed_view<T>, Char>
: basic_ostream_formatter<Char> {
template <typename OutputIt>
auto format(detail::streamed_view<T> view,
basic_format_context<OutputIt, char>& ctx) const -> OutputIt {
return ostream_formatter::format(view.value, ctx);
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
return basic_ostream_formatter<Char>::format(view.value, ctx);
}
};
@ -168,21 +146,22 @@ auto streamed(const T& value) -> detail::streamed_view<T> {
namespace detail {
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: basic_ostream_formatter<Char> {
using basic_ostream_formatter<Char>::format;
};
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
} // namespace detail
FMT_MODULE_EXPORT template <typename Char>
FMT_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args);
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
detail::write_buffer(os, buffer);
}
@ -195,12 +174,16 @@ void vprint(std::basic_ostream<Char>& os,
fmt::print(cerr, "Don't {}!", "panic");
\endrst
*/
FMT_MODULE_EXPORT template <typename... T>
FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
vprint(os, fmt, fmt::make_format_args(args...));
const auto& vargs = fmt::make_format_args(args...);
if (detail::is_utf8())
vprint(os, fmt, vargs);
else
detail::vprint_directly(os, fmt, vargs);
}
FMT_MODULE_EXPORT
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
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...));
}
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
#endif // FMT_OSTREAM_H_

View file

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

View file

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

View file

@ -8,10 +8,18 @@
#ifndef FMT_STD_H_
#define FMT_STD_H_
#include <atomic>
#include <bitset>
#include <cstdlib>
#include <exception>
#include <memory>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>
#include "format.h"
#include "ostream.h"
#if FMT_HAS_INCLUDE(<version>)
@ -25,6 +33,30 @@
# if FMT_HAS_INCLUDE(<variant>)
# include <variant>
# 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
#ifdef __cpp_lib_filesystem
@ -32,21 +64,32 @@ FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char> auto get_path_string(const std::filesystem::path& p) {
return p.string<Char>();
}
template <typename Char>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p) {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
# ifdef _WIN32
template <>
inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted,
const std::filesystem::path& p) {
auto s = p.u8string();
write_escaped_string<char>(
std::back_inserter(quoted),
string_view(reinterpret_cast<const char*>(s.c_str()), s.size()));
inline auto get_path_string<char>(const std::filesystem::path& p) {
return to_utf8<wchar_t>(p.native(), to_utf8_error_policy::replace);
}
# 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 <>
inline void write_escaped_path<std::filesystem::path::value_type>(
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
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920
// For MSVC 2017 and earlier using the partial specialization
// would cause an ambiguity error, therefore we provide it only
// conditionally.
template <typename Char>
struct formatter<std::filesystem::path, Char>
: formatter<basic_string_view<Char>> {
FMT_EXPORT
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs<Char> specs_;
detail::arg_ref<Char> width_ref_;
bool debug_ = false;
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>
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
typename FormatContext::iterator {
basic_memory_buffer<Char> quoted;
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
auto specs = specs_;
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);
return formatter<basic_string_view<Char>>::format(
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
return detail::write(ctx.out(),
basic_string_view<Char>(quoted.data(), quoted.size()),
specs);
}
};
#endif
FMT_END_NAMESPACE
#endif
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_variant
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
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>
auto format(const std::monostate&, FormatContext& ctx) const
auto format(std::optional<T> const& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out();
out = detail::write<Char>(out, "monostate");
return out;
out = detail::write<Char>(out, optional);
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 {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
// variant_size and variant_alternative check.
template <typename T, typename U = void>
struct is_variant_like_ : std::false_type {};
template <typename T>
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
: std::true_type {};
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... I>
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<I, T>, C>...>
check(std::index_sequence<I...>);
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
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;
};
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>
struct formatter<
Variant, Char,
@ -161,16 +270,196 @@ struct formatter<
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
FMT_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
}
FMT_CATCH(const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
return out;
}
};
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
} // 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_

View file

@ -9,17 +9,35 @@
#define FMT_XCHAR_H_
#include <cwchar>
#include <tuple>
#include "format.h"
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
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 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
// Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
template <> struct is_char<wchar_t> : std::true_type {};
@ -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<char32_t> : std::true_type {};
template <typename... Args>
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
const Args&... args) {
template <typename... T>
constexpr format_arg_store<wformat_context, T...> make_wformat_args(
const T&... 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,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
basic_memory_buffer<Char> buffer;
detail::vformat_to(buffer, format_str, args);
return to_string(buffer);
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, format_str, args);
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>
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
return vformat(fmt, fmt::make_wformat_args(args...));
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
}
// Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... Args, typename Char = char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
template <typename S, typename... T, typename Char = char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!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),
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);
}
template <typename Locale, typename S, typename... Args,
typename Char = char_t<S>,
template <typename Locale, typename S, typename... T, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::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> {
return detail::vformat(loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
@ -129,14 +146,14 @@ auto vformat_to(OutputIt out, const S& format_str,
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
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>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, 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),
fmt::make_format_args<buffer_context<Char>>(args...));
}
@ -152,18 +169,18 @@ inline auto vformat_to(
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf);
return detail::get_iterator(buf, out);
}
template <
typename OutputIt, typename Locale, typename S, typename... Args,
typename OutputIt, typename Locale, typename S, typename... T,
typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
Args&&... args) ->
T&&... args) ->
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...));
}
@ -174,36 +191,36 @@ inline auto vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> format_to_n_result<OutputIt> {
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
n);
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
template <typename OutputIt, typename S, typename... Args,
template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
const Args&... args) -> format_to_n_result<OutputIt> {
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> {
return vformat_to_n(out, n, detail::to_string_view(fmt),
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)>
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
detail::counting_buffer<Char> buf;
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
auto buf = detail::counting_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt),
fmt::make_format_args<buffer_context<Char>>(args...));
return buf.count();
}
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
wmemory_buffer buffer;
detail::vformat_to(buffer, fmt, args);
buffer.push_back(L'\0');
if (std::fputws(buffer.data(), f) == -1)
auto buf = wmemory_buffer();
detail::vformat_to(buf, fmt, args);
buf.push_back(L'\0');
if (std::fputws(buf.data(), f) == -1)
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...));
}
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*.
*/
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value);
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_XCHAR_H_

View file

@ -1,49 +1,44 @@
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 <cctype>
#include <cerrno>
#include <chrono>
#include <climits>
#include <clocale>
#include <cmath>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cwchar>
#include <exception>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iterator>
#include <limits>
#include <locale>
#include <memory>
#include <optional>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <variant>
#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>
#endif
#if defined __APPLE__ || defined(__FreeBSD__)
@ -65,22 +60,33 @@ module;
# endif
#endif
#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>
#endif
export module fmt;
#define FMT_MODULE_EXPORT export
#define FMT_MODULE_EXPORT_BEGIN export {
#define FMT_MODULE_EXPORT_END }
#define FMT_BEGIN_DETAIL_NAMESPACE \
} \
namespace detail {
#define FMT_END_DETAIL_NAMESPACE \
} \
export {
// all library-provided declarations and definitions
// must be in the module purview to be exported
#define FMT_EXPORT export
#define FMT_BEGIN_EXPORT export {
#define FMT_END_EXPORT }
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
// - all declarations are detached from module 'fmt'
// - the module behaves like a traditional static library, too
// - all library symbols are mangled traditionally
// - you can mix TUs with either importing or #including the {fmt} API
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
extern "C++" {
#endif
// All library-provided declarations and definitions must be in the module
// purview to be exported.
#include "fmt/args.h"
#include "fmt/chrono.h"
#include "fmt/color.h"
@ -88,8 +94,13 @@ export module fmt;
#include "fmt/format.h"
#include "fmt/os.h"
#include "fmt/printf.h"
#include "fmt/std.h"
#include "fmt/xchar.h"
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif
// gcc doesn't yet implement private module fragments
#if !FMT_GCC_VERSION
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*);
// 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,
basic_format_args<FMT_BUFFER_CONTEXT(char)>,
locale_ref);
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t.

View file

@ -18,6 +18,10 @@
# include <sys/stat.h>
# include <sys/types.h>
# ifdef _WRS_KERNEL // VxWorks7 kernel
# include <ioLib.h> // getpagesize
# endif
# ifndef _WIN32
# include <unistd.h>
# else
@ -72,34 +76,6 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
FMT_BEGIN_NAMESPACE
#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 {
class system_message {
@ -138,10 +114,10 @@ class utf8_system_category final : public std::error_category {
public:
const char* name() const noexcept override { return "system"; }
std::string message(int error_code) const override {
system_message msg(error_code);
auto&& msg = system_message(error_code);
if (msg) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg)) {
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,
const char* message) noexcept {
FMT_TRY {
system_message msg(error_code);
auto&& msg = system_message(error_code);
if (msg) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
fmt::format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg)) {
fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
string_view(utf8_message));
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())),
nullptr);
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() {
if (!file_) return;
int result = FMT_SYSTEM(fclose(file_));
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 {
#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_));
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;
}
#if FMT_USE_FCNTL
file::file(cstring_view path, int oflag) {
# ifdef _WIN32
using mode_t = int;
using mode_t = int;
# endif
constexpr mode_t mode =
constexpr mode_t default_open_mode =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
file::file(cstring_view path, int oflag) {
# if defined(_WIN32) && !defined(__MINGW32__)
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
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
# endif
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
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 {
@ -238,7 +225,8 @@ void file::close() {
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
int result = FMT_POSIX_CALL(close(fd_));
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 {
@ -260,7 +248,7 @@ long long file::size() const {
using Stat = struct stat;
Stat file_stat = Stat();
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),
"return type of file::size is not large enough");
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) {
rwresult result = 0;
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);
}
std::size_t file::write(const void* buffer, std::size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) 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);
}
@ -286,7 +276,8 @@ file file::dup(int fd) {
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd));
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);
}
@ -294,8 +285,9 @@ void file::dup2(int fd) {
int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) {
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
fd_, fd));
FMT_THROW(system_error(
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
int result = FMT_POSIX_CALL(pipe(fds));
# 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
// are closed.
read_end = file(fds[0]);
@ -334,28 +327,72 @@ buffered_file file::fdopen(const char* mode) {
# else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));
if (!f) {
FMT_THROW(system_error(
errno, FMT_STRING("cannot associate stream with file descriptor")));
}
buffered_file bf(f);
fd_ = -1;
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() {
# ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwPageSize;
# else
# ifdef _WRS_KERNEL
long size = FMT_POSIX_CALL(getpagesize());
# else
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;
# endif
}
# endif
FMT_API void ostream::grow(size_t) {
namespace detail {
void file_buffer::grow(size_t) {
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
FMT_END_NAMESPACE

View file

@ -13,8 +13,8 @@ Vagrant.configure("2") do |config|
config.vm.provision "shell", inline: <<-SHELL
apt-get update
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
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin
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.26.0-Linux-x86_64.tar.gz
ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin
SHELL
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/compile.h",
"include/fmt/core.h",
"include/fmt/format-inl.h",
"include/fmt/format.h",
"include/fmt/format-inl.h",
"include/fmt/os.h",
"include/fmt/ostream.h",
"include/fmt/printf.h",

View file

@ -1,6 +1,6 @@
# 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
@ -37,7 +37,6 @@ git_repository(
branch = "master",
remote = "https://github.com/fmtlib/fmt",
patch_cmds = [
"mv support/bazel/.bazelrc .bazelrc",
"mv support/bazel/.bazelversion .bazelversion",
"mv support/bazel/BUILD.bazel BUILD.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
# Even if MSYS2 is installed the Windows related patch commands can still be used.
patch_cmds_win = [
"Move-Item -Path support/bazel/.bazelrc -Destination .bazelrc",
"Move-Item -Path support/bazel/.bazelversion -Destination .bazelversion",
"Move-Item -Path support/bazel/BUILD.bazel -Destination BUILD.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*:
@ -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`.
# 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
//
// 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"
}

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:
b.data = b.data.replace(
'doc/latest/index.html#format-string-syntax', 'syntax.html')
# Fix issues in syntax.rst.
index = os.path.join(target_doc_dir, 'syntax.rst')
with rewrite(index) as b:
b.data = b.data.replace(
'..productionlist:: sf\n', '.. productionlist:: sf\n ')
b.data = b.data.replace('Examples:\n', 'Examples::\n')
# Build the docs.
html_dir = os.path.join(env.build_dir, 'html')
if os.path.exists(html_dir):

View file

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

View file

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

View file

@ -44,7 +44,7 @@ template <> struct formatter<custom_type> {
template <typename FormatContext>
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

View file

@ -7,6 +7,7 @@
#include "fmt/chrono.h"
#include <algorithm>
#include <ctime>
#include <vector>
@ -14,9 +15,15 @@
#include "util.h" // get_locale
using fmt::runtime;
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 time = std::tm();
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, 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";
for (auto ctm : tm_list) {
// 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;
}
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) {
auto t = std::time(nullptr);
auto tm = *std::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 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);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
}
TEST(chrono_test, time_point) {
auto t1 = std::chrono::system_clock::now();
EXPECT_EQ(strftime_full(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full(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(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
TEST(chrono_test, local_system_clock_time_point) {
# ifdef _WIN32
return; // Not supported on Windows.
# endif
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
using time_point = std::chrono::local_time<std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(86400 + 42));
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector<std::string> spec_list = {
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
@ -261,15 +378,20 @@ TEST(chrono_test, time_point) {
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
spec_list.push_back("%Y-%m-%d %H:%M:%S");
#ifndef _WIN32
# ifndef _WIN32
// Disabled on Windows because these formats are not consistent among
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
#endif
# elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
# endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm);
@ -278,8 +400,26 @@ 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), tm));
}
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
spec_list.cend()) {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime("%z", &tm);
sys_output.insert(sys_output.end() - 2, 1, ':');
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
}
}
#endif // FMT_USE_LOCAL_TIME
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
TEST(chrono_test, format_default) {
@ -324,7 +464,7 @@ TEST(chrono_test, format_default) {
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);
EXPECT_EQ("42s ", fmt::format("{:5}", s));
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));
}
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) {
EXPECT_EQ("%", fmt::format("{:%%}", 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");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
"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,
@ -454,7 +627,7 @@ TEST(chrono_test, format_default_fp) {
TEST(chrono_test, format_precision) {
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");
EXPECT_EQ("1ms", fmt::format("{:.0}", dms(1.234)));
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
@ -557,9 +730,9 @@ TEST(chrono_test, special_durations) {
"03:33");
EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
"03:33:20");
EXPECT_EQ("44.000000000000",
fmt::format("{:%S}", std::chrono::duration<float, std::pico>(
1.54213895E+26)));
EXPECT_EQ("01.234",
fmt::format("{:.3%S}", std::chrono::duration<float, std::pico>(
1.234e12)));
}
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}),
"-13.420148734");
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}.
auto dur = std::chrono::milliseconds{3601234};
auto formatted_dur = fmt::format("{:%T}", dur);
EXPECT_EQ(formatted_dur, "01:00:01.234");
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>;
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);
EXPECT_EQ(formatted_dur, "-00:01:39.123456789");
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
// fixed precision, and print zeros even if there is no fractional part.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
"07.000000");
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<long long, std::ratio<1, 3>>(1)),
EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
"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");
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
// 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.
cmake_minimum_required(VERSION 3.1...3.18)
cmake_minimum_required(VERSION 3.8...3.25)
project(compile-error-test CXX)
set(fmt_headers "
@ -64,7 +64,7 @@ function (run_tests)
")
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)
add_subdirectory(${FMT_DIR} fmt)
${cmake_targets}

View file

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

View file

@ -21,38 +21,6 @@ TEST(iterator_test, counting_iterator) {
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) {
// FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
// not available.
@ -227,10 +195,27 @@ TEST(compile_test, format_to_n) {
EXPECT_STREQ("2a", buffer);
}
TEST(compile_test, formatted_size) {
EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42));
EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0));
# ifdef __cpp_lib_bit_cast
TEST(compile_test, constexpr_formatted_size) {
FMT_CONSTEXPR20 size_t 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) {
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',
// line 8635)
#if ((FMT_CPLUSPLUS >= 202002L) && \
(!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9) && \
(!FMT_MSC_VERSION || FMT_MSC_VERSION < 1930)) || \
(FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002)
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_GT(string_view("foo").compare(string_view("fo")), 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::not_equal_to>();
check_op<std::less>();
@ -78,24 +88,6 @@ TEST(string_view_test, compare) {
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) {
EXPECT_TRUE((fmt::detail::is_output_iterator<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> {
MOCK_METHOD1(do_grow, size_t(size_t capacity));
MOCK_METHOD(size_t, do_grow, (size_t));
void grow(size_t capacity) override {
this->set(this->data(), do_grow(capacity));
@ -281,7 +273,7 @@ struct custom_context {
bool called = false;
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();
}
@ -298,11 +290,11 @@ struct test_struct {};
FMT_BEGIN_NAMESPACE
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();
}
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");
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) {
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));
auto ctx = custom_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()));
}
MOCK_METHOD1_T(visit, test_result(T value));
MOCK_METHOD0_T(unexpected, void());
MOCK_METHOD(test_result, visit, (T));
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();
return test_result();
}
@ -369,10 +361,11 @@ VISIT_TYPE(unsigned long, unsigned long long);
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
EXPECT_CALL(visitor, visit(expected)); \
using iterator = std::back_insert_iterator<buffer<Char>>; \
auto var = value; \
fmt::visit_format_arg( \
visitor, \
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \
value)); \
var)); \
}
#define CHECK_ARG_SIMPLE(value) \
@ -392,13 +385,13 @@ using test_types =
TYPED_TEST_SUITE(numeric_arg_test, test_types);
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);
}
template <typename T,
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);
}
@ -441,8 +434,8 @@ TEST(arg_test, pointer_arg) {
}
struct check_custom {
test_result operator()(
fmt::basic_format_arg<fmt::format_context>::handle h) const {
auto operator()(fmt::basic_format_arg<fmt::format_context>::handle h) const
-> test_result {
struct test_buffer final : fmt::detail::buffer<char> {
char data[10];
test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
@ -461,13 +454,13 @@ TEST(arg_test, custom_arg) {
auto test = test_struct();
using visitor =
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()));
fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test));
}
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(_));
auto arg = fmt::basic_format_arg<fmt::format_context>();
fmt::visit_format_arg(visitor, arg);
@ -475,185 +468,78 @@ TEST(arg_test, visit_invalid_arg) {
#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 {
arg_id_result res = arg_id_result::none;
int index = 0;
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;
index = i;
}
constexpr void operator()(string_view n) {
constexpr void on_name(string_view n) {
res = arg_id_result::name;
name = n;
}
constexpr void on_error(const char*) { res = arg_id_result::error; }
};
template <size_t 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);
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("42:").res == arg_id_result::index, "");
static_assert(parse_arg_id("42:").index == 42, "");
static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
static_assert(parse_arg_id("foo:").name.size() == 3, "");
static_assert(parse_arg_id("!").res == arg_id_result::error, "");
}
struct test_format_specs_handler {
enum result { none, hash, zero, loc, error };
result res = none;
fmt::align_t alignment = fmt::align::none;
fmt::sign_t sign = fmt::sign::none;
char fill = 0;
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;
template <size_t N> constexpr auto parse_test_specs(const char (&s)[N]) {
auto ctx = fmt::detail::compile_parse_context<char>(fmt::string_view(s, N),
43, nullptr);
auto specs = fmt::detail::dynamic_format_specs<>();
fmt::detail::parse_format_specs(s, s + N - 1, specs, ctx,
fmt::detail::type::float_type);
return specs;
}
TEST(core_test, constexpr_parse_format_specs) {
using handler = test_format_specs_handler;
static_assert(parse_test_specs("<").alignment == fmt::align::left, "");
static_assert(parse_test_specs("*^").fill == '*', "");
static_assert(parse_test_specs("<").align == fmt::align::left, "");
static_assert(parse_test_specs("*^").fill[0] == '*', "");
static_assert(parse_test_specs("+").sign == fmt::sign::plus, "");
static_assert(parse_test_specs("-").sign == fmt::sign::minus, "");
static_assert(parse_test_specs(" ").sign == fmt::sign::space, "");
static_assert(parse_test_specs("#").res == handler::hash, "");
static_assert(parse_test_specs("0").res == handler::zero, "");
static_assert(parse_test_specs("L").res == handler::loc, "");
static_assert(parse_test_specs("#").alt, "");
static_assert(parse_test_specs("0").align == fmt::align::numeric, "");
static_assert(parse_test_specs("L").localized, "");
static_assert(parse_test_specs("42").width == 42, "");
static_assert(parse_test_specs("{42}").width_ref.val.index == 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("d").type == fmt::presentation_type::dec, "");
static_assert(parse_test_specs("{<").res == handler::error, "");
}
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, "");
static_assert(
parse_test_specs("f").type == fmt::presentation_type::fixed_lower, "");
}
struct test_format_string_handler {
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 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;
}
@ -668,7 +554,7 @@ template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
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("}"), "");
static_assert(parse_string("{}"), "");
@ -687,19 +573,20 @@ struct disabled_formatter_convertible {
FMT_BEGIN_NAMESPACE
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();
}
auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) {
auto format(enabled_formatter, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out();
}
};
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();
}
auto format(enabled_ptr_formatter*, format_context& ctx)
auto format(enabled_ptr_formatter*, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out();
}
@ -720,7 +607,7 @@ struct nonconst_formattable {};
FMT_BEGIN_NAMESPACE
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();
}
@ -732,7 +619,7 @@ template <> struct formatter<const_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();
}
@ -754,7 +641,7 @@ struct convertible_to_pointer_formattable {
FMT_BEGIN_NAMESPACE
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();
}
@ -768,29 +655,17 @@ FMT_END_NAMESPACE
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) {
#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, "");
#ifdef __cpp_char8_t
static_assert(!fmt::is_formattable<char8_t>::value, "");
#endif
static_assert(!fmt::is_formattable<char16_t>::value, "");
static_assert(!fmt::is_formattable<char32_t>::value, "");
static_assert(!fmt::is_formattable<signed char*>::value, "");
static_assert(!fmt::is_formattable<unsigned char*>::value, "");
static_assert(!fmt::is_formattable<const signed char*>::value, "");
static_assert(!fmt::is_formattable<const unsigned char*>::value, "");
static_assert(!fmt::is_formattable<const wchar_t*>::value, "");
static_assert(!fmt::is_formattable<const wchar_t[3]>::value, "");
static_assert(!fmt::is_formattable<fmt::basic_string_view<wchar_t>>::value,
@ -798,7 +673,8 @@ TEST(core_test, is_formattable) {
static_assert(fmt::is_formattable<enabled_formatter>::value, "");
static_assert(!fmt::is_formattable<enabled_ptr_formatter*>::value, "");
static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, "");
static_assert(!fmt::is_formattable<disabled_formatter_convertible>::value,
"");
static_assert(fmt::is_formattable<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<unformattable_scoped_enum>::value, "");
static_assert(fmt::is_formattable<test::formattable_scoped_enum>::value, "");
static_assert(!fmt::is_formattable<test::convertible_to_enum>::value, "");
static_assert(!fmt::is_formattable<unformattable_scoped_enum>::value, "");
}
TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
TEST(core_test, format_to) {
std::string s;
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", 42);
EXPECT_EQ(s, "42");
}
TEST(core_test, format_as) {
EXPECT_EQ(fmt::format("{}", test::formattable_scoped_enum()), "42");
#ifdef __cpp_lib_byte
TEST(core_test, format_byte) {
EXPECT_EQ(fmt::format("{}", std::byte(42)), "42");
}
#endif
struct convertible_to_int {
operator int() const { return 42; }
};
struct convertible_to_c_string {
struct convertible_to_cstring {
operator const char*() const { return "foo"; }
};
FMT_BEGIN_NAMESPACE
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();
}
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());
}
};
template <> struct formatter<convertible_to_c_string> {
template <> struct formatter<convertible_to_cstring> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(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()) {
return std::copy_n("bar", 3, ctx.out());
}
@ -865,7 +743,7 @@ FMT_END_NAMESPACE
TEST(core_test, formatter_overrides_implicit_conversion) {
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.
@ -874,27 +752,26 @@ TEST(core_test, adl_check) {
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 {
operator fmt::string_view() const { return "foo"; }
};
TEST(core_test, format_implicitly_convertible_to_string_view) {
EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view()));
TEST(core_test, no_implicit_conversion_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.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
struct explicitly_convertible_to_string_view {
@ -923,25 +800,6 @@ TEST(core_test, format_explicitly_convertible_to_std_string_view) {
# 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 {
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) {
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 <vector>
#define I 42 // simulate https://en.cppreference.com/w/c/numeric/complex/I
#include "fmt/chrono.h"
#include "fmt/color.h"
#include "fmt/format.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
#include "fmt/xchar.h"
#undef I
// Exercise the API to verify that everything we expect to can compile.
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)

View file

@ -196,43 +196,6 @@ TEST(fp_test, multiply) {
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) {
using fmt::detail::dragonbox::floor_log10_pow2;
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 -
fmt::detail::num_significand_bits<float>() - 1));
using double_info = fmt::detail::dragonbox::float_info<double>;
EXPECT_EQ(
fmt::detail::const_check(double_info::max_k),
EXPECT_EQ(fmt::detail::const_check(double_info::max_k),
double_info::kappa -
floor_log10_pow2(std::numeric_limits<double>::min_exponent -
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);
floor_log10_pow2(
std::numeric_limits<double>::min_exponent -
2 * fmt::detail::num_significand_bits<double>() - 1));
}
TEST(format_impl_test, format_error_code) {
std::string msg = "error 42", sep = ": ";
{
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");
EXPECT_EQ(to_string(buffer), "test: " + msg);
}
@ -353,6 +268,16 @@ TEST(format_impl_test, count_digits) {
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
TEST(format_impl_test, write_float128) {
auto s = std::string();
@ -372,10 +297,22 @@ struct double_double {
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) {
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 {
template <> struct is_floating_point<double_double> : std::true_type {};
template <> struct numeric_limits<double_double> {
@ -383,14 +320,35 @@ template <> struct numeric_limits<double_double> {
static constexpr bool is_iec559 = true;
static constexpr int digits = 106;
};
template <> struct is_floating_point<slow_float> : std::true_type {};
template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
} // namespace std
FMT_BEGIN_NAMESPACE
namespace detail {
template <> struct is_fast_float<slow_float> : std::false_type {};
namespace dragonbox {
template <> struct float_info<slow_float> {
using carrier_uint = uint32_t;
static const int exponent_bits = 8;
};
} // namespace dragonbox
} // namespace detail
FMT_END_NAMESPACE
TEST(format_impl_test, write_double_double) {
auto s = std::string();
fmt::detail::write<char>(std::back_inserter(s), double_double(42), {});
#ifndef _MSC_VER // MSVC has an issue with specializing is_floating_point.
EXPECT_EQ(s, "42");
#endif
// Specializing is_floating_point is broken in MSVC.
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "42");
}
TEST(format_impl_test, write_dragon_even) {
auto s = std::string();
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
// Specializing is_floating_point is broken in MSVC.
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
}
#ifdef _WIN32
@ -401,3 +359,125 @@ TEST(format_impl_test, write_console_signature) {
(void)p;
}
#endif
// A public domain branchless UTF-8 decoder by Christopher Wellons:
// https://github.com/skeeto/branchless-utf8
constexpr bool unicode_is_surrogate(uint32_t c) {
return c >= 0xD800U && c <= 0xDFFFU;
}
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
if (c >= (1UL << 16)) {
s[0] = static_cast<char>(0xf0 | (c >> 18));
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));
s[2] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
s[3] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
return s + 4;
} else if (c >= (1UL << 11)) {
s[0] = static_cast<char>(0xe0 | (c >> 12));
s[1] = static_cast<char>(0x80 | ((c >> 6) & 0x3f));
s[2] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
return s + 3;
} else if (c >= (1UL << 7)) {
s[0] = static_cast<char>(0xc0 | (c >> 6));
s[1] = static_cast<char>(0x80 | ((c >> 0) & 0x3f));
return s + 2;
} else {
s[0] = static_cast<char>(c);
return s + 1;
}
}
// Make sure it can decode every character
TEST(format_impl_test, utf8_decode_decode_all) {
for (uint32_t i = 0; i < 0x10ffff; i++) {
if (!unicode_is_surrogate(i)) {
int e;
uint32_t c;
char buf[8] = {0};
char* end = utf8_encode(buf, i);
const char* res = fmt::detail::utf8_decode(buf, &c, &e);
EXPECT_EQ(end, res);
EXPECT_EQ(c, i);
EXPECT_EQ(e, 0);
}
}
}
// Reject everything outside of U+0000..U+10FFFF
TEST(format_impl_test, utf8_decode_out_of_range) {
for (uint32_t i = 0x110000; i < 0x1fffff; i++) {
int e;
uint32_t c;
char buf[8] = {0};
utf8_encode(buf, i);
const char* end = fmt::detail::utf8_decode(buf, &c, &e);
EXPECT_NE(e, 0);
EXPECT_EQ(end - buf, 4);
}
}
// Does it reject all surrogate halves?
TEST(format_impl_test, utf8_decode_surrogate_halves) {
for (uint32_t i = 0xd800; i <= 0xdfff; i++) {
int e;
uint32_t c;
char buf[8] = {0};
utf8_encode(buf, i);
fmt::detail::utf8_decode(buf, &c, &e);
EXPECT_NE(e, 0);
}
}
// How about non-canonical encodings?
TEST(format_impl_test, utf8_decode_non_canonical_encodings) {
int e;
uint32_t c;
const char* end;
char buf2[8] = {char(0xc0), char(0xA4)};
end = fmt::detail::utf8_decode(buf2, &c, &e);
EXPECT_NE(e, 0); // non-canonical len 2
EXPECT_EQ(end, buf2 + 2); // non-canonical recover 2
char buf3[8] = {char(0xe0), char(0x80), char(0xA4)};
end = fmt::detail::utf8_decode(buf3, &c, &e);
EXPECT_NE(e, 0); // non-canonical len 3
EXPECT_EQ(end, buf3 + 3); // non-canonical recover 3
char buf4[8] = {char(0xf0), char(0x80), char(0x80), char(0xA4)};
end = fmt::detail::utf8_decode(buf4, &c, &e);
EXPECT_NE(e, 0); // non-canonical encoding len 4
EXPECT_EQ(end, buf4 + 4); // non-canonical recover 4
}
// Let's try some bogus byte sequences
TEST(format_impl_test, utf8_decode_bogus_byte_sequences) {
int e;
uint32_t c;
// Invalid first byte
char buf0[4] = {char(0xff)};
auto len = fmt::detail::utf8_decode(buf0, &c, &e) - buf0;
EXPECT_NE(e, 0); // "bogus [ff] 0x%02x U+%04lx", e, (unsigned long)c);
EXPECT_EQ(len, 1); // "bogus [ff] recovery %d", len);
// Invalid first byte
char buf1[4] = {char(0x80)};
len = fmt::detail::utf8_decode(buf1, &c, &e) - buf1;
EXPECT_NE(e, 0); // "bogus [80] 0x%02x U+%04lx", e, (unsigned long)c);
EXPECT_EQ(len, 1); // "bogus [80] recovery %d", len);
// Looks like a two-byte sequence but second byte is wrong
char buf2[4] = {char(0xc0), char(0x0a)};
len = fmt::detail::utf8_decode(buf2, &c, &e) - buf2;
EXPECT_NE(e, 0); // "bogus [c0 0a] 0x%02x U+%04lx", e, (unsigned long)c
EXPECT_EQ(len, 2); // "bogus [c0 0a] recovery %d", len);
}
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)
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
endif ()
target_compile_features(${name} PRIVATE cxx_generic_lambdas)
target_compile_features(${name} PRIVATE cxx_std_14)
endfunction()
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.
TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
int n = 0;
(void)n;
using std::runtime_error;
EXPECT_THROW_MSG(throw runtime_error(""), 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.
TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
int n = 0;
(void)n;
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(throw 1, EDOM, ""), "");

View file

@ -12,12 +12,7 @@
#include <string>
#ifdef FMT_MODULE_TEST
import fmt;
#else
# include "fmt/os.h"
#endif // FMG_MODULE_TEST
#include "fmt/os.h"
#include "gmock/gmock.h"
#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)
target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
target_include_directories(gtest SYSTEM PUBLIC .)
target_compile_features(gtest PUBLIC cxx_std_11)
find_package(Threads)
if (Threads_FOUND)
@ -17,13 +18,6 @@ else ()
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
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)
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
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.
if (PatternMatchesString(name_str, pattern, pattern_end)) {
return true;
break;
}
// Give up on this pattern. However, if we found a pattern separator (:),
@ -13778,7 +13778,7 @@ UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith(
UntypedActionResultHolderBase* result = nullptr;
auto perform_action = [&] {
auto perform_action = [&, this] {
return untyped_action == nullptr
? this->UntypedPerformDefaultAction(untyped_args, ss.str())
: this->UntypedPerformAction(untyped_action, untyped_args);

View file

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

View file

@ -15,8 +15,6 @@
# define FMT_HIDE_MODULE_BUGS
#endif
#define FMT_MODULE_TEST
#include <bit>
#include <chrono>
#include <exception>
@ -41,7 +39,6 @@
#else
# define FMT_POSIX(call) call
#endif
#define FMT_OS_H_ // don't pull in os.h directly or indirectly
import fmt;
@ -53,13 +50,8 @@ static bool macro_leaked =
false;
#endif
// Include sources to pick up functions and classes from the module rather than
// from the non-modular library which is baked into the 'test-main' library.
// This averts linker problems:
// - strong ownership model: missing linker symbols
// - weak ownership model: duplicate linker symbols
#include "gtest-extra.cc"
#include "util.cc"
#define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly
#include "gtest-extra.h"
// an implicitly exported namespace must be visible [module.interface]/2.2
TEST(module_test, namespace) {
@ -75,9 +67,8 @@ bool oops_detail_namespace_is_visible;
namespace fmt {
bool namespace_detail_invisible() {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
((_MSC_VER == 1929 && _MSC_FULL_VER <= 192930136) || \
(_MSC_VER == 1930 && _MSC_FULL_VER <= 193030704))
// bug in msvc up to 16.11.5 / 17.0-pre5:
_MSC_FULL_VER <= 193700000
// bug in msvc up to at least 17.7:
// the namespace is visible even when it is neither
// implicitly nor explicitly exported
@ -111,7 +102,7 @@ TEST(module_test, macros) {
// but rather visibility of all client-facing overloads, reachability of
// non-exported entities, name lookup and overload resolution within
// 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.
TEST(module_test, to_string) {
@ -140,7 +131,7 @@ TEST(module_test, format_to) {
EXPECT_EQ("42", std::string_view(buffer));
fmt::memory_buffer mb;
fmt::format_to(mb, "{}", 42);
fmt::format_to(std::back_inserter(mb), "{}", 42);
EXPECT_EQ("42", std::string_view(buffer));
std::wstring w;
@ -152,7 +143,7 @@ TEST(module_test, format_to) {
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
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));
}
@ -211,8 +202,8 @@ TEST(module_test, dynamic_format_args) {
TEST(module_test, vformat) {
EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42)));
EXPECT_EQ(L"42", fmt::vformat(fmt::to_string_view(L"{}"),
fmt::make_wformat_args(42)));
EXPECT_EQ(L"42",
fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(42)));
}
TEST(module_test, vformat_to) {
@ -245,9 +236,9 @@ TEST(module_test, vformat_to_n) {
auto wstore = fmt::make_wformat_args(12345);
std::wstring w;
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};
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) {
@ -258,22 +249,18 @@ std::string as_string(std::wstring_view text) {
TEST(module_test, print) {
EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ");
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(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ"));
}
}
TEST(module_test, vprint) {
EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ");
EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)),
"4.2");
if (false) {
EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)),
as_string(L"42µ"));
EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)),
as_string(L"42"));
}
}
TEST(module_test, named_args) {
@ -284,9 +271,7 @@ TEST(module_test, named_args) {
TEST(module_test, literals) {
using namespace fmt::literals;
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", L"{}"_format(42));
}
TEST(module_test, locale) {
@ -320,7 +305,7 @@ TEST(module_test, string_view) {
TEST(module_test, memory_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));
fmt::memory_buffer nbuffer(std::move(buffer));
EXPECT_EQ("42", to_string(nbuffer));
@ -395,27 +380,20 @@ struct test_formatter : fmt::formatter<char> {
bool check() { return true; }
};
struct test_dynamic_formatter : fmt::dynamic_formatter<> {
bool check() { return true; }
};
TEST(module_test, formatter) {
EXPECT_TRUE(test_formatter{}.check());
EXPECT_TRUE(test_dynamic_formatter{}.check());
}
TEST(module_test, formatter) { EXPECT_TRUE(test_formatter{}.check()); }
TEST(module_test, join) {
int arr[3] = {1, 2, 3};
std::vector<double> vec{1.0, 2.0, 3.0};
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, 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(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, 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::gmtime(time_now).tm_year > 120);
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);
}
@ -453,34 +430,16 @@ TEST(module_test, weekday) {
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) {
EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456");
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"));
}
}
TEST(module_test, fprintf) {
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"));
std::wostringstream ws;
fmt::fprintf(ws, L"%s", L"bla");
EXPECT_EQ(L"bla", ws.str());
}
TEST(module_test, sprintf) {
@ -490,25 +449,15 @@ TEST(module_test, sprintf) {
TEST(module_test, vprintf) {
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)),
as_string(L"42"));
}
}
TEST(module_test, vfprintf) {
auto args = fmt::make_printf_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);
if (false) {
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) {
@ -538,8 +487,13 @@ TEST(module_test, buffered_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);
out.close();
#endif
}
struct custom_context {
@ -552,21 +506,15 @@ TEST(module_test, custom_context) {
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) {
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(L"42", fmt::format(L"{:}"_cf, 42));
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"));
#endif
}

View file

@ -22,60 +22,6 @@ using wstring_view = fmt::basic_string_view<wchar_t>;
# 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) {
LPWSTR message = nullptr;
auto result = FormatMessageW(
@ -83,7 +29,8 @@ TEST(os_test, format_windows_error) {
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
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);
fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
@ -108,7 +55,8 @@ TEST(os_test, format_long_windows_error) {
LocalFree(message);
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);
fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
@ -139,6 +87,17 @@ TEST(os_test, report_windows_error) {
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
#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_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()),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
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()),
format_error, "format specifier requires numeric argument");
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{0:05}"), test_string()),
format_error, "format specifier requires numeric argument");
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
@ -106,9 +106,17 @@ TEST(ostream_test, empty_custom_output) {
}
TEST(ostream_test, print) {
{
std::ostringstream os;
fmt::print(os, "Don't {}!", "panic");
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) {
@ -132,7 +140,7 @@ TEST(ostream_test, write_to_ostream_max_size) {
} buffer(max_size);
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 {
const void* v = s;
return xsputn(v, n);
@ -218,45 +226,6 @@ TEST(ostream_test, format_to_n) {
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 {};
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);
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
#ifndef _WIN32
@ -220,11 +214,11 @@ TEST(os_test, getpagesize) {
}
TEST(file_test, open_retry) {
# ifndef _WIN32
write_file("temp", "there must be something here");
std::unique_ptr<file> f{nullptr};
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
"cannot open file temp");
# ifndef _WIN32
char c = 0;
f->read(&c, 1);
# endif

View file

@ -37,8 +37,6 @@ int fstat(int fd, struct stat* buf);
#else
typedef unsigned size_t;
typedef int ssize_t;
errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag,
int pmode);
#endif
#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);
safe_sprintf(buffer, "%#a", 16.0);
EXPECT_PRINTF(buffer, "%#a", 16.0);
safe_sprintf(buffer, "%#A", 16.0);
EXPECT_PRINTF(buffer, "%#A", 16.0);
EXPECT_PRINTF("0x1.p+4", "%#a", 16.0);
EXPECT_PRINTF("0X1.P+4", "%#A", 16.0);
// '#' flag is ignored for non-numeric types.
EXPECT_PRINTF("x", "%#c", 'x');
@ -239,7 +237,7 @@ TEST(printf_test, width) {
// Width cannot be specified twice.
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,
"number is too big");
@ -407,10 +405,7 @@ TEST(printf_test, length) {
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
}
TEST(printf_test, bool) {
EXPECT_PRINTF("1", "%d", true);
EXPECT_PRINTF("true", "%s", true);
}
TEST(printf_test, bool) { EXPECT_PRINTF("1", "%d", true); }
TEST(printf_test, int) {
EXPECT_PRINTF("-42", "%d", -42);
@ -534,11 +529,12 @@ TEST(printf_test, wide_string) {
}
TEST(printf_test, vprintf) {
fmt::format_arg_store<fmt::printf_context, int> as{42};
fmt::basic_format_args<fmt::printf_context> args(as);
EXPECT_EQ(fmt::vsprintf("%d", args), "42");
EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42");
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42");
int n = 42;
auto store = fmt::format_arg_store<fmt::printf_context, int>(n);
auto args = fmt::basic_format_args<fmt::printf_context>(store);
EXPECT_EQ(fmt::vsprintf(fmt::string_view("%d"), args), "42");
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args),
"42");
}
template <typename... Args>
@ -554,31 +550,11 @@ TEST(printf_test, fixed_large_exponent) {
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
}
TEST(printf_test, vsprintf_make_args_example) {
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);
TEST(printf_test, make_printf_args) {
EXPECT_EQ("[42] something happened",
fmt::vsprintf("[%d] %s happened", args2));
EXPECT_EQ("[42] something happened",
fmt::vsprintf("[%d] %s happened",
fmt::vsprintf(fmt::string_view("[%d] %s happened"),
{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",
fmt::vsprintf(L"[%d] %s happened", args));
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::vsprintf(fmt::basic_string_view<wchar_t>(L"[%d] %s happened"),
{fmt::make_wprintf_args(42, L"something")}));
}

View file

@ -12,7 +12,10 @@
#include "fmt/ranges.h"
#include <map>
#include <queue>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
@ -40,6 +43,8 @@ TEST(ranges_test, format_2d_array) {
TEST(ranges_test, format_array_of_literals) {
const char* arr[] = {"1234", "abcd"};
EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
EXPECT_EQ(fmt::format("{:n}", arr), "\"1234\", \"abcd\"");
EXPECT_EQ(fmt::format("{:n:}", arr), "1234, abcd");
}
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
@ -47,17 +52,28 @@ TEST(ranges_test, format_vector) {
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
EXPECT_EQ(fmt::format("{::#x}", v), "[0x1, 0x2, 0x3, 0x5, 0x7, 0xb]");
EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
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) {
auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
EXPECT_EQ(fmt::format("{:::#x}", v), "[[0x1, 0x2], [0x3, 0x5], [0x7, 0xb]]");
EXPECT_EQ(fmt::format("{:n:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb");
}
TEST(ranges_test, format_map) {
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
}
TEST(ranges_test, format_set) {
@ -65,6 +81,35 @@ TEST(ranges_test, format_set) {
"{\"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 {
struct box {
int value;
@ -91,6 +136,7 @@ TEST(ranges_test, format_tuple) {
auto t =
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
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));
}
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
struct tuple_like {
int i;
@ -144,32 +209,24 @@ TEST(ranges_test, format_to) {
EXPECT_STREQ(buf, "[1, 2, 3]");
}
struct path_like {
template <typename Char> struct path_like {
const path_like* begin() const;
const path_like* end() const;
operator std::string() const;
operator std::basic_string<Char>() const;
};
TEST(ranges_test, path_like) {
EXPECT_FALSE((fmt::is_range<path_like, char>::value));
TEST(ranges_test, disabled_range_formatting_of_path) {
// 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
struct string_like {
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.
// 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
// 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;
public:
using const_iterator = typename ::std::vector<T>::const_iterator;
using iterator = typename ::std::vector<T>::iterator;
template <typename... 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&) = delete;
const_iterator begin() const { return vec.begin(); }
const_iterator end() const { return vec.end(); }
iterator begin() { return vec.begin(); }
iterator end() { return vec.end(); }
};
TEST(ranges_test, range) {
@ -232,7 +289,6 @@ TEST(ranges_test, enum_range) {
}
#if !FMT_MSC_VERSION
TEST(ranges_test, unformattable_range) {
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
fmt::format_context>::value));
@ -364,7 +420,7 @@ TEST(ranges_test, is_printable) {
EXPECT_FALSE(is_printable(0x110000));
}
TEST(ranges_test, escape_string) {
TEST(ranges_test, escape) {
using vec = std::vector<std::string>;
EXPECT_EQ(fmt::format("{}", vec{"\n\r\t\"\\"}), "[\"\\n\\r\\t\\\"\\\\\"]");
EXPECT_EQ(fmt::format("{}", vec{"\x07"}), "[\"\\x07\"]");
@ -375,22 +431,22 @@ TEST(ranges_test, escape_string) {
EXPECT_EQ(fmt::format("{}", vec{"\xcd\xb8"}), "[\"\\u0378\"]");
// Unassigned Unicode code points.
EXPECT_EQ(fmt::format("{}", vec{"\xf0\xaa\x9b\x9e"}), "[\"\\U0002a6de\"]");
// Broken utf-8.
EXPECT_EQ(fmt::format("{}", vec{"\xf4\x8f\xbf\xc0"}),
"[\"\\xf4\\x8f\\xbf\\xc0\"]");
EXPECT_EQ(fmt::format("{}", vec{"понедельник"}), "[\"понедельник\"]");
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
struct convertible_to_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(ranges_test, escape_convertible_to_string_view) {
EXPECT_EQ(fmt::format("{}", std::vector<convertible_to_string_view>(1)),
"[\"foo\"]");
EXPECT_EQ(fmt::format("{}", std::vector<std::vector<char>>{{'x'}}),
"[['x']]");
EXPECT_EQ(fmt::format("{}", std::tuple<std::vector<char>>{{'x'}}), "(['x'])");
}
#endif // FMT_USE_STRING_VIEW
template <typename R> struct fmt_ref_view {
R* r;
@ -406,3 +462,66 @@ TEST(ranges_test, range_of_range_of_mixed_const) {
fmt_ref_view<decltype(v)> r{&v};
EXPECT_EQ(fmt::format("{}", r), "[[1, 2, 3], [4, 5]]");
}
TEST(ranges_test, vector_char) {
EXPECT_EQ(fmt::format("{}", std::vector<char>{'a', 'b'}), "['a', 'b']");
}
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");
}
#ifndef _WIN32
#ifdef FMT_HAVE_STRPTIME
namespace fmt {
template <> struct scanner<tm> {
std::string format;

View file

@ -42,7 +42,7 @@ struct scan_context {
public:
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 end() const { return begin() + input_.size(); }
@ -83,17 +83,21 @@ class scan_arg {
// TODO: more types
};
scan_arg() : type(scan_type::none_type) {}
scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {}
scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {}
scan_arg(long long& value)
FMT_CONSTEXPR scan_arg() : type(scan_type::none_type), int_value(nullptr) {}
FMT_CONSTEXPR scan_arg(int& value)
: type(scan_type::int_type), int_value(&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) {}
scan_arg(unsigned long long& value)
FMT_CONSTEXPR scan_arg(unsigned long long& value)
: type(scan_type::ulong_long_type), ulong_long_value(&value) {}
scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {}
scan_arg(fmt::string_view& value)
FMT_CONSTEXPR scan_arg(std::string& 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) {}
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.scan = scan_custom_arg<T>;
}
@ -114,7 +118,7 @@ struct scan_args {
const detail::scan_arg* data;
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()) {
static_assert(N < INT_MAX, "too many arguments");
}
@ -154,7 +158,8 @@ struct scan_handler : error_handler {
}
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) {}
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) {
auto size = to_unsigned(end - begin);
auto it = scan_ctx_.begin();
if (it + size > scan_ctx_.end() ||
!std::equal(begin, end, make_checked(it, size))) {
if (it + size > scan_ctx_.end() || !std::equal(begin, end, it))
on_error("invalid input");
}
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)

View file

@ -6,52 +6,110 @@
// For the license information refer to format.h.
#include "fmt/std.h"
#include "fmt/ranges.h"
#include <bitset>
#include <stdexcept>
#include <string>
#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 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)
EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" ");
EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")),
"\"foo\\\"bar.txt\"");
using std::filesystem::path;
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar");
EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\"");
# ifdef _WIN32
// File.txt in Russian.
const wchar_t unicode_path[] = {0x424, 0x430, 0x439, 0x43b, 0x2e,
0x74, 0x78, 0x74, 0};
const char unicode_u8path[] = {'"', char(0xd0), char(0xa4), char(0xd0),
char(0xb0), char(0xd0), char(0xb9), char(0xd0),
char(0xbb), '.', 't', 'x',
't', '"', '\0'};
EXPECT_EQ(fmt::format("{}", std::filesystem::path(unicode_path)),
unicode_u8path);
EXPECT_EQ(fmt::format("{}", path(
L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
# endif
#endif
}
// Test ambiguity problem described in #2954.
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 c = std::vector<std::string>{"abc", "def"};
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
"path=\"foo/bar.txt\", range=[\"abc\", \"def\"]");
#endif
"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
TEST(std_test, thread_id) {
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) {
#ifdef __cpp_lib_variant
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("{}", 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
}
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, legacy_locale) {
auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251");
auto loc = get_locale("be_BY.CP1251", "Belarusian_Belarus.1251");
if (loc == std::locale::classic()) return;
auto s = std::string();
try {
s = fmt::format(loc, "День недели: {:L}", fmt::weekday(1));
s = fmt::format(loc, "Дзень тыдня: {:L}", fmt::weekday(1));
} catch (const fmt::format_error& e) {
// Formatting can fail due to an unsupported encoding.
fmt::print("Format error: {}\n", e.what());
@ -38,11 +38,11 @@ TEST(unicode_test, legacy_locale) {
os << std::put_time(&tm, "%a");
auto wd = os.str();
if (wd == "??") {
EXPECT_EQ(s, "День недели: ??");
EXPECT_EQ(s, "Дзень тыдня: ??");
fmt::print("std::locale gives ?? as a weekday.\n");
return;
}
#endif
EXPECT_THAT((std::vector<std::string>{"День недели: пн", "День недели: Пн"}),
EXPECT_THAT((std::vector<std::string>{"Дзень тыдня: пн", "Дзень тыдня: Пан"}),
Contains(s));
}

View file

@ -10,11 +10,7 @@
#include <locale>
#include <string>
#ifdef FMT_MODULE_TEST
import fmt;
#else
# include "fmt/os.h"
#endif // FMT_MODULE_TEST
#include "fmt/os.h"
#ifdef _MSC_VER
# define FMT_VSNPRINTF vsprintf_s
@ -81,5 +77,5 @@ class date {
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);

View file

@ -7,6 +7,7 @@
#include "fmt/xchar.h"
#include <algorithm>
#include <complex>
#include <cwchar>
#include <vector>
@ -15,31 +16,21 @@
#include "fmt/color.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
#include "fmt/std.h"
#include "gtest-extra.h" // Contains
#include "util.h" // get_locale
using fmt::detail::max_value;
using testing::Contains;
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()};
}
#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
struct non_string {};
} // namespace test_ns
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>;
EXPECT_TRUE(std::is_empty<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<test_ns::non_string>::value);
EXPECT_FALSE(fmt::detail::is_string<non_string>::value);
}
// 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"abc", fmt::format(L"{}", L"abc"));
EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error);
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
EXPECT_EQ(L"true", fmt::format(L"{}", true));
EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
@ -98,8 +88,9 @@ TEST(xchar_test, is_formattable) {
}
TEST(xchar_test, compile_time_string) {
EXPECT_EQ(fmt::format(fmt::wformat_string<int>(L"{}"), 42), L"42");
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
EXPECT_EQ(fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42), L"42");
#endif
}
@ -111,10 +102,12 @@ struct custom_char {
template <typename T>
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
template <> struct is_char<custom_char> : std::true_type {};
@ -153,17 +146,21 @@ TEST(xchar_test, format_to) {
}
TEST(xchar_test, vformat_to) {
using wcontext = fmt::wformat_context;
fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42);
auto wargs = fmt::basic_format_args<wcontext>(&warg, 1);
auto args = fmt::make_wformat_args(42);
auto w = std::wstring();
fmt::vformat_to(std::back_inserter(w), L"{}", wargs);
EXPECT_EQ(L"42", w);
w.clear();
fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs);
fmt::vformat_to(std::back_inserter(w), L"{}", args);
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) {
wchar_t buffer[4];
buffer[3] = L'x';
@ -199,7 +196,10 @@ TEST(xchar_test, named_arg_udl) {
TEST(xchar_test, print) {
// 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) {
@ -229,11 +229,24 @@ TEST(xchar_test, enum) {
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
}
struct streamable_and_unformattable {};
auto operator<<(std::wostream& os, streamable_and_unformattable)
-> std::wostream& {
return os << L"foo";
}
TEST(xchar_test, streamed) {
EXPECT_FALSE(fmt::is_formattable<streamable_and_unformattable>());
EXPECT_EQ(fmt::format(L"{}", fmt::streamed(streamable_and_unformattable())),
L"foo");
}
TEST(xchar_test, sign_not_truncated) {
wchar_t format_str[] = {
L'{', L':',
'+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error);
EXPECT_THROW(fmt::format(fmt::runtime(format_str), 42), fmt::format_error);
}
TEST(xchar_test, chrono) {
@ -269,8 +282,9 @@ std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
#endif
}
TEST(chrono_test, time_point) {
auto t1 = std::chrono::system_clock::now();
TEST(chrono_test_wchar, time_point) {
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::now());
std::vector<std::wstring> spec_list = {
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
@ -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"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"};
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
#ifndef _WIN32
// Disabled on Windows, because these formats is not consistent among
// platforms.
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
#elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
#endif
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
// Timezone formatters tests makes sense for localtime.
#if FMT_HAS_C99_STRFTIME
spec_list = {L"%z", L"%Z"};
#else
spec_list = {L"%Z"};
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
@ -293,8 +329,38 @@ TEST(chrono_test, time_point) {
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt_spec, t1));
EXPECT_EQ(sys_output, fmt::format(fmt_spec, tm));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), 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) {
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 409
{
std::wostringstream wos;
fmt::print(wos, L"Don't {}!", L"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
}
@ -325,6 +399,7 @@ TEST(xchar_test, escape_string) {
TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Char> struct numpunct : std::numpunct<Char> {
protected:
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}", 12000.0), "12~000");
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) {
@ -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("-256", fmt::format(loc, "{:L}", -256));
fmt::format_arg_store<fmt::format_context, int> as{1234567};
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as)));
auto n = 1234567;
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::make_format_args(n)));
auto s = std::string();
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
EXPECT_EQ("1~234~567", s);
@ -402,10 +480,9 @@ TEST(locale_test, wformat) {
auto loc = std::locale(std::locale(), new numpunct<wchar_t>());
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
using wcontext = fmt::buffer_context<wchar_t>;
fmt::format_arg_store<wcontext, int> as{1234567};
int n = 1234567;
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));
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>()));
}
TEST(locale_test, double_formatter) {
TEST(locale_test, int_formatter) {
auto loc = std::locale(std::locale(), new special_grouping<char>());
auto f = fmt::formatter<int>();
auto parse_ctx = fmt::format_parse_context("L");
f.parse(parse_ctx);
char buf[10] = {};
fmt::basic_format_context<char*, char> format_ctx(
buf, {}, fmt::detail::locale_ref(loc));
*f.format(12345, format_ctx) = 0;
EXPECT_STREQ("12,345", buf);
auto buf = fmt::memory_buffer();
fmt::basic_format_context<fmt::appender, char> format_ctx(
fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
f.format(12345, format_ctx);
EXPECT_EQ(fmt::to_string(buf), "12,345");
}
FMT_BEGIN_NAMESPACE
@ -442,13 +519,10 @@ template <class charT> struct formatter<std::complex<double>, charT> {
public:
FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
basic_format_parse_context<charT>& ctx) {
using handler_type =
detail::dynamic_specs_handler<basic_format_parse_context<charT>>;
detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
detail::type::string_type);
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
detail::parse_float_type_spec(specs_, ctx.error_handler());
return it;
auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type::float_type);
detail::parse_float_type_spec(specs_, detail::error_handler());
return end;
}
template <class FormatContext>
@ -495,4 +569,16 @@ TEST(locale_test, chrono_weekday) {
std::locale::global(loc_old);
}
TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
}
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